为什么选择微服务
我无法确定你们公司的目标。你们自己比我更了解你们公司的目标和你们目前正在面临的挑战。我能给出的是:为什么全世界的公司都采用微服务架构的原因。本着诚实的精神,我还会给出可以实现相同的结果的、微服务架构之外的其他方法。
提升团队自治
无论从事什么行业,都要认识到员工的重要性,并吸引他们正确地做事,为他们提供自信、动力、自由和渴望,以挖掘出他们真正的潜力。
——John Timpson(英国Timpson公司老板)
很多公司都已经获得创建自治团队的好处。保持小规模的团队,可以避免过多的官僚主义,从而使得团队成员之间建立密切的联系,并能一起高效工作。小团队也帮助很多公司在同行之中获得更有效的发展和扩张。在戈尔公司(W. L. Gore & Associates),所有的业务部门都不会超过150名员工,从而确保部门内的每个人都彼此认识。利用这种小团队的组织方式,戈尔公司取得了巨大的成功。为了使得这些较小规模的业务部门可以正常工作,必须赋予小团队权力和责任,以使其能够独立运作。
Timpsons——一家非常成功的英国零售商,已经通过增强全体员工能力、减少对公司总部的需求、允许本地商店自行做出决定、赋予员工可以给予不满意的顾客多少折扣的权利等方式实现了公司的大规模经营。公司的现任董事长John Timpson也以取消内部规则并将其替换为仅有的两条如下规则而闻名:
- Look the part and put the money in the till.
- You can do anything else to best serve customers.
自治也需要在较小的团队规模下才能成为可能。我合作过的大多数现代化公司都希望在其内部创建更多的自治团队,他们经常尝试复制其他公司的团队自治模式,例如:亚马逊的两个披萨(two-pizza)模式或Spotify模式1。
如果一切安好,团队自治就能够给予员工授权,并帮助员工成长以更快地完成工作。当团队拥有微服务并完全控制这些服务时,这些团队就提升了他们可以在公司中的自治程度。
还可以怎么做
可以通过多种方式实现团队自治(职责分配)。将更多职责下放到团队的方法并不需要改变架构。从本质上讲,这是一个确定哪些职责可以下放到团队的过程,并且可以通过多种方式来实现职责的分配。一种方案就是将代码库的部分权限授予不同的团队(该方案同样可以让模块化的单体应用从中受益):也可以将代码库按照功能划分为不同部分,并为不同部分确定有决策权限的人员以实现职责的分配(例如,Ryan最了解广告展示,因此由Ryan来负责广告展示的部分;Jane最了解查询的性能调优,因此涉及查询性能的任何工作都必须首先经过Jane)。
提高自治度会让事情变的简单,我们无需等待其他人的响应就可以完成工作。自助式服务避免了那些日复一日的、需要运维团队到场才能完成的工作。因此,采用自助服务的方式来配置机器或环境的方案是一种巨大的推动力。
缩短产品上市的时间
具备如下的能力时,我们就可以更快地向我们的客户发布新的功能:
- 对单个微服务进行变更并对变更进行部署
- 在部署单个微服务的变更时无需依赖其他服务的发布
投入更多的人来解决问题也是一种方案——这种方案我们稍后就会讨论。
还可以怎么做
在考虑如何更快地发布软件时,会有很多变量会影响软件的发布速度。那么,我们从哪个因素开始执行呢?我总是建议执行某种生产路径模型(path-to-production modeling)的演练,这种演练可能有助于发现那些意想不到的最严重的发布瓶颈。
我想起了多年以前,一个大型投资银行的一个项目。我们受邀来帮助他们提升软件的交付速度。我们收到如下的信息:“开发人员花费太长时间才能将产品部署到生产环境!” Kief Morris——我的一位同事——花了一些时间梳理出软件交付所涉及到的所有阶段,然后研究从产品负责人(PO: product owner)译注1提出某个想法到该想法真正在产品中实现的整个过程。
Kief Morris很快就发现:从开发人员开始工作到将其部署到生产环境,平均需要大概6周的时间。由于整个过程涉及到手工操作,因此我们认为可以利用适当的自动化手段将整个过程减少几周。但是,Kief在生产路径模型中发现了一个更大的问题——从产品负责人译注1提出想法到开发人员开始实现该想法,通常会耗时40周。通过专注于后者的流程改进,我们帮助客户更大地缩短了新功能的上市时间。
因此,在解决如何快速发软件时,需要考虑软件发布所涉及到的所有阶段。关注整个过程需要多长时间,每个阶段的持续时间(包括该阶段的总时间以及真正用于该阶段的时间译注2),并标注出整个过程的痛点。毕竟,微服务可能是解决软件快速发布的部分方案,可以并行尝试很多其他的方案。
有效的扩容
把我们的服务拆分为相互独立的微服务,就可以单独扩容拆分后的微服务。这意味着我们也能以此进行有效的扩容——只需要扩容那些当前会限制系统负载能力的微服务即可。同样的,对于那些负载较小的微服务,我们可以对其缩容,甚至在不需要时将其关闭。这就是为什么这么多构建SaaS产品的公司会采用微服务架构的原因——微服务让这些公司能够更好地控制运营成本。
还可以怎么做
对于扩容,有大量的替代方案可供我们选择,在决定使用微服务架构之前,可以更容易的对其中的大多数方案进行试验。可以从获取更强性能的机器(bigger box)开始,如果使用公有云或其他类型的虚拟化平台,则可以通过简单地配置来让程序运行在具备更高性能的机器上。显然,这种“垂直扩展”译注3有其局限性,但是对于负载的短期快速优化而言,该方案也可以处于考虑范围之内。
对于当前的单体应用而言,传统的水平扩展译注4——运行多个单体应用的副本——的有效性已经得到证实。通过某种负载分配机制(例如负载均衡或者队列),运行单体应用的多个副本可以轻松处理更多负载。然而,如果系统的负载瓶颈是数据库,则水平扩展则可能无济于事,当然,这也取决于采用的数据库是否支持水平扩展。可以快速评估出水平扩展的有效性。同时,水平扩展的缺点比成熟的微服务架构少得多。再者,水平扩展也比较简单。因此,在采用微服务之前,真的应该放手一试。
还可以采用能够更好地解决负载技术的其他替代方案。但是,这通常不是一件容易的事——考虑一下把当前的程序迁移到新型的数据库或编程语言的工作。对于微服务架构,当需要改变技术时,只需在那些使用该技术的微服务内部改变技术即可,而其他的微服务则保持不变,从而可以减少技术更改的影响。因此,实际上,微服务架构可以让改变技术变得更加容易。
提高系统鲁棒性
从单租户(single-tenant)译注5软件到多租户(multi-tenant)SaaS译注6应用程序的转变意味着系统宕机会更普遍。客户对软件可用性的期望以及软件在客户生活中的重要性都在增加。通过把应用程序分解为单独的、可独立部署的程序,我们提供了许多机制来提高应用程序的健壮性。
利用微服务架构,系统的功能得到了拆解,因此,我们可以实现更健壮的架构。也就是说,系统中部分功能的异常不会导致整个系统宕机。我们还能将时间和精力集中在系统中对鲁棒性要求最高的服务,以确保系统的关键部分保持万无一失。
弹性与鲁邦性(Resilience Versus Robustness)我们经常在如下的场景时讨论弹性:
- 提高系统的能力从而避免系统宕机
- 在发生故障时优雅地处理故障
- 在问题发生时迅速恢复服务
如今,在称为弹性工程学的领域中已经做了很多工作。弹性工程不仅涉及计算机领域,而是将其应用到的所有领域作为一个整体来研究。David Woods——弹性模型的开拓者——从更广泛的角度来看待弹性的概念,并指出弹性并非像我们之初想象的那样简单的事实,其中,弹性要求我们对已知故障源和未知故障源的处理能力进行划分。2
John Allspaw——David Woods的同事——帮助我们来区分鲁棒性和弹性。鲁棒性是系统对预期变化做出反应的能力。弹性是指组织应对那些未曾预料到的事情的能力,这也包括:通过类似混沌工程的方法而建立的一种实验文化。例如,我们知道有一台特定的机器可能会死机,因此我们通过对实例进行负载均衡为系统带来冗余。这就是提升鲁棒性的一个例子。组织不可能预见到所有的潜在问题,弹性即是组织为这些无法预见到的问题而做好准备的过程。
特别需要注意的是:微服务不一定会免费提升系统的鲁棒性。相反,微服务为设计如下的系统提供机会:具备更好地网络分区容错能力,服务宕机容错能力……
仅仅把功能分散到多个独立的程序和独立的机器上不仅不能保证提升鲁棒性——恰恰相反——可能会扩大系统故障的范围。
还可以怎么做
通过某种负载均衡器或其他的类似队列3的负载分配机制运行单体应用的多个副本,从而增加系统冗余。我们可以通过在多个故障平面上部署单体应用的实例来进一步提高应用的鲁棒性(例如,不要把所有机器都放在同一机架或同一数据中心)。
对更可靠的硬件和软件的投资同样可以产生收益,因此,可以对系统宕机的现有原因进行彻底的排查。我见过很多的线上事故,这些事故因为过度依赖手工操作或人们“不遵守规范”而引起,这通常意味着个人的无辜错误可能会产生重大影响。2017年,5月27日,英国航空公司发生重大的“IT系统故障”,导致进出伦敦希思罗机场和盖特威克机场的所有航班都被取消。此次故障是由单个数据中心的电源意外关闭而无意触发。如果应用程序的鲁棒性依赖于人类永远不会犯错,那么我们将一路艰难。
扩大开发人员数量
我们可能都已经意识到了这个问题:投入大量开发人员到项目中以试图加速项目进展的做法常常适得其反。但是有些问题确实需要更多的人才能完成。正如Frederick Brooks在其极具影响力的著作《人月神话》(The Mythical Man Month)4中所概述的那样:只有能够把工作划分为独立的不同部分,并且划分之后的工作之间的交互非常有限,增加更多的人才会继续提高交付速度。Frederick Brooks以田间收割庄稼为例:对于收割庄稼而言,让多个人并行工作是一件简单的事情,此时每个人的工作都不需要与其他人进行互动。软件开发却很少像收割庄稼般工作。在软件开发时,每个人完成的工作并不完全相同,一个人的工作输出经常会是其他人的输入。
有了清晰定义的边界,以及主要关注在确保限制微服务之间的耦合的架构,我们可以抽取出可独立开发的代码段。因此,我们希望通过减少交付冲突扩大开发人员的数量。
为了成功扩大解决问题的开发人员的数量,团队之间必须具备高度的自治。仅仅拥有微服务是不够的,还必须思考:团队和服务权限之间如何保持一致,团队之间需要协调什么。我们还需要把工作进行拆解,从而使得变更不需要经过太多微服务之间的协调。
还可以怎么做
由于微服务本身是没有耦合的、可以独立处理的服务,因此微服务对于较大的团队而言有很好的效果。模块化的单体应用是另一种替代方案:不同的团队拥有各自的模块,只要模块之间交互的接口保持稳定,团队之间就可以继续独立执行变更。
但是,模块化的单体应用的方案有些局限。不同的团队之间仍然存在某种形式的冲突。对于该方案而言,软件仍然需要把全部模块打包在一起,因此部署工作仍然需要相应各方之间的协调。
拥抱新的技术
单体应用通常会限制我们的技术选择。通常,我们在后端使用一种编程语言。我们固化到一种部署平台,一种操作系统和一种数据库。通过微服务架构,我们有机会为每个服务选择不同的技术。
通过将技术变更隔离在一个服务边界内,我们可以在隔离的服务中了解新技术的优势,并在新技术出现问题时限制其影响。
以我的经验,虽然成熟的微服务组织通常会限制他们支持的技术栈数量,但微服务之间所使用的技术很少是同质的。能够以安全的方式尝试新技术的灵活性可以为组织提供竞争优势:既可以为客户提供更好的结果,也可以帮助开发人员因为掌握新技能而保持快乐。
还可以怎么做
如果我们仍然继续将软件作为一个单独的程序来交付,那么我们可以引入的技术就会受到限制。当然,我们可以在同一运行时(runtime)安全地采用新语言——作为一个例子,JVM可以在相同的进程中托管多种语言编写的代码。新的数据库类型会出现更多问题,因为这意味着需要对先前的整个数据模型进行某种拆解以允许其增量迁移。除非我们打算立即完全地升级到新的数据库技术,否则更新数据库技术是一种复杂且有风险的行为。
如果当前的技术堆栈被认为是“burning platform”,那么别无选择,只能用更新更好的技术栈来替换当前的技术栈5。当然,没有什么可以阻止我们用新的应用逐步替换现有应用。第3章介绍的绞杀者模式可以很好地实现这一目标。
复用?复用是微服务迁移中最常提及的目标之一,而在我看来,复用却不是首先应该考虑的目标。就其本质而言,复用并不是人们想要的直接结果。人们希望复用可以带来其他好处。我们希望通过复用更快地发布新功能,或者降低成本。但是,如果我们的目标是这些事情,那请跟踪这些目标,否则最终会做了错误的优化。
为了解释我的意思,让我们更深入地研究选择复用作为目标的常见原因之一。我们希望通过复用更快地发布功能。我们认为,通过复用现有的代码来优化我们的开发流程,此时不必编写过多的代码——只需要更少的工作,就可以更快地发布软件。
是这样吗?
让我们来看一个简单的例子。Music Corp的客服团队需要格式化PDF文件才能给顾客提供发票。该系统的其他部分已经在处理PDF文件的生成:我们在仓库中生成用于打印的PDF,为运送给客户的订单生成装箱单,并给供应商发送订单请求。
按照复用的目标,团队可能会直接使用现有的PDF生成功能。但是目前,该功能由组织中的不同部门的不同团队管理。因此,我们现在必须与他们协作,并让他们进行必要的更改以支持我们的需求。这意味着我们不得不要求他们为我们而工作,或者我们不得不自己修改代码并提交pull request(假设公司是这样的工作模式)。无论哪种方式,我们都必须与另外的团队协作才能进行更改。
我们会花时间与他人协作并完成更改,然后就可以实现功能更改。但是,我们发现,与花费时间修改现有代码相比,实际上可以更快地编写自己的实现以更快的将功能交付给客户。如果实际目标是缩短产品上市时间,那么这可能是正确的选择。但是,如果针对复用进行了优化,并希望可以更快地将产品推向市场,那么我们最终做的是导致我们放慢脚步的事情。
对于复杂系统而言,度量复用性非常困难。正如我所描述的那样,通常我们会为实现其他目标而做某些事情。我们需要花时间专注于实际目标,并认识到复用可能并不总是正确的方案。
1. Which famously even Spotify doesn’t use anymore. ↩
2. See David Woods, “Four Concepts for Resilience and the Implications for the Future of Resilience Engineering.” Reliability Engineering & System Safety 141 (2015) 5–9. ↩
3. See the competing consumer pattern for one such example, in Enterprise Integration Patterns by Gregor Hohpe and Bobby Woolf, page 502. ↩
4. See Frederick P. Brooks, The Mythical Man-Month, 20th Anniversary Edition (Boston: Addison-Wesley, 1995). ↩
5. The term “burning platform” is typically used to denote a technology that is considered end-of-life. It may be too hard or expensive to get support for the technology, too difficult to hire people with the relevant experi‐ ence. A common example of a technology most organizations consider a burning platform is a COBOL main‐ frame application. ↩
译注1. product owner, 在Scrum敏捷开发中有三种主要的角色:Product Owner(产品负责人,简称"PO"), Scrum Master(敏捷教练),Team(团队)。产品负责人是有授权的产品领导力核心,担任的是产品经理的角色。 ↩
译注2. elapsed time and busy time. ↩
译注3. 垂直扩展: 通过优化系统中的现有的模块的处理能力来提高系统负载。 ↩
译注4. 水平扩展: 不是通过优化系统中模块的负载,而是简单的通过增加更多的模块来提高系统负载。 ↩
译注5. 单租户: 指的是为每个客户单独创建各自的软件应用和支撑环境。每个客户都有一份分别放在独立的服务器上的数据库和操作系统,或者使用强的安全措施进行隔离的虚拟网络环境中。 ↩
译注6. 多租户: 简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性。 ↩