第 1 章 微服务
多年以来,我们一直在寻找更好的方法来构建应用系统。我们一直在学习已有的技术,尝试新技术,也目睹过不少新兴技术公司使用不同的方式来构建 IT 应用系统,从而提高了客户满意度和开发效率。
Eric Evans 的《领域驱动设计》一书帮助我们理解了用代码呈现真实世界的重要性,并且告诉我们如何更好地进行建模。持续交付理论告诉我们如何更有效及更高效地发布软件产品,并指出保持每次提交均可发布的重要性。基于对 Web 的理解,我们寻找到了机器与机器交互的更好方式。Alistair Cockburn 的六边形架构理论(http://alistair.cockburn.us/Hexagonal+architecture )把我们从分层架构中拯救出来,从而能够更好地体现业务逻辑。借助虚拟化平台,我们能够按需创建机器并且调整其大小,借助基础设施的自动化我们也很容易从一台机器扩展到多台。在类似 Amazon 和 Google 这样成功的大型组织中,有很多小团队,他们各自对某个服务的全生命周期负责。最近,Netflix 分享了构建大型反脆弱系统的经验,而这种构建方式在 10 年前是很难想象的。
随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团队、大型集群系统这些实践的流行,微服务也应运而生。它并不是被发明出来的,而是从现实世界中总结出来的一种趋势或模式。但是没有前面提及的这些概念,微服务也很难出现。在本书接下来的内容中,我会尝试把这些概念整合起来,从而给出一个涉及如何构建、管理和演化微服务的全景图。
很多组织发现细粒度的微服务架构可以帮助他们更快地交付软件,并且有更多机会尝试新技术。微服务在技术决策上给了我们极大的自由度,使我们能够更快地响应不可避免的变化。
1.1 什么是微服务
微服务就是一些协同工作的小而自治的服务。让我们详细地分析一下微服务的定义,看看它有什么不同之处。
1.1.1 很小,专注于做好一件事
随着新功能的增加,代码库会越变越大。时间久了代码库会非常庞大,以至于想要知道该在什么地方做修改都很困难。尽管我们想在巨大的代码库中做到清晰地模块化,但事实上这些模块之间的界限很难维护。相似的功能代码开始在代码库中随处可见,使得修复 bug 或实现更加困难。
在一个单块系统内,通常会创建一些抽象层或者模块来保证代码的内聚性,从而避免上述问题。内聚性是指将相关代码放在一起,在考虑使用微服务的时候,内聚性这一概念很重要。Robert C. Martin 有一个对单一职责原则 (Single Responsibility Principle,http://programmer.97things.oreilly.com/wiki/index.php/The_Single_Responsibility_Principle )的论述:“把因相同原因而变化的东西聚合到一起,而把因不同原因而变化的东西分离开来。”该论述很好地强调了内聚性这一概念。
微服务将这个理念应用在独立的服务上。根据业务的边界来确定服务的边界,这样就很容易确定某个功能代码应该放在哪里。而且由于该服务专注于某个边界之内,因此可以很好地避免由于代码库过大衍生出的很多相关问题。
经常有人问我:代码库多小才算小?使用代码行数来衡量是有问题的,因为有些语言的表达力更好,能够使用很少的代码完成相同的功能。还有一个需要考虑的因素是,一个服务的代码可能有多个依赖项,而每个依赖项又会包含很多代码。此外,一个不可避免的事情是你的领域对象本身很复杂,所以需要更多的代码。澳大利亚 RealEstate.com.au 的 Jon Eaves 认为,一个微服务应该可以在两周内完全重写,这个经验法则在他所处的特定上下文中是有效的。
我可以给出的另一个比较老套的答案是:足够小即可,不要过小。当我在会议上做演讲的时候,几乎每次都会问听众:谁认为自己的系统太大了,想把它拆成更小的。几乎所有人都会举手。看起来大家都能够意识到什么是“过大”,那么换句话说,如果你不再感觉你的代码库过大,可能它就足够小了。
另外一个帮助你回答服务应该多小的关键因素是,该服务是否能够很好地与团队结构相匹配。如果代码库过大,一个小团队无法正常维护,那么很显然应该将其拆成小的。在后面关于组织匹配度的部分会对该话题做更多讨论。
当考虑多小才足够小的时候,我会考虑这些因素:服务越小,微服务架构的优点和缺点也就越明显。使用的服务越小,独立性带来的好处就越多。但是管理大量服务也会越复杂,本书的剩余部分会详细讨论这一复杂性。如果你能够更好地处理这一复杂性,那么就可以尽情地使用较小的服务了。
1.1.2 自治性
一个微服务就是一个独立的实体。它可以独立地部署在 PAAS(Platform As A Service,平台即服务)上,也可以作为一个操作系统进程存在。我们要尽量避免把多个服务部署到同一台机器上,尽管现如今机器的概念已经非常模糊了!后面会讨论到,尽管这种隔离性会引发一些代价,但它能够大大简化分布式系统的构建,而且有很多新技术可以帮助解决这种部署模型带来的问题。
服务之间均通过网络调用进行通信,从而加强了服务之间的隔离性,避免紧耦合。
这些服务应该可以彼此间独立进行修改,并且某一个服务的部署不应该引起该服务消费方的变动。对于一个服务来说,我们需要考虑的是什么应该暴露,什么应该隐藏。如果暴露得过多,那么服务消费方会与该服务的内部实现产生耦合。这会使得服务和消费方之间产生额外的协调工作,从而降低服务的自治性。
服务会暴露出 API(Application Programming Interface,应用编程接口),然后服务之间通过这些 API 进行通信。API 的实现技术应该避免与消费方耦合,这就意味着应该选择与具体技术不相关的 API 实现方式,以保证技术的选择不被限制。本书后面会讨论选择好的解耦性 API 的重要性。
如果系统没有很好地解耦,那么一旦出现问题,所有的功能都将不可用。有一个黄金法则是:你是否能够修改一个服务并对其进行部署,而不影响其他任何服务?如果答案是否定的,那么本书剩余部分讨论的那些好处对你来说就没什么意义了。
为了达到解耦的目的,你需要正确地建模服务和 API。后面会针对这个话题做更多讨论。
1.2 主要好处
微服务有很多不同的好处,其中很多好处也适用于任何一个分布式系统。但相对于分布式系统或者面向服务的架构而言,微服务要更胜一筹,它会把这些好处推向极致。
1.2.1 技术异构性
在一个由多个服务相互协作的系统中,可以在不同的服务中使用最适合该服务的技术。尝试使用一种适合所有场景的标准化技术,会使得所有的场景都无法得到很好的支持。
如果系统中的一部分需要做性能提升,可以使用性能更好的技术栈重新构建该部分。系统中的不同部分也可使用不同的数据存储技术,比如对于社交网络来说,图数据库能够更好地处理用户之间的交互操作,但是对于用户发布的帖子而言,文档数据库可能是一个更好的选择。图 1-1 展示了该异构架构。
图 1-1:微服务帮助你轻松地采用不同的技术
微服务可以帮助我们更快地采用新技术,并且理解这些新技术的好处。尝试新技术通常伴随着风险,这使得很多人望而却步。尤其是对于单块系统而言,采用一个新的语言、数据库或者框架都会对整个系统产生巨大的影响。对于微服务系统而言,总会存在一些地方让我可以尝试新技术。你可以选择一个风险最小的服务来采用新技术,即便出现问题也容易处理。这种可以快速采用新技术的能力对很多组织而言是非常有价值的。
不过为了同时使用多种技术,也需要付出一些代价。有些组织会限制语言的选择,比如 Netflix 和 Twitter 选用的技术大多基于 JVM(Java Virtual Machine,Java 虚拟机),因为他们非常了解该平台的稳定性和性能。他们还在 JVM 上开发了一些库和工具,使得大规模运维变得更加容易,但这同时也使得我们更难以采用 Java 外的其他技术来编写服务和客户端。尽管如此,Twitter 和 Netflix 也并非只使用一种技术栈。另一个会影响多技术栈选用的因素是服务的大小,如果你真的可以在两周内重写一个服务,那么尝试使用新技术的风险就降低了不少。
贯穿本书的一个问题是,微服务如何寻找平衡。第 2 章我们会讨论如何做技术选择,其中主要专注于演进式架构;第 4 章主要关注集成,你将学会如何避免服务之间的过度耦合,从而可以使其彼此独立地进行技术演化。
1.2.2 弹性
弹性工程学的一个关键概念是舱壁。如果系统中的一个组件不可用了,但并没有导致级联故障,那么系统的其他部分还可以正常运行。服务边界就是一个很显然的舱壁。在单块系统中,如果服务不可用,那么所有的功能都会不可用。对于单块服务的系统而言,可以通过将同样的实例运行在不同的机器上来降低功能完全不可用的概率,然而微服务系统本身就能够很好地处理服务不可用和功能降级问题。
微服务系统可以改进弹性,但你还是需要谨慎对待,因为一旦使用了分布式系统,网络就会是个问题。不但网络会是个问题,机器也如此,因此我们需要了解出现问题时应该如何对用户进行展示。
第 11 章会就弹性处理和对故障模式的处理做更多讨论。
1.2.3 扩展
庞大的单块服务只能作为一个整体进行扩展。即使系统中只有一小部分存在性能问题,也需要对整个服务进行扩展。如果使用较小的多个服务,则可以只对需要扩展的服务进行扩展,这样就可以把那些不需要扩展的服务运行在更小的、性能稍差的硬件上,如图 1-2 所示。
图 1-2:可以针对那些需要扩展的微服务进行扩展
Gilt 是一个在线时尚零售商,他们就是因为这个原因而采用了微服务。2007 年,他们还是一个单一的 Rails 应用,2009 年,Gilt 的系统无法解决其负载。通过将系统的核心部分抽离出来之后,Gilt 在流量处理方面有了大大的改进。如今 Gilt 有 450 多个微服务,每一个服务都分别运行在多台机器上。
在使用类似 Amazon 云服务之类的平台时,也可以只对需要的服务进行扩展,从而节省成本。通过架构来节省成本的情形还真是不多见。
1.2.4 简化部署
在有几百万代码行的单块应用程序中,即使只修改了一行代码,也需要重新部署整个应用程序才能够发布该变更。这种部署的影响很大、风险很高,因此相关干系人不敢轻易做部署。于是在实际操作中,部署的频率就会变得很低。这意味着在两次发布之间我们对软件做了很多功能增强,但直到最后一刻才把这些大量的变更一次性发布到生产环境中。这时,另外一个问题就显现出来了:两次发布之间的差异越大,出错的可能性就更大!
在微服务架构中,各个服务的部署是独立的,这样就可以更快地对特定部分的代码进行部署。如果真的出了问题,也只会影响一个服务,并且容易快速回滚,这也意味着客户可以更快地使用我们开发的新功能。Amazon 和 Netflix 等组织采用这种架构主要就是基于上述考虑。这种架构很好地清除了软件发布过程中的种种障碍。
微服务部署领域的技术在过去几年时间里发生了巨大的变化,第 6 章会对该话题做更深入的讨论。
1.2.5 与组织结构相匹配
我们经历过太多由于团队和代码库过大引起问题的情况。当团队是分布式的时候,问题会更明显。我们也知道在小型代码库上工作的小团队更加高效。
微服务架构可以很好地将架构与组织结构相匹配,避免出现过大的代码库,从而获得理想的团队大小及生产力。服务的所有权也可以在团队之间迁移,从而避免异地团队的出现。在第 10 章讲解康威定律时会对该话题做更深入的讨论。
1.2.6 可组合性
分布式系统和面向服务架构声称的主要好处是易于重用已有功能。而在微服务架构中,根据不同的目的,人们可以通过不同的方式使用同一个功能,在考虑客户如何使用该软件时这一点尤其重要。单纯考虑桌面网站或者移动应用程序的时代已经过去了。现在我们需要考虑的应用程序种类包括 Web、原生应用、移动端 Web、平板应用及可穿戴设备等,针对每一种都应该考虑如何对已有功能进行组合来实现这些应用。现在很多组织都在做整体考虑,拓展他们与客户交互的渠道,同时也需要相应地调整架构来辅助这种变化的发生。
在微服务架构中,系统会开放很多接缝供外部使用。当情况发生改变时,可以使用不同的方式构建应用,而整体化应用程序只能提供一个非常粗粒度的接缝供外部使用。如果想要得到更有用的细化信息,你需要使用榔头撬开它!第 5 章会讨论如何将已有的单块应用程序分解成为多个微服务,并且达到可重用、可组合的目的。
1.2.7 对可替代性的优化
如果你在一个大中型组织工作,很可能接触过一些庞大而丑陋的遗留系统。这些系统无人敢碰,却对公司业务的运营至关重要。更糟糕的是,这些程序是使用某种奇怪的 Fortran 变体编写的,并且只能运行在 25 年前就应该被淘汰的硬件上。为什么这些系统直到现在还没有被取代?其实你很清楚答案:工作量很大,而且风险很高。
当使用多个小规模服务时,重新实现某一个服务或者是直接删除该服务都是相对可操作的。想想看,在单块系统中你是否会在一天内删掉上百行代码,并且确信不会引发问题?微服务中的多个服务大小相似,所以重写或移除一个或者多个服务的阻碍也很小。
使用微服务架构的团队可以在需要时轻易地重写服务,或者删除不再使用的服务。当一个代码库只有几百行时,人们也不会对它有太多感情上的依赖,所以很容易替换它。
1.3 面向服务的架构
SOA(Service-Oriented Architecture,面向服务的架构)是一种设计方法,其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。服务之间通过网络调用,而非采用进程内调用的方式进行通信。
人们逐渐认识到 SOA 可以用来应对臃肿的单块应用程序,从而提高软件的可重用性,比如多个终端用户应用程序可以共享同一个服务。它的目标是在不影响其他任何人的情况下透明地替换一个服务,只要替换之后的服务的外部接口没有太大的变化即可。这种性质能够大大简化软件维护甚至是软件重写的过程。
SOA 本身是一个很好的想法,但尽管做了很多尝试,人们还是无法在如何做好 SOA 这件事情上达成共识。在我看来,业界的大部分尝试都没能把它作为一个整体来看待,因此很难给出一个比该领域现有厂家提供的方案更好的替代方案。
实施 SOA 时会遇到这些问题:通信协议(例如 SOAP)如何选择、第三方中间件如何选择、服务粒度如何确定等,目前也存在一些关于如何划分系统的指导性原则,但其中有很多都是错误的。本书的剩余部分会分别讨论这些问题。一些激进人士可能会认为这些厂家提出并推动 SOA 运动的目的不过就是想要卖更多的产品,而这些相似的产品最终破坏了 SOA 的目标。
现有的 SOA 知识并不能帮助你把很大的应用程序划小。它没有提到多大算大,也没有讨论如何在现实世界中有效地防止服务之间的过度耦合。由于这些点没有说清楚,所以你在实施 SOA 时会遇到很多问题。
在现实世界中,由于我们对项目的系统和架构有着更好的理解,所以能够更好地实施 SOA,而这事实上就是微服务架构。就像认为 XP 或者 Scrum 是敏捷软件开发的一种特定方法一样,你也可以认为微服务架构是 SOA 的一种特定方法。
1.4 其他分解技术
当你开始使用微服务时会发现,很多基于微服务的架构主要有两个优势:首先它具有较小的粒度,其次它能够在解决问题的方法上给予你更多的选择。那么其他的分解技术是否也有相应的好处呢?
1.4.1 共享库
基本上所有的语言都支持将整个代码库分解成为多个库,这是一种非常标准的分解技术。这些库可以由第三方或者自己的组织提供。
不同的团队和服务可以通过库的形式共享功能。比如说,我可能会创建一系列有用的集合操作类工具,或者一个可以重用的统计库。
团队可以围绕库来进行组织,而库本身可以被重用。但是这种方式存在一些缺点。
首先,你无法选择异构的技术。一般来讲,这些库只能在同一种语言中,或者至少在同一个平台上使用。其次,你会失去独立地对系统某一部分进行扩展的能力。再次,除非你使用的是动态链接库,否则每次当库有更新的时候,都需要重新部署整个进程,以至于无法独立地部署变更。而最糟糕的影响可能是你会缺乏一个比较明显的接缝来建立架构的安全性保护措施,从而无法确保系统的弹性。
共享库确实有其相应的应用场景。有时候你会编写代码来执行一些公共任务,这些代码并不属于任何一个业务领域,并且可以在整个组织中进行重用,很显然这些代码就应该成为可重用的库。但是你还是需要很小心,如果使用共享代码来做服务之间的通信的话,那么它会成为一个耦合点。第 4 章会再讨论该问题。
服务之间可以并且应该大量使用第三方库来重用公共代码,但有时候效果不太好。
1.4.2 模块
除了简单的库之外,有些语言提供了自己的模块分解技术。它们允许对模块进行生命周期管理,这样就可以把模块部署到运行的进程中,并且可以在不停止整个进程的前提下对某个模块进行修改。
作为一个与具体技术相关的模块分解技术,OSGI(Open Source Gateway Initiative,开放服务网关协议)值得一提。Java 本身并没有真正的模块概念,至少要到 Java 9 才能看到这个特性加入到语言中。OSGI 最初是 Eclipse Java IDE 使用的一种安装插件的方式,而现在很多项目都在使用库来对 Java 程序进行模块化。
OSGI 的问题在于它非常强调诸如模块生命周期管理之类的事情,但语言本身对此并没有足够的支持,这就迫使模块的作者做更多的工作来对模块进行适当的隔离。在一个进程内也很容易使模块之间过度耦合,从而引起各种各样的问题。我个人对 OSGI 的经验是,它带来的复杂度要远远大于它带来的好处,即使对于很优秀的团队来说也是不可避免。业界的其他同事也多有类似的看法。
Erlang 采用了不同的方式,模块的概念内嵌在 Erlang 语言的运行时中,因此这种模块化分解的方式是很成熟的。你可以对 Erlang 的模块进行停止、重启或者升级等操作,且不会引起任何问题。Erlang 甚至支持同时运行同一个模块的多个版本,从而可以支持更加优雅的模块升级。
Erlang 的模块化能力确实非常惊人,但是即使我们非常幸运地能够使用具有这种能力的平 台,还是会存在与使用共享库类似的缺点,即它会大大限制我们采用新技术和独立对服务 进行扩展的能力,并且有可能会导致使用过度耦合的集成技术,同时也会缺乏相应的接缝 来进行架构的安全性保护。
还有一个值得注意的事情是:尽管在一个单块进程中创建隔离性很好的模块是可能的,但是我很少见到真正有人能做到。这些模块会迅速和其他代码耦合在一起,从而失去意义。而进程边界的存在则能够有效地避免这种情况的发生(至少很难犯错误)。尽管我不认为这是使用进程隔离的主要原因,但是事实上确实很少有人能够在同一个进程内部做到很好的模块隔离。
除了把系统划分为不同的服务之外,你可能也想要在一个进程内部使用模块进行划分,但是仅仅使用模块划分不能解决所有的问题。如果你只使用 Erlang,可能会花很长时间才能把 Erlang 的模块化做好,但是我怀疑大部分人不会这么做。对于剩下的人来说,模块能够提供的好处与共享库比较类似。
1.5 没有银弹
在本章结束之前,我想强调一点:微服务不是免费的午餐,更不是银弹,如果你想要得到一条通用准则,那么微服务是一个错误的选择。你需要面对所有分布式系统需要面对的复杂性。尽管后面用很多的篇幅来讲解如何管理分布式系统,但它仍然是一个很难的问题。如果你过去的经验更多的是关于单块系统,那么为了得到上述那些微服务的好处,你需要在部署、测试和监控等方面做很多的工作。你还需要考虑如何扩展系统,并且保证它们的弹性。如果你发现,还需要处理类似分布式事务或者与 CAP 相关的问题,也不要感到惊讶!
每个公司、组织及系统都不一样。微服务是否适合你,或者说你能够在多大程度上采用微服务,取决于很多因素。在本书的剩余章节中我会试图给出一些指导,指出一些常见的陷阱,从而帮助你制定出清晰的演化路线。
1.6 小结
希望到目前为止你已经了解了什么是微服务、微服务与其他组合技术有何不同,以及它能够带来的主要好处又是什么。在后面的章节中,我会详细讨论如何得到这些好处及如何避免一些常见的陷阱。
用微服务,取决于很多因素。在本书的剩余章节中我会试图给出一些指导,指出一些常见的陷阱,从而帮助你制定出清晰的演化路线。
1.6 小结
希望到目前为止你已经了解了什么是微服务、微服务与其他组合技术有何不同,以及它能够带来的主要好处又是什么。在后面的章节中,我会详细讨论如何得到这些好处及如何避免一些常见的陷阱。
需要介绍的内容很多,但要从一个合适的点开始。架构师承担了驱动系统演化的职责,而引入微服务之后的一个主要挑战就是,架构师职责的相应变化。下一章会讲到有哪些方法可以保证我们从这个新架构中获益。