你的微服务敢独立交付么?

标签: 微服务 微服务 转载 | 发表时间:2018-04-26 21:34 | 作者:
出处:http://blog.didispace.com/

最近经常在项目或是社区里听到大家谈论微服务架构,但谈论的焦点更多集中在微服务拆分,分布式架构,微服务门槛,DevOps配套设施等话题上。

但是在我眼里,真正能称之为微服务架构的少之又少。原因也很简单,我所见到的很多所谓的微服务架构项目,大多都没有做到微服务架构的一个基本要求:服务的独立部署(交付)。

这里的独立部署和自动化部署还不是一个概念,服务的自动化部署相对简单,已有大量的工具可以帮助我们做到。但是这里所谈的独立部署,我认为关键和难点并不在于“部署”,而在于“独立”。

如果失去了服务独立部署(交付)的能力,一个微服务架构的威力将大打折扣,我们的系统虽然在物理上被拆分成了多个小的服务,但是如果从最终交付的角度来看,仍然是以一个整体存在的,就像单体应用一样,存在诸多的问题。

为什么服务的独立交付并不简单?

那为什么不能让每一个服务都独立部署到产品环境呢?问题的答案是:不是不能,而是 不敢

为了表达清楚,让我们来看个例子吧。

像下图一样,我现在就是那个程序员帅哥(本色出演),突然有一天心血来潮,动手开发了一个网上商城。代码Push到Github并通过CI构建持续交付流水线,最终自动化部署到云端产品环境,供用户访问使用。

随着用户和访问量的增加,需求和功能也越来越多,系统也变得越发复杂。

从网上了解到最近有个叫微服务的架构非常火爆,我也赶了回时髦,当然也觉得这种架构确实可以帮助我解决现在的一些问题。

经过对系统的分析,我将商城的后台部分拆分出了3个服务,为了简单我们就称之为ABC三个服务。


我们假设一个比较极端的情况,三个服务相互调用(先不考虑这样是否合理),每个服务通过自己的持续交付流水线独立部署到产品环境。当前产品环境的各个服务的版本是:A:1.0、B:2.0、C:3.0

一切都非常完美是不是?看!我们已经做到了服务的独立部署!So easy~

当然,事情肯定不会那么简单。

问题出现在当我对A服务做了一次新的提交之后,A服务的最新版本升级到了1.1。不幸的是,这个新的版本意外的破坏了A与B之间的契约,错误的调用了B的接口,导致出现了错误。

虽然我的A服务和B服务都有比较完备的UT(单元测试),但因为UT无法发现服务之间的集成是否被破坏,所以只有UT作为质量保障的A服务持续交付流水线也自然没有能力发现AB服务集成被破坏的这个问题。最终导致存在问题的A1.1版本被部署到了产品环境,产品环境出现了严重的Bug。

请问在座的同学,碰到这样的情况,你会如何处理?

“加集成测试啊!”

这位同学说的极是,我这么聪明自然也想到了这一点,不就是要测集成吗?UT干不了就加集成测试不就成了。

为了统一语言,毕竟对于各种测试的叫法太容易引起混淆,参考Martin Fowler在《微服务测试策略》中的定义,我们在本文中将这种测试多服务集成的测试统一称作端到端测试(End-to-End tests,简称E2E测试)。

添加了E2E测试之后,我的交付流水线就变成了下面这个样子。

因为有了E2E测试的存在,问题迎刃而解,当A服务的新版本破坏了与B服务的集成时,E2E测试就会及时诊断出来,并阻止A服务的最新版本向产品环境流动,保证产品环境不被破坏。

这样看似没有什么问题,通过添加E2E测试,解决了服务间集成的验证问题,但在不知不觉中,我们也失去了微服务架构的那个重要的特性:“ 服务的独立交付”。

怎么讲?别急,我们再往下看。

假设A服务的修复过程中,B和C服务也提交了新的代码,我们假设这两个提交是没有问题的,但因为A服务的1.1版本导致E2E测试挂掉的问题还没有被修复,所以B和C的新版本也被E2E测试拦了下来,此时的E2E测试就像是一个亮起红灯的路口,阻塞了所有服务通往产品环境的通道。

所以说,随着集中E2E测试的添加,质量被保障的同时,我们的“微服务架构”也已悄然失去了服务独立交付的能力,杀敌一千自损八百,损失惨重!

这并不是我假想的场景,在我自己经历的几个真实项目中,这个问题都在一直困扰着我们。带来了各种各样的衍生问题,例如E2E测试长时间失败,无人修复,修复难度大,服务交付堵塞,为了保持交付通路畅通还不得不引入同样存在很大副作用的CodeFrezze机制和提交Token机制等。

可以看到,虽然我们能够在代码库,在部署结构上,甚至在组织上进行服务化拆分,但就因为这最后一个交付的十里路口,最后这一个红绿灯,让所有的服务又纠缠在了一起,所有的服务化拆分形同虚设,最终我们得到的也只是一个 看起来像微服务架构的单体应用而已

拆除红绿灯,各行其道,收复失地!

那,如何才能将这个“红绿灯”拆除,让服务可以在有质量保障的前提下还可以做到独立交付呢?这就是本文要解决的问题,让我们继续往下看。

我的解决方法其实也很简单: Inline E2E tests

即并不添加新的集中的Pipeline做E2E测试,而是为每一个服务的Pipeline都添加一个相同的E2E测试的Stage,就相当于将E2E测试Inline到每个服务各自的部署流水线中,如下图所示。

其实Inline E2E测试还不是最关键的,最关键的变化点就是假设A服务有了新的提交,运行到A服务自己Pipeline的E2E测试的时候,此时的 E2E测试并不是像之前一样获取B和C服务的最新代码库版本做集成验证,而获取当前产品环境上的B和C服务的已部署当前版本做集成验证

例如,如图所示A服务的版本从1.0升级到了1.1,当前产品环境的B和C的版本是2.0和3.0。在执行A服务Pipeline上的E2E测试时,验证出A1.1和B2.0集成存在问题,测试变红,Pipeline挂掉,从而阻断了A服务的1.1版本部署到产品环境,保证了产品环境不会被A的1.1版本破坏。

同样,假设A还没有被修复之前,B也有了新的提交,产生了一个新的版本B2.1,这时在B服务Pipeline上的E2E测试并不获取当前A服务的代码库最新版本1.1做集成测试,而是获取产品环境上的当前版本A1.0版本做集成测试。我们假设B2.1和A1.0之间的集成没有问题,测试通过,所以B的2.1版本就被成功的交付到了产品环境,而此时产品环境的A服务的版本仍是1.0。

看!服务之间的阻塞被神奇的解决了,服务再也不会被堵在一个统一的十字路口,而是各行其道,A的车道出了事故,是A的问题,应该由A来承担后果和解决问题,不应该影响到其他服务,其他服务依然可以持续的交付到产品环境。

向前看是持续集成,向后看是持续交付!

看到这里可能有些小伙伴会感到有些失望。咋呼半天,不就是将E2E测试整到每个服务的Pipeline里,再把获取版本从最新代码改成产品环境么?有啥厉害的。

但是,在我看来,这个看似简单的变化,意义却是重大的:它揭示了“持续集成”和“持续交付”的一个主要区别。

“持续集成”和”持续交付”,这两个概念相信大家一定都不陌生,在软件领域也被提了不少年头了,不算什么新概念新技术。但对于这两个概念,我们经常一起提及,也经常混淆,搞不清楚两者的区别到底是什么,可能认为持续交付只不过是持续集成的演进版,新瓶装旧酒而已。

但其实它们却有着本质的区别。

“持续集成”关注的是各个集成单元之前最新版本的集成问题,即是不是某个集成单元的最新版本破坏了系统整体的集成,我管这种视角叫:向“前”看。

而“持续交付”关注的应该不是集成单元最新版本之间的集成问题,而是某个集成单元的最新版本是否可以(能和敢)部署到产品环境。换句话说就是维持产品环境的其他服务不变,只将当前集成单元的最新版本部署到产品环境,产品是否依然可用,不被破坏。所以在“持续交付”的视角下,应该关注的是当前集成单元与产品环境上的其他服务的版本是否兼容,我管这种视角叫:向“后”看。

向前看是持续集成,向后看才是持续交付,如果前后都不看那就是在裸奔。

但是肯定早有同学在心里疑惑,将E2E测试下放到每一个服务自己的Pipeline中,靠谱么?是不是太重了?根据测试金字塔,E2E测试应该是属于靠近金字塔顶端的测试种类,无论从数量和覆盖范围应该也都不会太多,怎么能靠它来保障服务之间的所有集成点和契约呢?

主角登场-契约测试

细心的同学肯定已经发现上面最后一张图中,我已经悄悄的把E2E测试变为了CT,即Contract Test,契约测试。

契约测试也是这两年伴随微服务架构的兴起,经常被提及的一种比较新的测试类型。在测试金字塔中,他的位置介于E2E和Component Tests(可以理解成单个服务的API测试)之间。

简单的理解,契约测试就是一种可以用类似于单元测试的技术验证两两服务之间集成的测试技术。它相比于更低层次的单元测试的优势是可以测集成(两两服务之间),相比于更高层次的E2E测试的优势是实现方式上又类似于单元测试,更轻量,跑的更快,覆盖的范围也自然可以更广更细。

使用契约测试替换掉E2E测试之后,整个架构也会变得更复杂一些,目前契约测试的框架也有很多,如大家常常提到的Pact或是SpringContracts等等。这里我先以Pact为例予以说明,其他框架实现上可能有些差别,但是思路是一致的。

A服务调用B服务的一个API,我们就称为A和B之间存在了一个契约,即B应该按照这个契约提供一个满足契约要求的API,而A也应该按照这个契约约定的方式来调用B的这个API。在这个过程中A作为调用方,我们称之为Consumer端。B作为被调用方,我们称之为Provider端。

如果A和B都履行契约,按照契约定义的约定调用和被调用,我们就可以认为集成不会有问题。但无论是B擅自修改了API破坏了契约,还是A擅自修改了调用API的方式破坏了契约,都会导致契约被破坏,反应到测试上就是契约测试会失败,反应到产品上就是功能被破坏,出现Bug。

每个契约,例如A->B,都会有Consumer端和Provider端生成的两个产出物:分别是a-b.consumer.json.1.1(由Consumer端生成的契约文件,所以版本也是Consumer端A的版本号)和a-b.provider.jar.2.0(由Provider端生成的契约验证测试包,他由Provider端生成,所以版本是B的版本)。这个jar包其实就是一组测试,他的输入是a-b.consumer.json,产出则是测试的结果,也就是契约的验证结果:成功或是失败。

可以把A服务产出的契约文件a-b.consumer.json.1.1想象成一把钥匙,把B服务产出的Provider端的测试a-b.provider.jar.2.0想象成一把锁。那契约测试的执行过程就像是用这把钥匙试着去打开这把锁:如果可以打开,我们认为这A1.1->B2.0的契约是满足的,反之契约就是被破坏了。

值得注意的一点就是,契约测试不像E2E测试,它是有方向的,所以我们看到a-b和b-a是两个不同的契约。

所以,只有当A1.1->B2.0和B2.0->A1.1双向的契约都被验证通过后,我们才能认为A1.1版本和B2.0版本的集成是没有问题的。

用契约测试替换E2E测试

回到前面的例子上,假设我们已经构建了ABC三个服务两两之间的契约测试。此时,A服务有了新的提交升级到了1.1版本,那我们如何才能通过契约测试来验证A1.1版本能否交付到产品环境呢?

答案就是只要通过A的1.1版本的最新代码,生成所有A作为Consumer端的契约文件(a-b.consumer.json.1.1和a-c.consumer.json.1.1),用这两把“钥匙”去试着开(作为输入执行Provider端测试)产品环境对应的两把“锁”(a-b.provider.jar.2.0和a-c.provider.jar.3.0)。

如果都可以打开(测试通过)的话,就证明A的新版本1.1作为Consumer端与产品环境的B和C服务是兼容的。

等等,别着急,还没完……

因为我们还需要考虑A作为Provider的情况,做法还是通过A的1.1版本的最新代码生成A版本作为Provider端的契约测试(b-a.provider.jar.1.1和c-a.provider.jar.1.1),拿着这两把“新锁”,然后试着用产品环境上的两把“钥匙”(b-a.consumer.json.2.0和c-a.consumer.json3.0)去开。

如果也都可以打开(测试通过)的话,就证明A的新版本1.1作为Provider端与产品环境的B和C服务也是兼容的。

至此,当验证了A的新版本1.1无论是作为调用端还是被调用端都与产品环境上的其他服务契约满足后,我们就认为A1.1与B2.0和C3.0集成是没有问题的,也就代表A1.1可以被放心地部署到产品环境中,替代现在的1.0版本。

最后,敲黑板划重点

  1. 微服务架构下的独立部署(交付)很重要,但往往容易被忽视,没有被引起足够重视。
  2. 为了实现微服务的独立持续交付,我们要向“后”看,不要向“前”看,即关注当前变更服务与部署环境中其他服务的兼容性而不是关注当前变更服务与其他服务最新版本的兼容性。
  3. 用契约测试来替代E2E测试,降低测试成本,提高测试覆盖,尽早测试。并通过不断地完善契约管理,保障微服务架构质量和避免微服务架构腐化僵化。

相关 [微服务 独立] 推荐:

你的微服务敢独立交付么?

- - 程序猿DD
最近经常在项目或是社区里听到大家谈论微服务架构,但谈论的焦点更多集中在微服务拆分,分布式架构,微服务门槛,DevOps配套设施等话题上. 但是在我眼里,真正能称之为微服务架构的少之又少. 原因也很简单,我所见到的很多所谓的微服务架构项目,大多都没有做到微服务架构的一个基本要求:服务的独立部署(交付).

初识微服务

- - ITeye博客
微服务架构越来越火,有必要学习一下. 软件开发过程中碰到什么问题. 一个简单的应用会随着时间推移逐渐变大. 在每次的sprint中,开发团队都会面对新“故事”,然后开发许多新代码. 几年后,这个小而简单的应用会变成了一个巨大的怪物. 一旦你的应用变成一个又大又复杂的怪物,那开发团队肯定很痛苦. 敏捷开发和部署举步维艰,其中最主要问题就是这个应用太复杂,以至于任何单个开发者都不可能搞懂它.

谈微服务架构

- - 人月神话的BLOG
其实在前面很多文章谈到SOA,特别是系统内的SOA和组件化的时候已经很多内容和微服务架构思想是相同的,对于微服务架构,既然出现了这个新名称,那就再谈下微服务架构本身的一些特点和特性. 从这个图可以看到微服务架构的第一个重点,即业务系统本身的组件化和服务化,原来开发一个业务系统本身虽然分了组件和模块,但是本质还是紧耦合的,这关键的一个判断标准就是如果要将原有的业务系统按照模块分开部署到不同的进程里面并完成一个完整业务系统是不可能实现的.

微服务性能模式

- - 互联网 - ITeye博客
前言:基于微服务系统越来越普遍. 下面我们就来看看五种常见的特定微服务性能的挑战,以及如何应解他们. 背景:在IT界微服务架构为基础的系统越来越多, 每一个应用系统都集成了不同的组件和服务,几乎所有的特定业务应用程序都需要集成一个或更多的应用服务. 但是一个综合性系统集成不同的服务这无疑是一个巨大的挑战.

微服务与架构师

- - 乱象,印迹
因为工作的关系,最近面试了很多软件架构师,遗憾的是真正能录用的很少. 很多候选人有多年的工作经验,常见的框架也玩得很溜. 然而最擅长的是“用既定的技术方案去解决特定的问题”,如果遇到的问题没有严格对应的现成框架,就比较吃力. 这样的技能水平或许适合某些行业,但很遗憾不符合我们的要求. 软件架构师到底应该做什么,又为什么这么难做好,这都是近来的热门问题,我也一直在和朋友们讨论.

从Excel到微服务

- - 乱象,印迹
Excel很老,Excel很土,Excel一点也不sexy;微服务新,微服务很潮门,微服务很高大上. 那么,Excel和微服务有什么关系. 上个月看了篇文章,The Unbunlding of Excel. 作者认为,对于初创公司(尤其是非“纯IT”初创公司)来说,Excel几乎包办各种工作. 想做轻量级的CRM,可用Excel.

微服务拆分之道

- - DockOne.io
微服务在最近几年大行其道,很多公司的研发人员都在考虑微服务架构,同时,随着 Docker 容器技术和自动化运维等相关技术发展,微服务变得更容易管理,这给了微服务架构良好的发展机会. 在做微服务的路上,拆分服务是个很热的话题. 我们应该按照什么原则将现有的业务进行拆分. 接下来一起谈谈服务拆分的策略和坚持的原则.

微服务之saga模式

- -
你已经使用 database ber service 模式. 每个service拥有自己的database. 一些业务事务会跨越多个service,所以你需要来确保data consistency. 例如,假设你正在构建一个电子商务网站,这个网站的用户的会有一个最大欠款限制,应用程序必须确保一个新订单不能超过用户的最大前款限制,但是orders表和customers表不在同一个数据库,所以应用程序不能简单的使用本地的ACID事务.

微服务安全简介

- - 掘金 架构
​由于其可扩展性、灵活性和敏捷性,微服务架构已经变得越来越受欢迎. 然而,随着这种架构的分布和复杂性增加,确保强大的安全措施变得至关重要. 微服务的安全性超越了传统的方法,需要采用全面的策略来保护免受不断演变的威胁和漏洞的影响. 通过理解核心原则并采取有效的安全措施,组织可以加强其微服务架构,并保护敏感数据和资源.

独立日游行

- 躲在街角的猫 - 南桥的博客