微服务和演进式架构
在我们的软件开发流程中,经常需要面临改动,有来自用户需求的改动,来自市场的,以及为了一些潜在机会而产生的改动等。当这些改动来临的时候,我们需要能够快速做出调整。但不幸的是,事情并不总是如我们所愿。
那么我们之前是怎么做的呢?
对于资历较老的程序员来说,应该都还记得有一种基于可重用组件的设计方法。在这种方法中,专门有一个架构团队,他们来识别出整个组织中 各个部门应该怎么使用某一组件。某些时候架构团队的业绩甚至还会与其所设计的组件被使用的次数进行挂钩。但是不幸的是,由于架构团队和组件使用团队缺少连接,因此架构团队很难设计出一个有口皆碑,受使用者欢迎的组件。
还记得另外一种通过数据库做集成的架构么?是的,我们之前都是这么做的,在这种架构模式中,我们有一个巨型数据库,所有的数据都囊括其中。围绕着这个数据库的周围,有一圈各种各样的应用,它们支配着这些数据。
此外还有一种曾经颇为流行的面向服务的架构,这种架构理念声称它们可以达到我们想达到的目标。这种架构确实有其做的不错的一些地方,首当其冲的是它让组织者知道并不是所有的东西都必须是模块化的。与此同时,它也激发了大家开始思考,将多种功能更好的集成到一起,而不是紧耦合到一块,比如你可以想想使用中间件,消息队列或者其他一些更松耦合的方法。
SOA(面向服务型架构)让最终一致性得到更加广泛的认同。一直以来,传统的(DBA)数据库管理员这个角色起着至关重要的作用,因为最有价值的数据资源均由他们保护。在访问数据库的时候,一切都是关系型的,都是事务处理。因此每当你说我需要做持久化的时候,就会有人告诉你,放在一个关系型数据库里就好了,大家对此都达成默契了,没有任何问题。然后所有的操作都是完整的事务处理,我们可以使用关系型数据库所能提供的所有功能。以前大家都是这么干的。而SOA的出现最少启发了大家开始讨论我们是不是可以用另外的方式来取代之前的老路子,从这个方面来讲,SOA确实有其正面意义。
但不幸的是,SOA也犯了一些错误。最主要的一个问题是其庞大的服务。如果你看一看使用SOA模型做出来的这些服务,这些海量服务作为一个全局组件,还是太大了,太难做出改动,也太难部署了。其次,SOA倾向于提供方驱动的服务,我指的意思是,当你开始想我要写一个用户服务,我要给用户提供这些信息,因为我是这些信息的提供方。我假设我知道用户将会如何使用这些信息。这种思路其实和设计可重用组件的架构师差不多,我们统称为信息提供方驱动。一般而言是由组织中的IT部门做完之后移交给业务部门使用,这种设计中的想法大部分是从系统的角度在想问题,而不是从应用的角度。
SOA的另外一个问题是其充斥着大量的编配(orchestration),注意这里我们说的是编配而不是编排(choreography)。在管弦乐(也是orchestration这个词)中,有一个统一的指挥,由他来指挥整个乐团,他来设置节奏,音量等各种信息。整个乐团里的个体都无一例外的听命于这个指挥。我们设想一下,如果某个时候同时发生两件事,那么可能就需要两个指挥。总而言之,在这种模型里面,有一个类似指挥的角色来对整个系统全盘负责。而编排却不是这样,在编排的时候,每个个体只需要关注自己周围的其他成员就行,而并没有一个统一的总指挥。当出现问题的时候,每个个体只要知道自己应该怎么调整就行,同样也不需要一个所谓的总指挥来指挥它进行调整。SOA这种类似管弦乐团的组织方式,过分集中化了,从而导致其本身通常难以测试。
SOA还有一个问题是工艺受阻。SOA需要我们有这样的供应商,能满足业务部门的所有想法,然后业务部门只需要坐在自己的办公室里面做一些自定义的配置就可以了。因此最后的工具通常是我们将一些之前有人用过的系统稍作修改来供应另外一群用户。
事实上,SOA搞出来的东西甚至比没有他们之前还要更难以修改。讽刺的是SOA恰恰是为了让改动变得容易而诞生的。与此同时,SOA不可避免的也很难进行测试。
好,现在我们来聊聊微服务(micro-services)
简单来讲,你可以将微服务理解成正确的SOA模式。我们的关注点还是服务,在微服务的架构里面,服务是作为一个基础单元存在的。SOA本身即是面向服务的架构,所以我们这个其实也可以叫SOA2,但是我们还是用了微服务这个名字,目的就是提醒大家不要搞海量服务。这是一段James Lewis和Martin Fowler的文章中摘抄的一段关于微服务的定义:
一种将一组小型服务做成一个单独应用的开发方式,每一个小型服务在其独自的进程中运行,使用诸如HTTP资源API等其他轻量级方式进行通信。重点是要轻量级。
微服务有些什么特点呢?
第一个显而易见的特点是,微服务中每个个体服务单元很小。这里所谓的小其实并不是绝对的,其思想的要点是,与其搞一个大而全的服务体,我们更倾向于小一些的单一服务。微服务的另外一个特点是,这些服务是按照业务能力构建的。之前我们经常掉入一个陷阱,就是从系统的角度思考问题,而不是从业务角度。现在我们不仅要思考系统能力,更要思考业务能力。并且我们需要按照业务能力来组织工作。对于业务部门而言,如何实现的并不重要,他们所关心的是系统长什么样子,怎么样能方便用户使用。业务部门是这么思考的,我们也对应的需要围绕业务能力进行设计。
微服务中的服务个体都是可以单独部署的。也就是说,只要没有动接口协议,在修改某一个服务个体的时候,完全不用理会其他服务个体。在微服务中,只有很少一部分的集中管理,而由于各个服务个体之间是相对独立的,因此我们甚至可以在不同的服务单元采用不同的技术栈。比如某一部分的计算逻辑比较复杂,我们选用clojure,另外一部分则对应着很强的对象模型,我们可以用一些面向对象的编程语言等等。
在微服务中,我们主张“智能端点”和“傻瓜管道”。基本上如果管道两边都按照一定的假设强制执行的话,需要重新配置时就会简单许多,而并不需要时刻监控管道中的各种中间状态。当我们尝试把现在的这种模块拆分的时候,需要考虑的另一个问题是,数据库的结构应该是什么样的?具体的问题还包括数据如何进行维护,其他单元如何缓存等等。毫无疑问在微服务中,数据结构的形态会有很大不同。
使用微服务意味着什么呢?
首先粒度的问题是至关重要的,当你在思考微服务的边界问题时,这应该是使用微服务架构需要做出的一个最重要的决定。在这个过程中需要平衡好内聚和耦合的关系,一方面为了加强内聚你可能需要将某一些服务合并到一起,另外一方面为了解耦,需要对某些服务进行拆分。事实上在使用微服务架构的整个过程中都充斥着来回往复的拆分和合并,直到你真的理解了服务的边界应该是什么为止。我们后面会继续回到这个问题上。
微服务中的各个服务个体是可以独立扩展的,也就是说如果某个服务上的需求量猛增,我们只需要扩展该服务即可,于其他服务无关。相应的,对于各个服务的状态监控也非常重要。如果你手头是一个单块,从定义来看,理论上你需要监控的只有一个东西。而到了微服务里面,因为我们有大量的小型服务,因此需要对所有这些服务进行监控才行,你需要清除的知道每一个服务是不是在工作,是不是在正常工作,为此光监控就会需要更多的机器。
在微服务中,我们还需要考虑服务挂掉的情况,而这些情况在传统单块架构里面一般不用怎么考虑。比如我们有一块业务需要依赖数个独立的微服务,那么我们需要考虑的失败场景就相当多了,基本上是把所有这些可能的失败进行排列组合,而不得不说的一点是,在传统单块架构里面这些可能根本就不成之为一个问题。
在微服务架构中,因为我们有很多独立的单元,它们需要独自进行部署,因此我们真心离不开基础设施的自动构建,自动化部署和持续交付这些基础。在微服务中,虽然我们讲在不同的服务中可以使用不同的技术栈,但是这种灵活性如果任其发展最终也会完全不可控,因此我们需要管理好这种灵活性。我之前呆过的一个项目里面,光是XML解析就有11种不同的方法,是的你没听错,11种之多。一般来讲我觉得两种XML解析器我尚能接受,但是11种实在是难以理解。因此在微服务架构中,我们必须要避免引入所有类型的编程语言,引入所有不同种类的数据库,没什么原因,明眼人都知道,我们不能这么做。在系统的技术栈方面,我们必须在某个层面进行控制,否则一发不可收拾了。
当然最终一致性也必须管理好。一旦我们开始将数据分散到各个不同的服务单元里面去,我们必须考虑到这对于信息传播会有什么影响。其中的一个结果就是,我们需要给业务人员讲清楚,什么是最终一致性。一般而言我觉得业务人员会理解成“可能会一致”,而不是最终会达到一致。我们必须时刻想到由于最终一致性的存在所可能产生的各种后果。同样,如果我们用的是一个大而全的关系型数据库的话,压根就不会存在这个问题。
好了,大家看看我们列出来的这些个问题,他们一个比一个复杂,而在传统的单块模型中,这些问题我们根本不用想。当我们回顾这些问题的时候,不由得会想到我们的初衷,如果我们的初衷能够得以实现,最少能弥补解决这些问题时所带来的痛处。我们的初衷是什么呢?让系统能尽可能快的响应变化!另外一个方面,这些问题也在一定程度上警醒着你,微服务这个东西不是你想象的那么简单,不是照着教程做一遍就可以大功告成,然后出门跟人炫耀:哥在搞微服务哦,牛逼吧!
最后有一点差点忘了说了,接口的改动也必须有个限度。在定义清楚服务边界的时候,你少不了要修改一些接口协议,在修改的时候务必想清楚这个修改意味着什么。这个也深受团队成员结构的影响,比如如果写另外一个服务的人就在你旁边,改起来三两句话就搞定了,但是如果团队成员咫尺天涯或者天各一方,这个沟通过程本身就是一个巨大的成本。
好了,以上这些就是我认为的在准备使用微服务的时候,需要想清楚的一些问题。
好了,我们现在有两个选项。第一个是一片绿地,就好比我们拿到一个空白的项目,没有任何的遗留代码。这种情况也有,但是很少。另外还有一点,在马丁即将发表的一篇文章里提到,我们并不建议在一个崭新的项目中一开始就使用微服务进行开发。因为你很可能并不真的理解业务领域,从而也很难理解各个服务的边界。因此在这种项目中,可以先做成单块的,进行适当的模块化,加深理解之后再考虑演进成微服务。总而言之,你迟早会走到这个点,就是我们怎么将一个单块系统拆解成微服务架构的。
首先,想清楚边界上下文。这其实是从领域驱动设计一书中拿过来的概念。所谓的边界,指的是你可以将某些部分用一个圈圈起来,圈里的内容代表了一定的业务面。这个所谓的边界其实就是我们设计服务边界时的一个重要指示。再回到我之前说的那个问题,你需要对你的系统有一个全面透彻的认知之后,才能做出这些决定。作为划分服务边界的第一个提示便是:尝试按照领域驱动设计的方式来思考服务的边界应该在哪里。
其次还是这一点:从业务能力的角度来思考服务的边界。想想系统所提供的产品、服务是什么,从这个角度想想怎么样才算一个合理的服务边界。
想想调用者需要什么。与提供方驱动的方式不同的是,你需要想想这个服务的使用者会需要些什么信息。也就是说需要从真实的需求开始想问题,而不是一些作为提供方揣测出来的需求。
想想服务之间的通信模式。想想哪些服务,哪些系统可能会用到同一组数据,不同的服务之间怎么合作来实现一个特殊的业务产出。特别是当我们在犹豫是要将两个服务分开还是合并到一块的时候,想想拆开之后可能产生的各种通信,以及通信失败的情形等等。
想想数据结构。一般而言,大家不愿意花太多精力想怎么保护数据的问题,也不会纠结太多最终一致性产生的影响。你怎么设计数据,合适的数据结构应该是什么样的也可以作为一个指示你如何设计服务边界的提示。因为服务是持有数据的。我们不再想的是一个数据库周围围绕着一堆服务,而是每一个服务有一个自己的持久化存储。
想想联动变化模式。比如有两个业务部分需要一起改,其实某种程度上在提示你,是不是把这两块放到一个服务里面更加合适。因此多想想未来可能产生的各种改动,对你做出决定也会有所帮助。
做好出错的准备,你可能经常需要将一些服务进行合并,而后搞不好又要重新再拆开。由于服务还牵扯到数据,如果两个服务的关系是一个服务主要负责一部分数据,而另外一个服务中对这部分数据只是进行缓存的话,你或许还要考虑一些需要数据迁移的情况。一般而言你不会想经常修改服务的边界,因为这会带来一系列的应用程序,数据的改动。但是还是那句话,做最坏的打算。
最后的一点是:做一名有耐心的读者。这其实扯到演进架构的内容了。首先是我们不能把build搞挂了。我们能做的是只有在没有别的选择的时候,我们才开始改。而做耐心的读者就是为了确保他们改的确实是你所需要的。也就是说在正式确定修改我们实现之前,需求本身可能改的还要多一些。
OK,那么微服务与演进式架构还有什么联系呢?其实当我们谈论演进式架构的时候,其实是有很多不同的原则的。这些原则都很严格,而微服务某种程度上展现出了一些演进式架构的原则。我们在谈微服务的时候,主要是想用它来快速应对频繁的需求变更,因此微服务所承载的期望便是让我们能尽可能快的适应变化。而演进式架构中所倡导的进化性与此不谋而合。可进化型并不同于可维护性,但也不是说要怎么预测未来,而是一种随时准备响应变化的状态,而不管你是否提前就设想好了这些变化。以前关于可进化性的一个理解误区是,我要非常聪明的想到所有可能出现的变化,并且为所有的这些可能的变化写好代码,哪怕到头来根本就没变。因此微服务将可进化性作为其首要目标。
耐心的读者,是的前面已经提到过一遍了。我们可以做出任何改变,但这种改变必须是我们经过大量的沟通讨论之后得出的统一结论。
遵从康威法则。我们需要关注怎么组织团队结构,以及不同的团队结构可能影响到最终的系统形态。只有团队对服务有很好的所有权意识,团队做出来的微服务才是这种松耦合的独立服务。某些采用了微服务的组织中,但是并没有一个专门的支持维护团队,因此每个团队成员会不自觉地将他的那部分代码写好,因为谁也不想大周末的还要跑到公司来修BUG。
适当耦合。我们在谈演进式架构的时候,其实总是在平衡耦合与性能、复杂度等其他因素之间的关系。
协议。在微服务架构中,各个服务可以独立演进。那么必不可少的一环就是相互之间的接口协议。通常而言我们可以借助于一些协议测试工具或者文档来确保相互之间正常工作。作为服务提供方同时还应该提供对应的接口测试给调用方,而一旦调用方发现这个测试失败了,马上需要找到提供方确认问题。测试的另外一重含义是,只要这个全面的接口测试是正常通过的,那么无论提供方把代码改成什么样了,调用一方都不用关心,因为大家都是独立的,链接这彼此的就是这些接口协议,这也是为什么微服务中的接口协议显得如此重要的一个重要原因。
最后,作为一名来自ThoughtWorks的员工,我不得不谈谈测试相关的事情。我们发现如果你始终沿着方面测试的方向设计架构,最后的架构会更加简洁。而且易于测试的系统一定是有一个清楚的定义的,你甚至不需要颗粒度太细的测试,哪怕只有一个非常顶层的端到端测试,但是只要这个测试能简单明了的描述了其行为就已足够。当你的架构设计的很好,边界划分的很清楚的时候,系统本身就应该是很好测试的。我们发现,一般而言,不管是从代码层面还是系统层面,关注测试始终会让你的架构更加完善。而且一般而言,基于测试定义的边界,也让整个架构更容易做出改变。
持续交付在这里扮演什么角色呢?
微服务相比于传统架构,增加了大量的运维负担。有更多的东西需要部署,有更多的地方需要监控,出错的地方自然也成倍增加,有更多的数据库,对应的需要更多的备份。在微服务架构中,出现问题时如何快速恢复也是件很复杂的事,因为服务数量众多,对应的排列组合情况更是数不胜数。
第二点是微服务需要很强的运维文化,这里我用了“不明智”这个词,主要是强调运维文化对于微服务架构的重要性。如果你的开发团队和运维团队之间有堵高墙,那么用微服务实施下去必然是个灾难。这里面有大量的琐事需要做好做对,需要理解清楚。如果没有一种强运维环境,你那个单独的运维团队一定会被你搞疯掉的。
如果不能严格执行持续交付,而选择了微服务也不是一种明智之举。在程序出错的时候,我们需要赶紧进行调试,但是如果你连现在的代码是哪个版本都没法准确知道,你告诉我怎么他喵的调试?所以严格执行持续交付是微服务架构赖以成功之根本。关于持续交付的内容,比如基础设置自动化,自动化部署,自动化测试等等都缺一不可。拿自动化测试举例,与传统架构相比,你的测试场景会更加复杂,甚至就连程序主路径这一个方面都要比原来负责很多,因此测试就显得至关重要了。
我应该从哪儿开始呢?
跟前面讲的一样,我们始终建议在完全透彻的理解业务背景的前提下再尝试使用微服务。在一个崭新的项目上,始终建议从一个单块架构开始,因为你不可避免的会犯一些边界划分的错误,除非你真的是对于这个领域烂熟于胸,倒背如流。在开始的单块架构中,你依然可以让代码保持整洁清晰,有完整的测试覆盖。实施微服务的时候所面临的一个最大问题是,大家容易把它用错了,导致大量没有必要的反复更改,从这个意义上来讲,从一个单件开始不失为一种较为安全的选择。
微服务是一把尚方宝剑吗?
我们说它并不是。我们确实见证了不少使用微服务架构的成功案例,但是就微服务本身还是有很多问题需要考虑。比如我之前提到的最终一致性的问题,错误的排列组合问题等。这些问题也并不是我们就完全不知道怎么解决,我们知道怎么管理开发流程,我们知道该怎么沟通,只是在微服务中要稍微复杂一些而已。但是面对这些问题的时候,不要害怕做出修改。你完全可以重新设计某些部分。我们有严格的接口协议,全面的接口测试覆盖,来保证你在修改业务流程、引入新功能、新的校验方式时更加从容。事实上这些东西也确实容易朝秦暮楚,现在只要谁在社交媒体上秀了个新功能,马上大家一窝蜂的都想要这个新功能了。用户的期待就是改变的这么快,业务生命周期也越来越短,安全相关的问题也是日新月异,所有这些变更都驱使着我们必须快速的适应变化,而微服务便是在这样的大环境下应运而生的,但是微服务并不是免费的蛋糕,也有其自身的成本消耗。
最后给大家推荐一本关于微服务的书,是的你没看错,就是这本《持续集成》。你可以将微服务看成我们开将持续交付的里面引入软件开发行业之后的第一个主要架构。在这本书里面你会看到,如果没有持续交付,微服务中所提到的很多东西你根本就不会朝这个方向想,也更不会想这么做。持续交付和微服务只有组合在一起才能展现出相互的价值,就像我前面所说的,我们需要喊上运维的同事一起来做这个事情,因为在微服务中他们的部署方式,监控方式都会与之前大不相同。
谢谢各位 Rebecca