讨论微服务之前,你知道微服务的 4 个定义吗?

关于“什么是微服务”的问题,其实并没有一个统一的认识。这些年在不同的场合里和不同背景的朋友都在探讨微服务。但聊得越多,越发现大家聊的不是同一回事。和 DevOps 一样,“微服务”也是一个内涵十分广泛的词。本文从“Microservice“这个概念的源头出发,总结了 4 个常用的微服务定义。

James Lewis 原始版的微服务 6 大特征

这个版本起源于2012年,这里首先要注意年份,那时候还没有 Docker,而且 Netflix 的微服务化过程也在这个概念提出之前——2008年就开始了,那时候甚至连 DevOps 还没发明出来。James Lewis 在波兰第 33 次 Degree in Kraków 会议上分享了一个案例,名称是 “Micro Services – Java, the Unix Way”。在这个分享里, James Lewis 分享了在 2011 年中参与的一个项目中所采用的一系列实践,以 UNIX 的哲学重新看待企业级 Java 应用程序,并且把其中的一部分称之为“ Micro-Services ”。

这个时候的微服务所用的单词和我们现在所用的 Microservices 这个单词有所不同。一方面,采用 Micro 作为形容词,是和 Monolithic 相对,而不是和 Macro 相对是源于操作系统这门大学课程。我们知道,现代的操作系统课程都是以 UNIX 作为案例进行讲解的。而这两个单词来自于“微内核”(Micro-Kernel)和“宏内核”(Monolithic kernel)的比较。而非常见的“微观经济学”和“宏观经济学”中的 Micro 和 Macro 两个相对应的单词。

另一方面,服务要以复数形式出现,表示的是一个以上。由于汉语里单复数是同型的,所以我们在翻译的时候会出现问题。因此,“微服务”在作为架构的形式出现的时候,我们会用“微服务架构”称呼。单个的微服务从概念上为了和 SOA 以及其它领域的“服务”有所区分,会以“单个微服务”以示区别。而“微服务”单独拿出来是被看作为一系列技术实践的总称。

在这个分享里,James Lewis将所实践的“微服务架构”总结为 6 大特征:

  1. Small with a single responsibility —— “小到只有单一原则”在这个特征里,关于微服务有多小有两个标准:第一个标准是如果一个类复杂的超过一个开发人员的理解范围,那么它就太大了,需要被继续拆分。第二个标准是:如果它小到可以随时丢弃并重写,而不是继续维护遗留代码,那么它就足够小。这个标准有个很重要的原则就是 Rewrite over Maintain,即“重写胜于维护”。
  2. Containerless and installed as well behaved Unix services —— “去容器化并且作为 Unix Service 安装”在这个特征中,James Lewis 提倡采用 Jetty 这样的工具集成到 Maven 里,可以很方便的调试或者部署,然后打包成一个可执行的 JAR 包并以 UNIX 守护进程的方式在系统启动时执行。特别是在 AWS 这样的公有云环境下,把这样的应用程序和虚拟机实例的初始化脚本结合在一起。使得应用程序的生命周期和虚拟机的生命周期绑定成为一体,由于守护进程在所有 Unix 系统中都是通用的,因此简化整体架构的开发和运维。
  3. Located in different VCS roots ——“分布在不同的版本控制代码库里”在这个特征中,James Lewis 提到了应用程序的分离,他认为一个“微服务”应该完全和另外一个服务实现彻底的隔离,这里当然是指的从开始的代码库就开始隔离了。他同样也要求开发人员看到相似性和抽象,并采用单一的领域来指导开发团队的开发。因此接下来他继续讨论了领域驱动设计领域驱动设计和康威定律的重要性。他认为界限上下文要足够的清晰,但可以有所重合。如果没有办法做到领域之间很清晰,就通过“物理上的手段”——分离不同的团队来做到这一点。这不可避免的带来一些公共代码,但要把这些公共代码作为“库”和“基础设施即代码”来对待,就像你代码中用到的开源软件。并搭建一个 nexus 库来存储那些二进制依赖
  4. Provisioned automatically ——“自动初始化”自动初始化的要点不在于如何自动化,因为不同的应用不同的平台有不同的初始化方式。这里的重点在于管理分布式应用的复杂性。所以对于每个服务,能够采用声明出这些初始化。例如:服务 A,需要一个 负载均衡,并且可以自动扩展。服务 B,也是同样的声明方式。而这些声明可以用基础设施即代码技术很好的管理起来。
  5. Status aware and auto-scaling ——“关注状态和自动扩展”在这里,他认为这些应用应该是能够感知吞吐量的监控指标来自我进行扩展的。对于一个现代的应用而言,这是一个基本的架构性要求,但这需要团队有一定的 DevOps 能力。因为这不光要求开发人员能够让应用无状态化,而且要求基础设施可以及时捕获环境的变化。
  6. They interact via the uniform interface —— “它们通过统一格式的接口进行交互”在这里,James 建议大家采用已经成熟的 HTTP 协议以及标准的媒体类型进行接口交互,而不是采用其它的方式。并且采用HATEOAS(Hypermedia As The Engine Of Application State) 的方式构建 Restful API,使其成为一个超媒体的应用状态引擎。这样就可以将状态和执行过程隔离区分开来,更加容易进行水平扩展。此外,它也构建了一个避免架构孵化的层,可以独立于客户端持续演进。

在总结的时候,它特意提到了 UNIX 哲学。这引用自Doug McIlroy 的一篇采访

Everybody started putting forth the UNIX philosophy. Write programs that do one thing and do it well. Write programs to work together. Write programs that handle text streams, because that is a universal interface.” Those ideas which add up to the tool approach, were there in some unformed way before pipes, but they really came together afterwards. Pipes became the catalyst for this UNIX philosophy. “The tool thing has turned out to be actually successful. With pipes, many programs could work together, and they could work together at a distance.”

从这段话里,我们看到了“微服务架构”和 UNIX 哲学之间的关联:

  1. 职责独立:让多个程序(注意是 Programs 不是 Program)做好一件事。
  2. 统一接口:文本流是统一的接口,每个程序都可以通过统一的接口进行消费。
  3. 公共通信:采用管道(pipe)的方式可以说,微服务架构本身是对 UNIX 哲学在企业级 Java 应用系统中的另一个案例。

可以说,虽然应用场景变了,但 UNIX 分解复杂度的方式和保持简单的理念并未改变。

最后,James Lewis 把上述六点特征变成了一个六边形的业务能力:

Hexagonal Business capabilities composed of: Micro Services that you can Rewrite rather than maintain and which form A Distributed Bounded Context. Deployed as containerless OS services With standardised application protocols and message semantics Which are auto-scaling and designed for failure

翻译过来就是:

微服务可以通过重写而非维护一个分布式的界限上下文,且作为一个无应用容器的操作系统服务部署。并以标准化的应用协议和消息语义,为失败设计且可自动扩展。

Martin Fowler & James Lewis 合作版的微服务 9 大特征

由于在 James Lewis 之后,有很多不同的项目也采用“微服务”作为它们的实践名称。然而,不同项目之间还是存在一些差异的,且每个人都按照自己的方式在实践“微服务”。因此,基于“求同存异”的原则,Jame Lewis 的同事 —— 大名鼎鼎的 Martin Fowler 采用一种归纳的方式来解决这个问题:他认为“定义”是一些“共有的特征”(Common characteristics)。Martin Fowler 继续采用了 James Lewis 对这一系列实践的命名,并且做了修改,使之成为一个单独的名词 —— Microservices。

所以,他将微服务总结为以下9大特征

  1. 通过服务组件化
  2. 围绕业务能力组织
  3. 是产品不是项目
  4. 智能端点和哑管道
  5. 去中心化治理
  6. 去中心化数据管理
  7. 基础设施自动化
  8. 为失效设计
  9. 演进式设计

这 9 大特征的中文版具体内容请参考这里,限于篇幅原因,本文不展开讨论。

我们可以从中看出,Martin Fowler 试图将 James Lewis 的微服务定义进行一般化推广,使其不光可以在不同的语言架构和技术栈上使用。又可以兼顾敏捷、DevOps 等其它技术,成为一个架构的“最佳实践”集合。但这样一组实践本质上并没有太多的创新,只是把我们本身知道的很多架构和设计的原则结合在当前的技术栈上进行了一次整体的组合和应用。

恰逢一系列互联网公司的成功事迹带来的新实践(持续交付、DevOps)和新技术(Docker)在经历了早期实践者(Early Adopter)实践积累后的结果井喷后。这样的最佳实践的集中反应固然得到了技术人员的掌声。然而,这种定义对于妄图采用“微服务架构“的人来说是一个很高的门槛。如果这样的 9 个特征的总结是对”微服务架构“的定义。那么,为了要满足以上的 9 个定义,则需要花费很大的精力来进行改造,而且已经超出了技术升级和企业 IT 部门的职责范围。此外,即便我们知道其中每个特征所带来的收益,但却很难拿出案例和数据去佐证满足这 9 个特征的改造收益。

避开这 9 个特征的概念正交性不谈,即便这 9 个特征可以从既有的结果来回答”什么(What)是微服务“,但却没有给出“为什么(Why)要满足这些特征”和”如何(How)同时满足这些特征”。

如果自己挖的坑填不了,就教给别人来填吧:

Sam Newman 版微服务的两大特征和 7 个原则

同样作为 Martin Fowler 的同事,Sam Newman 在其著作 “Building Microservice”(中文译名为“微服务设计”)的第一章就重新回答了”什么是微服务架构“并回答了”为什么要采用微服务架构“的问题。

Sam Newman 在书中是这么定义微服务的(《微服务设计》的翻译):

微服务就是一些协同工作的小而自治的服务。

Sam Newman 自述的微服务的定义更加简单,包含了两个特征:“小” 和 “自治”。

除了继承 James Lewis 关于微服务应该有多小的描述以外(当然,大小都是基于个人的主观判断),还创造性的用康威定律来约束微服务的大小,即“能否和团队结构相匹配”:如果你的团队维护单个服务很吃力,需要保持团队大小不变的情况下还对维护工作游刃有余,那么这个服务就需要继续被拆分。

而“自治” 则很谨慎的把 Martin Fowler 微服务定义的 9 大特征中的“去中心化”、“独立” 、”松散耦合“等字眼进行了统一。并进一步解释到“一个微服务就是一个独立的实体”。并且从外部,也就是黑盒的角度来看每个符合”自治”的单个微服务所具有的特征,即:

  1. 可以独立部署。
  2. 通过网络通信。
  3. 对消费方的透明。
  4. 尽可能降低耦合,使其自治。

此外,他还采用了更简单的“黄金法则”来判断期”自治性”。即能否修改一个服务并对其部署,且不影响其他任何服务。如果答案是否定的,说明你的微服务还不够”自治“。

从 Sam Newman 的定义中,我们可以推导出“微服务”的几个基本事实:

  1. 微服务架构是一个分布式系统架构。
  2. 微服务是微服务架构的基本单元。
  3. 网络隔离是“必要的”解耦手段。
  4. 微服务的业务功能从概念上是完整的,并符合用户角度的“独立”认知。

简而言之,以上的两个特征的表述主要是将微服务从逻辑架构上和部署架构上都看作是一个正交的原子功能单元。而要做到这一点,则需要而要把整个应用系统正确的建模到这个层次,则需要参考很多的内部外部因素。

此外,为了达到“小”和“自治”的目的,Sam Newman 还总结了 7 条原则用来在实施的时候和具体实践结合,分别是:

  1. 围绕业务概念建模
  2. 接受自动化文化
  3. 隐藏内部实现细节
  4. 让一切都去中心化
  5. 可独立部署
  6. 隔离失败
  7. 高度可观察

可以看出,Sam Newman 把 Martin Fowler 的 9 大特征用更加具体的术语来重新描述,并且从逻辑上处理了 Martin Fowler 微服务 9 大特征中概念重复和不明确的部分,使其更简单和明确并且更加可操作。例如把“去中心化的数据管理” 和 “去中心化治理”合并为“让一切都去中心化”等。

更重要的是,Sam Newman 提出了采用微服务技术的主要好处,告诉了我们“为什么要用微服务”:

  1. 技术异构性:采用更合适的技术栈灵活的处理局部问题。
  2. 弹性:这里的“弹性”是弹性工程学的概念,指的是局部失败会被隔离,使得整体不会失败。
  3. 扩展:可以根据系统的部分组件按需扩展。
  4. 简化部署:这里简化部署不是指的是部署的拓扑结构,而是通过持续的小批量、小范围的部署来降低整体失败的风险。
  5. 与组织结构相匹配:微服务架构可以让组织的团队转化为合适的大小,并采用透明的制度来进行规范和复制。避免团队的人数增长而带来更多的管理层,使组织熵的上涨。
  6. 可组合性:由于各个微服务间不存在依赖关系,所以可以根据用户界面的情况进行灵活的调整和复用,避免对单体应用进行整体的大规模调整。
  7. 对可替代性的优化:由于风险和领域更加独立和隔离。因此,抛弃一个微服务并重写的成本并就变的十分低廉。

Chris Richardson 的“微服务架构模式”

2017 年,Chris Richardson 使用 Microservices.io 域名开始推广自己的微服务理念。他是这样定义微服务的:

Microservices – also known as the microservice architecture – is an architectural style that structures an application as a collection of loosely coupled services, which implement business capabilities. The microservice architecture enables the continuous delivery/deployment of large, complex applications. It also enables an organization to evolve its technology stack.

中文翻译过来,大意如下:

微服务,也就是微服务架构。是一种用于把一个应用程序结构化为一个实现业务功能的松散耦合的服务集合的架构风格。 微服务架构使得在大型、复杂的应用程序中实现持续交付和持续部署成为可能。它使得组织可以演进自己的技术栈。

在 Chris Richardson 采用了较为简单的架构定义和准确的目标定义相结合的方式来定义”微服务架构“:它一方面简单的把微服务架构定义成一个实现业务功能的松散耦合的服务集合,另一方面又以十分具体的目标和结果(持续交付/持续集成)来约束这样一个松散耦合系统的效果:组织可以演进自己的技术栈。

Chris Richardson 将“单体架构”和“微服务架构”看做两种架构模式。并且在同样的上下文中对二者各自的优劣进行了比较。更加重要的是,Chris Richardson 采用 AFK 扩展立方来拆分微服务从而回答了“如何做微服务”的问题。

值得注意的是,Chris Richardson 所采用的例子虽然在同样的上下文中,但由于特征不同并不具备可比较性。因此,他采用了在“单体架构模式”(Pattern: Monolithic Architecture)的基础上描述其局限性的方法引出了“微服务架构模式”(Pattern: Microservice Architecture)。严格的说,Chris Richardson 的“单体架构模式”是一种对现状的和举例,并没有给出其特征和方法的描述,因此不能称之为模式。而“微服务架构模式”则又是一系列模式的总和,如下图所示:

从这个角度看,Chris Richardson 的这些模式并没有突破 Sam Newman 在《微服务设计》中总结出的实践。但相较于我们所知道的微服务的优点。Chris Richardson 也列出了微服务的缺点:

  1. 开发者的 IDE 对分布式系统的在线开发和调试相对于单体应用架构来说并不友好。
  2. 测试更加困难。
  3. 开发者必须实现跨服务的通信机制。
  4. 不采用分布式事务来跨服务构建业务是十分困难的。
  5. 需要进行跨团队的协调工作。
  6. 部署更加复杂。
  7. 更多的内存消费,对于 Java 应用来说,独立的部署意味着无法共享 JVM 的内存管理。

相较于之前的微服务定义而言, Chris Richardson 的微服务体系比较完整,而不仅仅是总结和列举实践。Chris Richardson 的”微服务架构模式”不光回答了“什么是(What)微服务”,也回答了“为什么(Why)要用微服务”,“什么时候(When)用微服务”,“什么场景(Where)下”以及“如何(How)实现微服务”的问题。

Chris Richardson 还编写了一套微服务的指南,可以在这里查看。

比“什么是微服务”更重要的事

本文总结了微服务常见的 4 个定义。但比这些定义更重要的是你为什么要用微服务?你想从微服务中获得什么益处?你是否了解为了追求这些益处所带来的代价?如果不先明确这些问题,在不理解微服务架构或者技术所带来的的风险和成本。盲目的采用所谓的微服务,可能带来的结果并不理想。

不过,在讨论这些问题之前,坐下来统一一下对微服务的理解,会提升我们讨论和实践微服务的效率。


更多精彩洞见,请关注微信公众号:思特沃克

Share

Serverless的微服务持续交付案例

本文是GitChat《Serverless 微服务的持续交付》部分内容已做修改。文章聊天实录请见:“顾宇:Serverless 微服务的持续交付解析

Serverless 风格微服务的持续交付(上):架构案例”中,我们介绍了一个无服务器风格的微服务的架构案例。这个案例中混合了各种风格的微服务。

架构图如下:

在这个架构中,我们采用了前后端分离的技术。我们把 HTML,JS, CSS 等静态内容部署在 S3 上,并通过 CloudFront 作为 CDN 构成了整个架构的前端部分。我们把 Amazon API Gateway 作为后端的整体接口连接后端的各种风格的微服务,无论是运行在 Lambda 上的函数,还是运行在 EC2 上的 Java 微服务,他们整体构成了这个应用的后端部分。

从这个架构图上我们可以明显的看到 前端(Frontend)和后端(Backend)的区分。

持续部署流水线的设计和实现

任何 DevOps 部署流水线都可以分为三个阶段:待测试,待发布,已发布

由于我们的架构是前后端分离的,因此我们为前端和后端分别构造了两条流水线,使得前后端开发可以独立。如下图所示:

(整体流水线)

在这种情况下,前端团队和后端团队是两个不同的团队,可以独立开发和部署,但在发布的时候则有些不同。由于用户是最后感知功能变化的。因此,为了避免界面报错找不到接口,在新增功能的场景下,后端先发布,前端后发布。在删除功能的场景下,前端先发布,后端后发布。

我们采用 Jenkins 构建我们的流水线,Jenkins 中已经含有足够的 AWS 插件可以帮助我们完成整个端到端的持续交付流水线。

前端流水线

前端持续交付流水线如下所示:

前端流水线的各步骤过程如下:

  1. 我们采用 BDD/ATDD 的方式进行前端开发。用 NightWatch.JS 框架做 端到端的测试,mochachai 用于做某些逻辑的验证。
  2. 我们采用单代码库主干(develop 分支)进行开发,用 master 分支作为生产环境的部署。生产环境的发布则是通过 Pull Request 合并的。在合并前,我们会合并提交。
  3. 前端采用 Webpack 进行构建,形成前端的交付产物。在构建之前,先进行一次全局测试。
  4. 由于 S3 不光可以作为对象存储服务,也可以作为一个高可用、高性能而且成本低廉的静态 Web 服务器。所以我们的前端静态内容存储在 S3 上。每一次部署都会在 S3 上以 build 号形成一个新的目录,然后把 Webpack 构建出来的文件存储进去。
  5. 我们采用 Cloudfront 作为 CDN,这样可以和 S3 相互集成。只需要把 S3 作为 CDN 的源,在发布时修改对应发布的目录就可以了。

由于我们做到了前后端分离。因此前端的数据和业务请求会通过 Ajax 的方式请求后端的 Rest API,而这个 Rest API 是由 Amazon API Gateway 通过 Swagger 配置生成的。前端只需要知道 这个 API Gateway,而无需知道API Gateway 的对应实现。

后端流水线

后端持续交付流水线如下所示:

后端流水线的各步骤过程如下:

  1. 我们采用“消费者驱动的契约测试”进行开发,先根据前端的 API 调用构建出相应的 Swagger API 规范文件和示例数据。然后,把这个规范上传至 AWS API Gateway,AWS API Gateway 会根据这个文件生成对应的 REST API。前端的小伙伴就可以依据这个进行开发了。
  2. 之后我们再根据数据的规范和要求编写后端的 Lambda 函数。我们采用 NodeJS 作为 Lambda 函数的开发语言。并采用 Jest 作为 Lambda 的 TDD 测试框架。
  3. 和前端一样,对于后端我们也采用单代码库主干(develop 分支)进行开发,用 master 分支作为生产环境的部署。
  4. 由于 AWS Lambda 函数需要打包到 S3 上才能进行部署,所以我们先把对应的构建产物存储在 S3 上,然后再部署 Lambda 函数。
  5. 我们采用版本化 Lambda 部署,部署后 Lambda 函数不会覆盖已有的函数,而是生成新版本的函数。然后通过别名(Alias)区分不同前端所对应的函数版本。默认的 $LATEST,表示最新部署的函数。此外我们还创建了 Prod,PreProd, uat 三个别名,用于区分不同的环境。这三个别名分别指向函数某一个发布版本。例如:函数 func 我部署了4次,那么 func 就有 4个版本(从1开始)。然后,函数 func 的 $LATEST 别名指向 4 版本。别名 PreProd 和 UAT 指向 3 版本,别名 Prod 在 2 版本。
  6. 技术而 API 的部署则是修改 API Gateway 的配置,使其绑定到对应版本的函数上去。由于 API Gateway 支持多阶段(Stage)的配置,我们可以采用和别名匹配的阶段绑定不同的函数。
  7. 完成了 API Gateway 和 Lamdba 的绑定之后,还需要进行一轮端到端的测试以保证 API 输入输出正确。
  8. 测试完毕后,再修改 API Gateway 的生产环境配置就可以了。

部署的效果如下所示:

(API Gateway + Lambda 配置)

无服务器微服务的持续交付新挑战

在实现以上的持续交付流水线的时候,我们踩了很多坑。但经过我们的反思,我们发现是云计算颠覆了我们很多的认识,当云计算把某些成本降低到趋近于 0 时。我们发现了以下几个新的挑战:

  1. 如果你要 Stub,有可能你走错了路。
  2. 测试金字塔的倒置。
  3. 你不再需要多个运行环境,你需要一个多阶段的生产环境 (Multi-Stage Production)。
  4. 函数的管理和 Nanoservice 反模式。

Stub ?别逗了

很多开发者最初都想在本地建立一套开发环境。由于 AWS 多半是通过 API 或者 CloudFormation 操作,因此开发者在本地开发的时候对于AWS 的外部依赖进行打桩(Stub) 进行测试,例如集成 DynamoDB(一种 NoSQL 数据库),当然你也可以运行本地版的 DynamoDB,但组织自动化测试的额外代价极高。然而随着微服务和函数规模的增加,这种管理打桩和构造打桩的虚拟云资源的代价会越来越大,但收效却没有提升。另一方面,往往需要修改几行代码立即生效的事情,却要执行很长时间的测试和部署流程,这个性价比并不是很高。

这时我们意识到一件事:如果某一个环节代价过大而价值不大,你就需要思考一下这个环节存在的必要性。

由于 AWS 提供了很好的配置隔离机制,于是为了得到更快速的反馈,我们放弃了 Stub 或构建本地 DynamoDB,而是直接部署在 AWS 上进行集成测试。只在本地执行单元测试,由于单元测试是 NodeJS 的函数,所以非常好测试。

另外一方面,我们发现了一个有趣的事实,那就是:

测试金字塔的倒置

由于我们采用 ATDD 进行开发,然后不断向下进行分解。在统计最后的测试代码和测试工作量的的时候,我们有了很有趣的发现:

  • End-2-End (UI)的测试代码占30%左右,占用了开发人员 30% 的时间(以小时作为单位)开发和测试。
  • 集成测试(函数、服务和 API Gateway 的集成)代码占 45%左右,占用了开发人员60% 的时间(以小时作为单位)开发和测试。
  • 单元测试的测试代码占 25%左右,占用了10%左右的时间开发和测试。

一开始我们以为我们走入了“蛋筒冰激凌反模式”或者“纸杯蛋糕反模式”但实际上:

  1. 我们并没有太多的手动测试,绝大部分自动化。除了验证手机端的部署以外,几乎没有手工测试工作量。
  2. 我们的自动化测试都是必要的,且没有重复。
  3. 我们的单元测试足够,且不需要增加单元测试。

但为什么会造成这样的结果呢,经过我们分析。是由于 AWS 供了很多功能组件,而这些组件你无需在单元测试中验证(减少了很多 Stub 或者 Mock),只有通过集成测试的方式才能进行验证。因此,Serverless 基础设施大大降低了单元测试的投入,但把这些不同的组件组合起来则劳时费力 。如果你有多套不一致的环境,那你的持续交付流水线配置则是很困难的。因此我们意识到:

你不再需要多个运行环境,你只需要一个多阶段的生产环境 (Multi-Stage Production)

通常情况下,我们会有多个运行环境,分别面对不同的人群:

  1. 面向开发者的本地开发环境
  2. 面向测试者的集成环境或测试环境(Test,QA 或 SIT)
  3. 面向业务部门的测试环境(UAT 环境)
  4. 面向最终用户的生产环境(Production 环境)

然而多个环境带来的最大问题是环境基础配置的不一致性。加之应用部署的不一致性。带来了很多不可重现问题。在 DevOps 运动,特别是基础设施即代码实践的推广下,这一问题得到了暂时的缓解。然而无服务器架构则把基础设施即代码推向了极致:只要能做到配置隔离和部署权限隔离,资源也可以做到同样的隔离效果。

我们通过 DNS 配置指向了同一个的 API Gateway,这个 API Gateway 有着不同的 Stage:我们只有开发(Dev)和生产(Prod)两套配置,只需修改配置以及对应 API 所指向的函数版本就可完成部署和发布。

然而,多个函数的多版本管理增加了操作复杂性和配置性,使得整个持续交付流水线多了很多认为操作导致持续交付并不高效。于是我们在思考:

对函数的管理和“ Nanoservices 反模式 ”

根据微服务的定义,AWS API Gateway 和 Lambda 的组合确实满足 微服务的特征,这看起来很美好。就像下图一样:

但当Lambda 函数多了,管理众多的函数的发布就成为了很高的一件事。而且, 可能会变成“Nanoservice 反模式”:

Nanoservice is an antipattern where a service is too fine-grained. A nanoservice is a service whose overhead (communications, maintenance, and so on) outweighs its utility.

如何把握微服务的粒度和函数的数量,就变成了一个新的问题。而 Serverless Framework ,就是解决这样的问题的。它认为微服务是由一个多个函数和相关的资源所组成。因此,才满足了微服务可独立部署可独立服务的属性。它把微服务当做一个用于管理 Lambda 的单元。所有的 Lambda 要按照微服务的要求来组织。Serverless Framework 包含了三个部分:

  1. 一个 CLI 工具,用于创建和部署微服务。
  2. 一个配置文件,用于管理和配置 AWS 微服务所需要的所有资源。
  3. 一套函数模板,用于让你快速启动微服务的开发。

此外,这个工具由 AWS 自身推广,所以兼容性很好。但是,我们得到了 Serverless 的众多好处,却难以摆脱对 AWS 的依赖。因为 AWS 的这一套架构是和别的云平台不兼容的。

所以,这就又是一个“自由的代价”的问题。

CloudNative 的持续交付

在实施 Serverless 的微服务期间,发生了一件我认为十分有意义的事情。我们客户想增加一个很小的需求。我和两个客户方的开发人员,客户的开发经理,以及客户业务部门的两个人要实现一个需求。当时我们 6 个人在会议室里面讨论了两个小时。讨论两个小时之后我们不光和业务部门定下来了需求(这点多么不容易),与此同时我们的前后端代码已经写完了,而且发布到了生产环境并通过了业务部门的测试。由于客户内部流程的关系,我们仅需要一个生产环境发布的批准,就可以完成新需求的对外发布!

在这个过程中,由于我们没有太多的环境要准备,并且和业务部门共同制定了验收标准并完成了自动化测试的编写。这全得益于 Serverless 相关技术带来的便利性。

我相信在未来的环境,如果这个架构,如果在线 IDE 技术成熟的话(由于 Lambda 控制了代码的规模,因此在线 IDE 足够),那我们可以大量缩短我们需求确定之后到我功能上线的整体时间。

通过以上的经历,我发现了 CloudNative 持续交付的几个重点:

  1. 优先采用 SaaS 化的服务而不是自己搭建持续交付流水线。
  2. 开发是离不开基础设施配置工作的。
  3. 状态和过程分离,把状态通过版本化的方式保存到配置管理工具中。

而在这种环境下,Ops工作就只剩下三件事:

  1. 设计整体的架构,除了基础设施的架构以外,还要关注应用架构。以及优先采用的 SaaS 服务解决问题。
  2. 严格管理配置和权限并构建一个快速交付的持续交付流程。
  3. 监控生产环境。

剩下的事情,就全部交给云平台去做。


更多精彩洞见,请关注微信公众号:思特沃克

Share

Serverless的微服务架构案例

本文首发于GitChat《Serverless 风格微服务的持续交付(上):架构案例》,部分内容已做修改。文章聊天实录请见:“顾宇:构建Serverless 风格微服务实战解析(上)

一次微服务架构的奇遇

2016年12月初,当时我正在以一名 DevOps 咨询师的身份参与某客户的 DevOps 转型项目。这个项目是提升该部门在 AWS (Amazon Web Services)云计算平台上的 DevOps 能力。

自助服务的应用系统基于 Ruby on Rails 框架开发,前端部分采用 AngularJS 1.0,但是没有采用前后端分离的设计,页面代码仍然是通过 ERB 组合而成。移动端则采用 Cordova 开发。为了降低开发难度和工作量, 移动端的应用内容实际上是把 AngularJS 所生成的 Web 页面通过响应式样式的方式嵌入到移动端。但因为经常超时,所以这款 APP 体验并不好。

整套 Rails 应用部署在 AWS 上,并且通过网关和内部业务系统隔离。BOSS 系统采用 SOAP 对外暴露服务,并由另外一个部门负责。因此,云上的应用所做的业务是给用户展现一个使用友好的界面,并通过数据的转化和内部 BOSS 系统进行交互。系统架构如下图所示:

应用的交互流程如下

  1. 浏览器或者移动端通过域名(由 AWS Route 53托管)转向 CDN(采用 AWS Cloudfront)。
  2. CDN 根据请求的内容类别进行区分,静态文件(图片,JS,CSS 样式等),会转向 AWS S3 存储。动态请求会直接发给负载均衡器 (AWS Elastic Load Balancer)。
  3. 负载均衡器会根据各 EC2 计算实例的负载状态将请求转发到不同的实例上的 Ruby On Rails 应用上。每一个应用都是一个典型的 MVC Web 应用。
  4. EC2 上的应用会将一部分数据存储在关系型数据服务(AWS RDS,Relational Database Service)上,一部分存储在本地文件里。经过应用的处理,转换成 SOAP 请求通过 网关发送给 BOSS 系统处理。BOSS 系统处理完成后会返回对应的消息。

根据业务的需要,一部分数据会采用 AWS ElastiCache 的 Redis 服务作为缓存以优化业务响应速度。

团队痛点

这个应用经历了多年的开发,前后已经更换过很多技术人员。但是没有人对这个应用代码库有完整的的认识。因此,我们对整个团队和产品进行了一次痛点总结:

组织结构方面

运维团队成为瓶颈,60 个人左右的开发团队只有 4 名 Ops 支持。运维团队除了日常的事务以外,还要给开发团队提供各种支持。很多资源的使用权限被限制在这个团队里,就导致各种问题的解决进度进一步拖延。

随着业务的增长,需要基础设施代码库提供各种各样的能力。然而 Ops 团队的任何更改都会导致所有的开发团队停下手头的进度去修复更新所带来的各种问题。

应用架构方面

应用架构并没有达到前后端分离的效果,仍然需要同一个工程师编写前后端代码。这样的技术栈对于对于开发人员的要求很高,然而市场上缺乏合适的 RoR 工程师,导致维护成本进一步上升。经过了三个月,仍然很难招聘到合适的工程师。

多个团队在一个代码库上工作,新旧功能之间存在各种依赖点。加上 Ruby 的语言特性,使得代码中存在很多隐含的依赖点和类/方法覆盖,导致了开发进度缓慢。我们一共有 4 个团队在一个代码库上工作,3个团队在开发新的功能。1 个团队需要修复 Bug 和清理技术债,这一切都要同时进行。

技术债方面

代码库中有大量的重复 Cucumber 自动化测试,但是缺乏正确的并行测试策略,导致自动化测试会随机失败,持续集成服务器 (Jenkins)的 slave 节点本地难以创建,导致失败原因更加难以查找。如果走运的话,从提交代码到新的版本发布至少需要 45 分钟。如果不走运的话,两三天都无法完成一次成功的构建,真是依靠人品构建。

基础设施即代码(Infrastructure as Code)建立在一个混合的遗留的 Ruby 代码库上。这个代码库用来封装一些类似于 Packer 和 AWS CLI 这样的命令行工具,包含一些 CloudFormation 的转化能力。由于缺乏长期的规划和编码规范,加之人员变动十分频繁,使得代码库难以维护。

此外,基础设施代码库作为一个 gem 和应用程序代码库耦合在一起,运维团队有唯一的维护权限。因此很多基础设施上的问题开发团队无法解决,也不愿解决。

我参与过很多 Ruby 技术栈遗留系统的维护。在经历了这些 Ruby 项目之后,我发现 Ruby 是一个开发起来很爽但是维护起来很痛苦的技术栈。大部分的维护更改是由于 Ruby 的版本 和 Gem 的版本更新导致的。此外,由于 Ruby 比较灵活,人们都有自己的想法和使用习惯,因此代码库很难维护。

虽然团队已经有比较好的持续交付流程,但是 Ops 能力缺乏和应用架构带来的局限阻碍了整个产品的前进。因此,当务之急是能够通过 DevOps 提升团队的 Ops 能力,缓解 Ops 资源不足,削弱 DevOps 矛盾。

DevOps 组织转型中一般有两种方法:一种方法是提升 Dev 的 Ops 能力,另一种方法是降低 Ops 工作门槛。在时间资源很紧张的情况下,通过技术的改进,降低 Ops 的门槛是短期内收益最大的方法。

微服务触发点:并购带来的业务功能合并

在我加入项目之后,客户收购了另外一家业务相关的企业。因此原有的系统要同时承载两个业务。恰巧有个订单查询的业务需要让当前的团队完成这样一个需求:通过现有的订单查询功能同时查询两个系统的业务订单。

这个需求看起来很简单,只需要在现有系统中增加一个数据源,然后把输入的订单号进行转化就可以。但由于存在上述的痛点,完成这样一个简单的功能的代价是十分高昂的。几乎 70% 的工作量都和功能开发本身没有关系。

在开发的项目上进行 DevOps 转型就像在行进的汽车上换车轮,一不留心就会让所有团队停止工作。因此我建议通过设立并行的新团队来同时完成新功能的开发和 DevOps 转型的试点。

这是一个功能拆分和新功能拆分需求,刚好订单查询是原系统中一个比较独立和成熟的功能。为了避免影响原有各功能开发的进度。我们决定采用微服务架构来完成这个功能。

构建微服务的架构的策略

我们并不想重蹈之前应用架构的覆辙,我们要做到前后端分离。使得比较小的开发团队可以并行开发,只要协商好了接口之间的契约(Contract),未来开发完成之后会很好集成。

这让我想起了 Chris Richardson 提出了三种微服务架构策略,分别是:停止挖坑,前后端分离和提取微服务。

停止挖坑的意思是说:如果发现自己掉坑里,马上停止。

原先的单体应用对我们来说就是一个焦油坑,因此我们要停止在原来的代码库上继续工作。并且为新应用单独创建一个代码库。所以,我们拆分策略模式如下所示:

在我们的架构里,实现新的需求就要变动老的应用。我们的想法是:

  1. 构建出新的业务页面,生成微服务契约。
  2. 根据 API 契约构建出新的微服务。
  3. 部署 Web 前端到 S3 上,采用 S3 的 Static Web Hosting (静态 Web 服务) 发布。
  4. 部署后端微服务上线,并采用临时的域名和 CDN 加载点进行测试。
  5. 通过更新 CDN 把原应用的流量导向新的微服务。
  6. 删除旧的服务代码。

我们原本要在原有的应用上增加一个 API 用来访问以前应用的逻辑。但想想这实际上也是一种挖坑。在评估了业务的复杂性之后。我们发现这个功能如果全新开发只需要 2人2周(一个人月)的时间,这仅仅占我们预估工作量的20%不到。因此我们放弃了对遗留代码动工的念头。最终通过微服务直接访问后台系统,而不需要通过原有的应用。

在我们拆微服务的部分十分简单。对于后端来说说只需要修改 CDN 覆盖原先的访问源(Origin)以及保存在 route.rb 里的原功能访问点,就可以完成微服务的集成。

构建出新的业务页面,生成微服务契约

结合上面的应用痛点和思路,在构建微服务的技术选型时我们确定了以下方向:

  1. 前端框架要具备很好的 Responsive 扩展。
  2. 采用 Swagger 来描述 API 需要具备的行为。
  3. 过消费者驱动进行契约测试驱动微服务后端开发。
  4. 前端代码库和后端代码库分开。
  5. 前端代码框架要对持续交付友好。

因此我们选择了 React 作为前端技术栈并且用 yarn 管理依赖和任务。另外一个原因是我们能够通过 React-native 为未来构建新的应用做好准备。此外,我们引入了 AWS SDK 的 nodejs 版本。用编写一些常见的诸如构建、部署、配置等 AWS 相关的操作。并且通过 swagger 描述后端 API 的行为。这样,后端只需要满足这个 API 规范,就很容易做前后端集成。

部署前端部分到 S3 上

由于 AWS S3 服务自带 Static Web Hosting (静态页面服务) 功能,这就大大减少了我们构建基础环境所花费的时间。如果你还想着用 Nginx 和 Apache 作为静态内容的 Web 服务器,那么你还不够 CloudNative。

虽然AWS S3 服务曾经发生过故障,但 SLA 也比我们自己构建的 EC2 实例处理静态内容要强得多。此外还有以下优点:

  1. 拥有独立的 URL,很容易做很多 301 和 302 的重定向和改写操作。
  2. 和 CDN(CloudFront)集成很好。
  3. 很容易和持续集成工具集成。
  4. 最大的优点:比 EC2 便宜。

根据 API 契约构建出新的微服务

在构建微服务的最初,我们当时有两个选择:

采用 Sinatra (一个用来构建 API 的 Ruby gem) 构建一个微服务 ,这样可以复用原先 Rails 代码库的很多组件。换句话说,只需要 copy 一些代码,放到一个单独的代码库里,就可以完成功能。但也同样会面临之前 Ruby 技术栈带来的种种问题。

采用 Spring Boot 构建一个微服务,Java 作为成熟工程语言目前还是最好的选择,社区和实践都非常成熟。可以复用后台很多用来做 SOAP 处理的 JAR 包。另一方面是解决了 Ruby 技术栈带来的问题。

然而,这两个方案的都有一个共同的问题:需要通过 ruby 语言编写的基础设施工具构建一套运行微服务的基础设施。而这个基础设施的搭建,前前后后估计得需要至少 1个月,这还是在运维团队有人帮助的情况下的乐观估计。

所以,要找到一种降低环境构建和运维团队阻塞的方式避开传统的 EC2 搭建应用的方式。

这,只有 Lambda 可以做到!

基于上面的种种考量,我们选择了 Amazon API Gateway + Lambda 的组合。而 Amazon API Gateway + Lambda 还有额外好处:

  1. 支持用 Swagger 规范配置 API Gateway。也就是说,你只要导入前端的 Swagger 规范,就可以生成 API Gateway。
  2. 可以用数据构建 Mock API,这样就可以很大程度上实现消费者驱动契约开发。
  3. 通过 Amazon API Gateway 的 Stage 功能,我们无需构建 QA 环境,UAT 环境和 Staging 环境。只需要指定不同的 Stage,就可以完成对应的切换。
  4. Lambda 的发布生效时间很短,反馈很快。原先用 CloudFormation 构建的 API 基础设施需要至少 15 分钟,而 Lambda 的生效只需要短短几秒钟。
  5. Lambda 的编写很方便,可以采用在线的方式。虽然在线 IDE 并不很好用,但是真的也写不了几行代码。
  6. Lambda 自动根据请求自扩展,无需考虑负载均衡。

虽然有这么多优点,但不能忽略了关键性的问题:AWS Lambda 不一定适合你的应用场景!

很多对同步和强一致性的业务需求是无法满足的。所以,AWS Lambda 更适合能够异步处理的业务场景。此外,AWS Lambda 对消耗存储空间和 CPU 很多的场景支持不是很好,例如 AI 和 大数据。(PS: AWS 已经有专门的 AI 和大数据服务了,所以不需要和自己过不去)

对于我们的应用场景而言,上文中的 Ruby On Rails 应用中的主要功能(至少60% 以上)实际上只是一个数据转换适配器:把前端输入的数据进行加工,转换成对应的 SOAP 调用。因此,对于这样一个简单的场景而言,Amazon API Gateway + Lambda 完全满足需求!

部署后端微服务

选择了Amazon API Gateway + Lambda 后,后端的微服务部署看起来很简单:

  • 更新 Lambda 函数。
  • 更新 API 规范,并要求 API 绑定对应 Lambda 函数处理请求。

但是,这却不是很容易的一件事。我们将在下一篇文章《Serverless 风格微服务的持续交付》中对这方面踩过的坑详细介绍。

把原应用的请求导向新的微服务

这时候在 CDN 上给新的微服务配置 API Gateway 作为一个新的源(Origin),覆盖原先写在 route.rb 和 nginx.conf 里的 API 访问规则就可以了。CDN 会拦截访问请求,使得请求在 nginx 处理之前就会把对应的请求转发到 API Gateway。

当然,如果你想做灰度发布的话,就不能按上面这种方式搞了。CloudFront 和 ELB 负载均衡 并不具备带权转发功能。因此你需要通过 nginx 配置,按访问权重把 API Gateway 作为一个 upstream 里的一个 Server 就可以。

删除旧的服务代码

不要留着无用的遗留代码!

不要留着无用的遗留代码!

不要留着无用的遗留代码!

重要且最容易被忽略的事情要说三遍。斩草要除根,虽然我们可以保持代码不动。但是清理不再使用的遗留代码和自动化测试可以为其它团队减少很多不必要的工作量。

最终的架构

经过6个人两个月的开发(原计划8个人3个月),我们的 Serverless 微服务最终落地了。当然这中间有 60% 的时间是在探索全新的技术栈。如果熟练的话,估计 4 个人一个月就可以完成工作。

最后的架构如下图所示:

在上图中,请求仍然是先通过域名到 CDN (CloudFront),然后:

  1. CDN 根据请求点的不同,把页面请求转发至 S3 ,把 API 请求转发到 API Gateway。
  2. 前端的内容通过蓝绿部署被放到了不同的 S3 Bucket 里面,只需要改变 CDN 设置就可以完成对应内容的部署。虽然对于部署来说蓝绿 Bucket 乍看有一点多余,但这是为了能够在生产环境下做集成在线测试准备的。这样可以使环境不一致尽可能少。
  3. API Gateway 有自己作用的 VPC,很好的实现了网络级别的隔离。
  4. 通过 API Gateway 转发的 API 请求分成了三类,每一类都可以根据请求状况自扩展。
  5. 身份验证类:第一次访问会请求 ElastCache(Redis),如果 Token 失效或者不存在,则重新走一遍用户验证流程。
  6. 数据请求类:数据请求类会通过 Lambda 访问由其他团队开发的 Java 微服务,这类微服务是后台系统唯一的访问点。
  7. 操作审计类:请求会记录到 DynamoDB (一种时间序列数据库)中,用来跟踪异步请求的各种日志。
  8. API Gateway 自己有一些缓存,可以加速 API 的访问。
  9. 消息返回后,再有三类不同的请求的结果统一通过 API Gateway 返回给客户端。

Serverless 风格微服务架构的优点

由于没有 EC2 设施初始化的时间,我们减少了至少一个月的工作量,分别是:

  1. 初始化网络配置的时间。
  2. 构建 EC2 配置的时间。
  3. 构建反向代理和前端静态内容服务器的时间。
  4. 构建后端 API 应用基础设施的时间。
  5. 构建负载均衡的时间。
  6. 把上述内容用 Ruby 进行基础设施即代码化的时间。

如果要把 API Gateway 算作是基础设施初始化的时间来看。第一次初始化 API Gateway 用了一天,以后 API Gateway 结合持续交付流程每次修改仅仅需要几分钟,Serverless 风格的微服务大大降低了基础设施配置和运维门槛。

此外,对于团队来说,Amazon API Gateway + Lambda 的微服务还带来其它好处:

  1. 开发效率高,原先至少 45 分钟的开发反馈周期缩短为 5 分钟以内。
  2. 无关的代码量少,需要维护的代码量少。除了专注业务本身。上游和 API Gateway 的集成以及下游和后端服务的集成代码量很少。
  3. 应用维护成本低。代码仅仅几十行,且都为函数式,很容易测试。避免了代码库内部复杂性的增加。

此外,我们做了 Java 和 NodeJs 比较。在开发同样的功能下,NodeJS 的开发效率更高,原因是 Java 要把请求的 json 转化为对象,也要把返回的 json 转化为对象,而不像 nodejs 直接处理 json。此外, Java 需要引入一些其它 JAR 包作为依赖。在 AWS 场景下开发同样一个函数式微服务,nodejs 有 4 倍于 java 的开发效率提升。

最后

Serverless 风格的微服务虽然大大减少了开发工作量以及基础设施的开发维护工作量。但也带来了新的挑战:

  1. 大量函数的管理。
  2. SIT,UAT 环境的管理。
  3. 持续交付流水线的配置。
  4. 面对基础设施集成带来的测试。

这让我们重新思考了 Serverless 架构的微服务如何更好的进行持续交付。


更多精彩洞见,请关注微信公众号:思特沃克

Share

提升微服务实施效率的7个步骤

本文首发于GitChat,经作者同意授权转发。转载请联系作者或GitChat。

《微服务进展缓慢的5个难点》中描述了实施微服务常见的主要阻碍。本文针对前文提到的5个难点提出了7个步骤。每个步骤分别包含了管理和技术两方面的建议。

如果以上5点都让你膝盖中箭。那么根据我个人的经验,综合解决微服务实施难点的第一步就是:

步骤1:以终为始,先构建一个独立的敏捷微服务团队

我们对微服务的期待就是:可以独立开发、独立部署、独立发布,并且进行去中心化的管理。那么,我们就先构造一支这样的团队。

这个团队为了达到上述目标,会采取各种方法(例如:DevOps、全功能团队)解决阻碍”独立开发、独立部署、独立发布和去中心化的问题。而根据康威定理,系统的架构会慢慢向“去中心化”方向发展。

一定要意识到,这个过程会打破大型系统自上而下的既有流程并采用更有生产力的方式构建新的组织结构。你唯一需要做的就是要充分信任团队,把它看做是一个微型的技术管理创新。不要用老的方式控制团队的运作,这会打击团队的士气。

管理建议:

  • 让微服务团队完全脱离之前的工作,专心于微服务的工作中。如果分心同时做几件事,每件事都不会做到最好。
  • 给微服务团队一些特权,为了满足“全功能微服务团队的”诉求,特事特办。
  • 如果团队在执行的过程出现了依赖,进而阻碍了进度。则需要把依赖标明出来。代码中的依赖容易看见,但组织中的流程依赖很难发现。
  • 为了避免团队对外部的“依赖惯性”,让团队自己想办法在内部解决依赖。
  • 组织流程的改变也是很重要的微服务架构产物,而不仅仅是微服务代码或基础设施。

技术建议:

  • 为微服务建立一个全新的代码库,而不要从原先的代码库上克隆或者复制,避免和原团队的开发依赖。
  • 建设一个独立的持续交付流水线,最好是通过“流水线即代码技术”(例如Jenkinsfile)来自动生成流水线。

步骤2:构建微服务的“电梯演讲”

成立了微服务团队之后,接下来就是要选择第一个实现的微服务。但是“这个微服务应该多大,边界在哪”是个问题。这不需要进行严格的设计和反复的论证,只要发现当前的痛点或者想要完成一个假设就足够上路了。让整个过程变轻,而不是变重。

我的建议是通过“微服务电梯演讲”的方式来定义微服务。格式可以是:

  • (XX微服务)用来
  • 在(出现痛点的场景)的情况下
  • 解决(解决现有的某个问题)
  • 从而(达到什么样的效果)
  • 提升(微服务的价值)

例如:

  • (订单查询微服务)用来
  • 在(订单查询数量快速)的情况下
  • 解决(访问数量迅速升高导致整体应用性能下降的问题)
  • 从而(分离了订单查询请求)
  • 提升(其他功能的性能)

当构造了微服务的电梯演讲,团队就可以以此为原则来启动了。当碰到和现有系统冲突的问题,替换几个词比较能帮助你做决策。(语言在一定程度上也是具有魔力的)

把“拆分”换成“移除”。例如:“从现有系统中拆分出订单查询功能”转变为”从现有系统中移除订单查询功能”。思维方式就从一个团队负责两个系统变成了两个团队负责两个系统。

把“集成”换成“调用”。例如:“用户注册和用户登录需要集成”转变为“用户登录服务需要调用用户注册服务的信息”。思维方式的转换就把两个系统的关系更精确了,从而明确了微服务之间的关系和沟通方式。

管理建议:

  • 把微服务的电梯演讲打印出来挂到墙上,让团队成员铭记于心。这会强化组织对微服务的边界认识。
  • 随着团队的反思和学习,电梯演讲有可能会变更,但一定要让团队形成共识和一致的意见。
  • 不要期望一次就能划分正确。划分是一个持续权衡取舍的过程。

技术建议:

  • 明确了微服务的职责和边界之后再去看代码,否则会被代码的复杂度影响。
  • 领域驱动设计(DDD)可以帮助你更好的划分微服务。领域驱动设计很好的遵循了“关注点分离”(Separation of concerns,SOC)的原则,提出了更成熟、清晰的分层架构。
  • 不会领域驱动设计(DDD)也没有关系。简单的使用“关注点分离原则”也可以帮你达到这一点。例如:从接口中分离出流量较大的接口独立部署,把读数据库和写数据库的API分开独立部署,把静态和动态访问分离……

步骤3:以最小的代价发布出第一个微服务

要注意两个关键点:一个是“最小的代价”,另一个是“发布”(Release)。

正如前文所述,微服务架构本身就决定了微服务一定是低成本低风险的渐进式演进。而最大的浪费在于:

  1. 级别/职责分工明确的组织沟通结构。
  2. “长时间,慢反馈”的行动习惯。
  3. 先进且学习成本较高的技术栈。

因此,“最小的代价”包含了以下三个方面:

  1. 最精简的独立敏捷全功能团队。
  2. 最快的时间。
  3. 代价最小的技术栈。

此外,很多微服务的“爱好者”由于害怕失败,因此将微服务技术始终放在“实验室”里。要勇于面对失败,在生产环境中面对真实的问题,但要采取一些规避风险的措施。

管理建议:

  • 尽量让现有微服务团队自己学习解决问题,成为全功能团队。如无必要,绝不增添新的人手。
  • “扯破嗓子不如甩开膀子”,先动起来,在前进中解决问题。
  • 先考虑最后如何发布,根据发布流程倒推。

技术建议:

  • 根据当前技术采用的情况选择代价较小的技术栈。
  • 采用动态特性开关(Feature Toggle),在发布后可以在生产环境动态的控制微服务的启用,降低失败风险。
  • 如果采用了特性开关,一定要设立删除特性开关和对应旧代码的时间,一般不超过两个月。否则后面大量的特性开关会带来管理成本的提升和代码的凌乱。
  • 由于团队比较小,功能比较单一,不建议采用分支来构建微服务,而应该采用单主干方式开发。

步骤4:取得快速胜利(Quick wins),演示(Showcase)驱动开发

刚开始进行微服务改造的时候一定会是一个试错的过程。如果目标定得太大,会让团队倍感压力,从而士气低落。而制定每日的短期目标,赢得快速胜利则会不断激励团队的士气。通过“设定当天结束的产出来确定今天需要做什么”是一个非常有效的办法。

每日演示(Daily Showcase)就是一种推进产出的做法。每天向团队分享今天的工作内容,使小组能够共同学习。并且以当天或者明天的showcase作为目标。每个人showcase的内容一般不超过20分钟,一天的showcase时间不超过一小时。可以早上showcase,也可以下班后showcase。

常见的快速胜利目标如下:

  • 构造第一条微服务流水线。
  • 上线第一个微服务HelloWorld
  • 构造出第一个微服务自动化测试。

而以下的目标不适合作为快速胜利的目标:

  • 构造出微服务DevOps平台。
  • 完成整个产品的微服务架构拆分。
  • 构造微服务自动化运维体系。

管理建议:

  • 要防止团队画大饼,完成好每日和每周的工作目标即可。微服务开发本身就没有很长周期。
  • 强迫团队有所产出,这样才能用关键产出驱动开发。产出不一定是代码或者基础设施,一篇总结,或者学习的文章分享,甚至是踩过的坑和遇到的问题都可以展示,目的是要打造自治学习的团队。
  • 贵在坚持,不要计划太远。超过一个月,就要对目标是不是范围过大进行反思。
  • 以天为单位拆分任务,超过一天的必须要拆分。无法在一天完成的工作需要拆分出阶段性产出。
  • 如果能结对,并且能够每天交换结对,showcase不必要。
  • 可视化所有任务,用敏捷看板来管理任务是了解现状的最好方式。

技术建议:

  • 想办法让第一个微服务尽快发布到生产环境,生产是最重要的。
  • 完成了HelloWord的发布,就要考虑如何对发布流程进行改进。而不是着急准备下一个业务上线。

步骤5:代码未动,DevOps先行

微服务解耦的本质是把代码内部的复杂性通过一些工具转化外部复杂性。把代码内部的复杂性分散到各个微服务中以降低整体复杂性和架构风险。在这个过程中会大量采用DevOps技术和工具。也可以说,微服务是DevOps文化和技术在走到极致的必然结果。

以J2EE的应用为例,以前Web Server + App Server + MiddleWare + Database的传统架构被更多的基础设施工具所取代,所需要的编程工作量更小。因为基础设施相对于应用代码来说更加稳定,更加利于扩展。

我把微服务的技术架构问题比作“搭台唱戏”:首先需要建立好微服务交付和运行的平台,然后让微服务上台“唱戏”。

这个平台一开始不需要很完善,只需要满足生产上线的必要要求即可。而在很多企业里,这个部分是由Ops团队在交付流程的末尾把关的。因此,把最后一道关卡的确认工作放到最前面考虑可以减少后期的返工以及不必要的浪费。

以前,软件的开发和测试过程是分开的。然而,随着DevOps运动和各种自动化运维工具的兴起,这之间的必要性有所降低,只要有足够的自动化测试做质量保证,就可以很快的将微服务快速部署和发布到生产环境上。

最开始的时候,哪怕是发布一个Hello World程序,都表明微服务的持续交付和运行的平台已经搭建好,微服务交付流程已经打通,这一点是重中之重。

从技术交付产物来说,DevOps主要交付两点:

  • 持续交付流水线。
  • 微服务运行平台。

为了保证微服务交付的高效,需要把这二者通过自动化的方式有机的结合起来,而不是各为其主。让开发和运维的矛盾变成“自动化的开发运维矛盾”

此外,DevOps指的不光是一系列技术,更是一种工作方式。从团队工作方式来说,DevOps要做到:

  • 要让Dev和Ops共同参与决策、设计、实现和维护。
  • 团队完全独立自主,打破对现有流程的依赖。
  • 不断的追求改进,让团队形成改进的团队文化。

管理建议:

  • “新程序快速投入生产”能给团队继续前进最大的动力。
  • 如果你的组织是Dev和Ops分离的组织,先咨询一下Ops工程师的意见。最好是能够给微服务团队里面配备一名Ops工程师。
  • 如果不具备实施DevOps的条件,微服务架构就要从运维侧,而不是开发侧开始进行。

技术建议:

  • 微服务的平台一开始可以很简单,以后慢慢增强和扩展。但是一定要部署到生产环境里使用。
  • 如果想使用现成的微服务平台,可以参考Spring Cloud。
  • 微服务运行平台可以通过灰度发布技术在生产环境并行运行。
  • 采用灰度发布技术在生产环境中逐步提升微服务的使用占比。
  • 基础设施即代码是DevOps核心实践,可以帮助开发人员迅速在本机构建生产环境相似的开发环境,减少环境的不一致性。可以采用Docker,Ansible,Vagrant等工具来完成。
  • 基础设施对微服务应该是透明的。微服务不应该也没必要知道运行环境的细节。只要能够正常启动并执行业务就完成了它的任务。因此,基础设施代码要和微服务业务代码分开,且微服务不应该告诉平台自己如何部署。
  • 服务注册和发现是微服务架构的核心部分。consul和Eureka是这方面的佼佼者。
  • 部署(Deploy)和发布(Release)要分开。

步骤6:除了提交代码和发布,微服务平台一切都应当自动化

在完成了微服务的基础设施和交付流程之后,就可以开始实现微服务的业务了。这时候需要依据电梯演讲划分出来的微服务进行业务逻辑的开发。在以DevOps的方式工作一段时间之后,团队应该养成了一些自动化的习惯,如果没有,就应该检查一下自己的自动化程度。最佳的自动化理想的状态就是除了代码提交和发布,在这之间的每一个流程和环节都应当由自动化的手段来完成。

当然,也有不能自动化的部分。根据我的经验,不能自动化的原因主要来自于流程管理的制度要求,而非技术困难。这往往是组织没有依据微服务进行流程变革导致的。这时候需要检讨不能自动化的部分是不是有存在的必要。

另一方面,虽然自动化可以大量缩短微服务交付时间,提升微服务交付效率。但是自动化的同时需要考虑到安全因素和风险,不能顾此失彼。对于生产来说,可用性和安全性是最重要的部分。 关键的自动化:

  • 自动化功能性测试(UI/集成/单元/回归)
  • 自动化构建
  • 自动化部署
  • 自动化性能测试
  • 自动化安全扫描

管理建议:

  • 团队成员自发的进行自动化的改进,这会给未来微服务批量开发带来很多益处。
  • 不要一开始就追求全面的自动化,自动化需要花费一定时间。根据团队的进度视情况适度进行。

技术建议:

  • 采用TDD的方式开发不光可以提升质量,也完成了测试的自动化。
  • 契约测试可以解耦微服务提供者和消费者的开发。但是要注意始终保持契约的有效性,一定要先改契约后开发。
  • 注意自动化的安全隐患。机密信息需要独立管理,例如可以采用 Hashicorp Vault 这样的服务。
  • 关键步骤需要准备自动、手动两种方式,必要时可以干预自动过程。
  • 采用git的hook技术,在代码push之前就可以完成测试和静态检查,提升CI的成功率。

步骤7:总结并复制成功经验,建立起微服务交付的节奏

当完成了第一个微服务,不要着急开始进行下一个微服务的开发。而是需要进行一次关于可复制经验的总结,识别微服务开发中的经验教训并总结成可复制的经验和产出。

以下是一些需要总结出来的关键产出:

  • 微服务开发到发布的端到端流程规范。
  • 微服务开发的技术质量规范。
  • 团队合作中坚持的最佳实践。
  • 常见技术问题总结。

有了以上的关键产出,就可以对微服务开发团队进行扩张。这时候有了微服务开发的老司机,带着刚加入的同事一起开发,风险会相对低很多。

管理建议:

  • 刚开始的时候可以每周举行一个回顾会议,团队需要快速的反馈和调整。
  • 不要急于扩张团队,要在成功经验稳定并形成模式之后再快速扩充。
  • 避免微服务良好的开发氛围被稀释,刚开始的时候扩充团队可以慢一点。新老成员的配比不要超过1:1。
  • 虽然微服务平台趋于稳定,但在微服务没有上规模之前,不要让团队里缺少Ops成员。
  • 注意知识的传递和人员的培养。

技术建议:

  • 不要急于在微服务应用规模不大的时候形成微服务模板,这会限制未来微服务的开发和扩展。
  • 在微服务不成规模的时候不要放松对微服务平台和交付流程的改进。要做到最快的时间交付和发布微服务。

参考书目

  • 《微服务设计》是一本有关微服务各个方面技术的综合参考材料。如果你在实施微服务的过程中碰到了问题,它就是一个解决方案的分类汇总。
  • 《持续交付》汇集了很多交付最佳实践,当你的微服务实施碰到阻碍时,里面的建议或许能够让你解决当前的困境。
  • 《领域驱动设计》《实现领域驱动设计》为拆分微服务提供了方法论,当团队之间对于微服务的拆分有困难的时候,采用领域驱动的方法往往会得到更好的效果。·
  • 《微服务那些事儿》是一本快速启动微服务的工具和实践的总结,能够帮助微服务入门者快速跨越门槛。

更多精彩文章,请关注微信公众号:软件乌托邦

Share

微服务进展缓慢的5个难点

本文首发于GitChat,原作者顾宇,经作者同意授权转发。转载请联系作者或GitChat。

前言

笔者从2013年加入ThoughtWorks至今共4年时间。在这4年时间里,我分别以开发人员、DevOps工程师、DevOps咨询师、微服务架构师以及微服务咨询师的角色参与了共计7个产品和项目的微服务咨询和实施。其中有成功,有失败,有反思,更多的是学习和总结。以下是我这些年来在微服务咨询上的经验总结,希望能给陷入微服务实施困境的人带来一些帮助。

难点1:“一步到位”的认知错觉

这些年微服务大红大紫,但是真正能够拿出来做为可实践案例的少之又少。大部分的微服务案例只能看到微服务架构的“演进结果”,但是看不到其“演进过程”。这就像每个人可以看到一个架构的高峰,却看不到攀登高峰的路径。

这就造成了一个假象:微服务的架构是通过能力极高的架构师一步到位设计出来的。

这和很多团队自上而下的架构设计感受很相似。于是架构师们蜂拥而至,各种分析方法论层出不穷,讨论和分享络绎不绝。然而真正落地实施的却很少,使得微服务在网络上慢慢变成了一种“玄学”:微服务的实施一直在“理论研究”的阶段。

这违反了软件架构的最基本规律:架构是通过解决当前的需求和痛点而演进的,无法根据没有出现的问题和痛点进行设计。因此,一步到位的、整体的微服务架构设计完全没有必要。况且一个集中化的设计,很难体现微服务的轻量级优势。

我相信技术一定是向不断降低成本的方向发展的。如果新技术没有降低成本反而提升了成本,要么这个新技术有问题,要么是姿势不对、走错了路。

因此,准备实施微服务一定要有一个长期的思想准备。不过跨过了最初的门槛之后,剩下的工作可以被复制、而且速度会越来越快。

难点2:“架构师精英主义”

很多产品对架构师的依赖很大,即“架构师精英主义”:认为只有这个组织的“技术精英”——架构师才可以完成该产品的架构,而团队其它成员只需要实现架构师的设计就可以。这是大型企业和大型系统的常见问题,来源于长期以来重量级企业级架构的习惯。

而微服务则类似于一种“敏捷边际革命”:即由一个不超过2~8个人的小团队就可以完成的轻量级架构。而且对于这种规模的团队而言,即使把整个微服务团队从产品团队移除也不会对整体产品的研发进度产生影响。因此,即使失败了也不会带来太多的损失。不过,当第一个微服务改造成功,那么成功经验的复制带来的乘数效应却能带来很大的收益。

从架构改造投资的风险收益比来看,这是非常划算的。

因此,微服务团队完全没必要大张旗鼓,只需要两三个人就可以动工。但是,谁也没有微服务的实践经验啊,万一失败了怎么办?

这就带来了下一个难点。

难点3:缺乏一个信任并鼓励创新的环境

面对未知的领域,失败再所难免。而处在这个不确定性频发的世界,成功和失败已经不再重要:也许今天的失败,明天再看就是成功,反之亦然。

成功只是表明结果符合自己的假设预期,而失败仅仅意味着结果不符合自己的假设预期。但是无论成败,我们都能在行动的过程中有所学习和反思,而这样的经验才是研发活动中最有价值的。

然而,很多组织,尤其“精英主义”的产品团队,责任和压力往往从上至下分解。由于组织庞大,金字塔的结构往往会构建一种以“不信任”为基础的制度。这种制度营造了一种“宁可不作为,也不能犯错”的文化。由于上层需要对失败负责,使得所有创新只能停留在上层,难以落实推进。在这种情况下,组织的长期合作形成了稳定的工作习惯和思维定势,并形成了利益平衡,这会使得整个组织在面对创新的时候“卡壳”。

当微服务以一种政治任务从上而下派发的时候,为了避免失败,团队内部会相互推诿。通过不断的分析讨论和设计来论证这个事情的难度。在我看来,只要想搞,就一定能找到办法,而不是先设想出一堆还没有遇到的问题和责任。在行进中解决问题是比设计和讨论更加有效率的方法。

而组织解决“卡壳”的办法就是引入“背锅侠”:例如新聘请的架构师或外部咨询师,来完成这个事情。出了问题就不用自己来承担责任了。这样虽然是解决问题的一种折中办法,可以让事情毫无风险的执行下去。但这是一种短期效应,无法解决组织本身的创新窘境,长期依赖外部力量来解决最有价值的问题不会让自己提升,反而形成了对外部力量的依赖。对于凝聚组织来说不是一件好事。

只有打破当前的工作习惯和思维定势,充分认识到创新的困难、风险以及价值,才可以占领创新的高点,吸引人才。

难点4:微服务技术栈的“选择困难症”

由于“精英主义”的架构师需要担负很大的责任和压力。他们必须要为微服务架构谨慎的选择技术栈。因此会在不同的技术栈之间不断尝试。对于习惯了在大型研发组织里“精心设计,加班生产 ”的架构师而言。“长设计,慢反馈”节奏似乎是理所应当的。

微服务开源社区的快速发展滋长了“架构师焦虑”:如果采用落后的技术会被同行鄙视,被不懂技术的老板鄙视,甚至被下属鄙视。因此架构师们疲于在各种新型的技术栈之间比较和学习。此外,不熟悉技术往往会增大风险,架构师就需要更多的时间研究。带着“一步到位”的架构幻想对微服务技术栈精挑细选,而不会采用现有低成本的方案快速迭代的解决问题。

微服务的核心在于采用“小规模,快反馈”的机制降低软件系统的复杂性并通过虚拟和自动化技术分散风险,从而及时应对市场变化带来的各种挑战、进行快速销售创新,获得市场的反馈。而不仅仅是利用到了时下新兴的语言,编程框架或工具。

学习和实践是相辅相成的过程,在实践中学习,并把学习到的知识应用到实践中。这不同于准备一场考试:先停下来学习,准备好了再全力以赴。

以上四点会让大型组织在微服务实施中“卡壳”,这往往会导致微服务实施忽略最重要一点,也是我认为也是核心的一点。

难点5:对微服务的技术变革预估过高,而对微服务带来的组织变革预估不足

作为架构师,永远要不要低估康威定理的威力:“设计系统的组织,其产生的设计和架构等价于组织间的沟通结构。”

从制度经济学角度来讲,软件产品本身就是企业内部组织(员工)和外部组织(用户)沟通制度的计算机程序表达。这个制度一定会朝着缩短组织内外部沟通成本的方向发展。

因此,系统架构一定是和组织架构相吻合的,如果不吻合,势必会阻碍组织的渐进。

这就引出了一个推论:如果企业组织的架构不是唯一的,那么微服务的架构方案也不是唯一的。

当架构和组织结构相一致的时候,一切都会很顺畅。反之,就会出现各种问题。

这个关系就像鞋和脚的关系,只有穿上合适的鞋,走起路来才会舒服。过大过小的鞋都无法让你加快前进的步伐。当然,你可以选择买鞋(引入产品),虽然并不是很合脚,但还可以凑合穿,只是在换鞋的时候你不得不停下来试。你也可以花高价为自己定制一套,这个不会让你在短时间内走得更快,只会越来越合脚。

如果所有人穿上了新鞋,都能跑得很快,只有你不能,那么这就不是鞋的问题,而是你脚的问题,这就不是换鞋能解决的了。你得先把脚的问题解决了,然后再看鞋的问题。当然,也可以通过鞋来矫正脚,只不过会花些功夫,但一定会比不停的换鞋更加有效。

很不幸,大多数的组织并没有准备好迎接微服务架构带来的组织变化。仍然把“系统架构问题”和“组织问题”割裂成两个不同领域的问题:微服务是技术问题,组织问题是管理问题。

有竞争力的组织,是能够通过融合优势达到1+1>2效果的组织。而不是把优势割裂开,让1+1<=2的组织。因此,技术问题和管理问题并不是两个问题,而是同一个问题的两个侧面。

因此,如果你的组织结构是去中心化的小团队结构,那么不用担心,你的应用架构会朝组织架构的方向演进。反之,如果你不是一个去中心化的小团队结构,那么微服务的架构会和组织架构格格不入。

如何解决这些问题?

作为微服务的实践者,对微服务不应该是“叶公好龙”,仅仅停留在研讨的层面。而应该采用敏捷和精益的方式迅速开始,在行进中解决碰到的问题。每个组织的组织结构和业务结构都有所不同,微服务实施所面对的挑战也截然不同。在实施的过程中快速学习并改进,没有必要进行周期较长的总体设计。

关于如何解决本文提到的5个问题,请参考下篇《提升微服务实施效率的7个步骤》。


更多精彩内容,请关注微信公众号:软件乌托邦

Share