架构师书库
点击查看第二章
点击查看第三章
微服务架构设计模式
Microservices Patterns:With Examples in Java
[美] 克里斯·理查森(Chris Richardson) 著
喻勇 译
第1章
逃离单体地狱
本章导读
- 单体地狱的特征,如何借助微服务架构逃离单体地狱
- 微服务架构的基本特征,它的好处和弊端
- 开发大型复杂应用时,如何借助微服务实现DevOps式开发风格
- 微服务架构的模式语言及为什么使用它
周一的上午还没过完,Food to Go(以下简称为FTGO)公司的首席技术官玛丽就已经开始抓狂。其实她这天早上的时候还是斗志昂扬的,因为上周她刚带领公司的架构师和程序员参加了一场不错的技术会议,学习了像持续部署和微服务架构这样的最新软件开发技术。玛丽还遇见了她在北卡罗来纳A&T州立大学计算机系的老同学,并与他们分享了作为技术领导者的各种实战故事。她感觉颇有收获,并打算用这些新技术来改进FTGO当前的软件开发方式。
不幸的是,这种斗志昂扬的感觉在一场资深工程师和业务人员之间的扯皮会议后荡然无存。这群人坐在一起,花了两个小时讨论为何研发团队要再一次错过产品的关键交付期限。更加不幸的是,这类会议在过去的几年内几乎是家常便饭。尽管公司已经用上了敏捷这一套东西,可是研发的步调仍旧快不起来,这已经严重地拖了业务发展的后腿。令人绝望的是,这样的问题似乎找不到一个很好的解决办法。
玛丽意识到,FTGO的现状跟她参加的技术会议中提到的“单体地狱”不谋而合,而解决之道,是采用微服务架构来重构当前的系统。然而,微服务架构和随之而来的一大套“时髦”的软件开发实践,对玛丽来说有些遥不可及。对于同时解决眼前的各种问题和系统性地采纳微服务架构,玛丽一筹莫展。
幸好,本书会给玛丽提供了一个可行的方案。但是在开始之前,我们先来看看FTGO眼前的问题,以及他们是如何一步步陷入泥潭的。
1.1 迈向单体地狱的漫长旅程
自从2005年末创立以来,FTGO的业务一直突飞猛进。目前,它已经成为全美领先的在线餐饮速递企业。FTGO一直计划进行海外业务扩展,然而,前进的步伐却被软件层面的种种交付延迟而拖慢。
FTGO的核心业务其实非常简单。消费者(Consumer)使用FTGO的网站或者移动应用在本地的餐馆(Restaurant)下订单,FTGO会协调一个由送餐员(Courier)组成的快递网络来完成订单食品(Order)的运送(Delivery)。显然,给送餐员和餐馆支付费用(Payment)也是FTGO的重要任务之一。餐馆使用FTGO的网站编辑菜单并管理订单。这套应用程序使用了多个Web服务,例如使用Stripe管理支付、使用Twilio实现消息传递、使用Amazon SES (Simple Email Service)发送电子邮件,等等。
与其他陈旧的企业应用程序一样,FTGO的应用程序是一个单体,它由一个单一的Java WAR(Web Application Archive)文件构成。随着时间的推移,这个文件变成了一个庞大的、复杂的应用程序。尽管FTGO开发团队做出了最大的努力,但这个应用程序已成为“泥球模式”(the Big Ball of Mud Pattern, www.laputan.org/mud/
)的一个典型例子。泥球模式的作者Brian Foote和Joseph Yoder把这样的软件比喻为“随意架构的、庞大的、草率的、布满了胶带和线路,如同意大利面条一般的代码丛林”。软件交付的步伐已经放缓。更糟糕的是,FTGO应用程序是使用一些日益过时的框架编写的。FTGO应用程序展示了单体地狱的几乎所有症状。
我会在下一节介绍FTGO应用程序的架构,你会明白为什么这样的架构在一开始可以正常工作。我会接着介绍FTGO应用程序架构的演变和膨胀,以及它是如何逐渐步入单体地狱的。
1.1.1 FTGO应用程序的架构
FTGO应用程序是一个典型的分层模块化企业级Java应用。图1-1展示了它的架构。FTGO应用程序拥有一个六边形的架构,我们会在第2章中详细介绍这类架构风格。在这个六边形中,应用程序的核心是业务逻辑组件。在业务逻辑外围是各种用来实现用户界面和与外部服务集成的适配器。
业务逻辑由包含了服务和领域对象的模块组成,一些典型的模块包括Order Management、Delivery Management、Billing和Payments。若干适配器用来完成与外部系统的对接工作,一些是入站(inbound)适配器,它通过调用业务逻辑来处理各类请求,包括REST API和Web用户界面适配器。其他是出站(outbound)适配器,它使业务逻辑能够访问MySQL数据库并调用Twilio和Stripe等云服务。
尽管逻辑上FTGO是一个模块化的架构,这个应用还是被整体打包成一个单一的WAR文件,部署运行在Tomcat之上。这是一个非常典型并且被广泛应用的单体软件架构风格:一个系统被作为单一的单元打包和部署。如果FTGO应用是采用GoLang语言编写的,那它的交付形态就是一个单一的可执行文件;如果是用Ruby或者Node.js开发的,那它的交付形态就是一个目录和它之下的子目录中包含的各种源代码。单体架构本身并不存在问题, FTGO开发人员在选择单体架构时为他们的应用程序架构做出了一个很好的决定。
1.1.2 单体架构的好处
在FTGO发展的早期,应用程序相对较小,单体架构具有以下好处。
- 应用的开发很简单:IDE和其他开发工具只需要构建这一个单独的应用程序。
- 易于对应用程序进行大规模的更改:可以更改代码和数据库模式,然后构建和部署。
- 测试相对简单直观:开发者只需要写几个端到端的测试,启动应用程序,调用REST API,然后使用Selenium这样的工具测试用户界面。
- 部署简单明了:开发者唯一需要做的,就是把WAR文件复制到安装了Tomcat的服务器上。
- 横向扩展不费吹灰之力:FTGO可以运行多个实例,由一个负载均衡器进行调度。
但是,随着时间的推移,开发、测试、部署和扩展都会变得更加困难。我们来看看为什么。
1.1.3 什么是单体地狱
不幸的是,正如FTGO的开发人员已经意识到的,单体架构存在着巨大的局限性。类似FTGO这样渴求成功的应用程序,往往都不断地在单体架构的基础之上扩展。每一次开发冲刺(Sprint),FTGO的开发团队就会实现更多的功能,显然这会导致代码库膨胀。而且,随着公司的成功,研发团队的规模不断壮大。代码库规模变大的同时,团队的管理成本也不断提高。
如图1-2所示,那个曾经小巧的、简单的、由一个小团队开发维护的FTGO应用程序,经过10年的成长,已经演变成一个由大团队开发的巨无霸单体应用程序。同样,小型开发团队现在已成为多个所谓Scrum敏捷团队,每个团队都在特定的功能领域工作。作为架构扩展的结果,FTGO已经陷入了单体地狱。开发变得缓慢和痛苦。敏捷开发和部署已经不可能。我们来看看这是为什么。
过度的复杂性会吓退开发者
FTGO应用程序的首要问题是它的过度复杂性。这个系统本身过于庞大和复杂,以至于任何一个开发者都很难理解它的全部。因此,修复软件中的问题和正确地实现新功能就变得困难且耗时。各种交付截止时间都可能被错过。
更糟糕的是,这种极度的复杂性正在形成一个恶性循环:由于代码库太难于理解,因此开发人员在更改时更容易出错,每一次更改都会让代码库变得更复杂、更难懂。之前图1-1所示的干净、模块化架构并不能反映现实世界的真实情况。真实情况是FTGO应用正在一步一步地成为一个巨大的、令人费解的“脏泥球”。
玛丽记得她曾经在某个技术会议上遇到一个极客,他开发了一个分析数以千计JAR文件和数百万行代码之间依赖关系的工具,那时候玛丽觉得这个工具也许能帮助FTGO厘清头绪。现在她并不这么想。玛丽认为更好的方法是迁移到更适合复杂应用程序的架构风格:微服务架构。
开发速度缓慢
因为要跟这些极度复杂的系统打交道,FTGO的开发人员发现他们日常的开发工作变慢了。这个巨大的项目把开发人员的IDE工具搞得很慢,构建一次FTGO应用需要很长时间,更要命的是,因为应用太大,每启动一次都需要很长的时间。因此,从编辑到构建、运行再到测试这个周期花费的时间越来越长,这严重地影响了团队的工作效率。
从代码提交到实际部署的周期很长,而且容易出问题
另一个困扰FTGO应用团队的问题是:把程序更改部署到生产环境的时间变得更长,整个流程几乎令人抓狂。目前团队每月对生产环境进行一次更新部署,通常都是在周五或者周六的晚上。玛丽总是读到一些关于SaaS应用持续部署的“黑科技”:每天都可以在业务时间对应用进行多次修改并快速完成部署。显然,对于像Amazon.com这样的公司,2011年就已经能够做到每11.6秒完成一次变更到生产环境的部署。一个月完成几次更新,对FTGO开发人员来说简直就是神话。实现持续部署更是遥不可及的梦想。
FTGO的敏捷实践是不完整的。工程团队被分成各个小队(squad),以两周为一个冲刺周期。不幸的是,从代码完成到运行在生产环境是一个漫长且费力的过程。一个问题是,众多开发人员都向同一个代码库提交代码更改,这常常使得这个代码库的构建结果处于无法交付的状态。当FTGO尝试采用功能分支来解决这个问题时,带来的是漫长且痛苦的合并过程。紧接着,一旦团队完成一个冲刺任务,随后迎接他们的将是一个漫长的测试和代码稳定周期。
把更改推向生产环境的另一个挑战是运行测试需要很长时间。因为代码库如此复杂,以至于一个更改可能引起的影响是未知的,为了避免牵一发而动全身的后果,即使是一个微小的更改,开发人员也必须在持续集成服务器上运行所有的测试套件。系统的某些部分甚至还需要手工测试。如果测试失败,诊断和修复也需要更多的时间。因此,完成这样的测试往往需要数天甚至更长时间。
难以扩展
FTGO团队在对应用进行横向扩展时也遇到了挑战。因为在有些情况下,应用的不同模块对资源的需求是相互冲突的。例如,餐馆数据保存在一个大型的内存数据库中,理想情况下运行这个应用的服务器应该有较大容量的内存。另外,图片处理模块又需要比较快的CPU来完成图形运算,这需要应用部署在具有多个高性能CPU的服务器之上。因为这些模块都是在一个应用程序内,因此FTGO在选用服务器时必须满足所有模块的需要。
交付可靠的单体应用是一项挑战
FTGO应用的另一个问题是缺乏可靠性,这个问题导致了频繁的系统故障和宕机。系统不可靠的一个原因是应用程序体积庞大而无法进行全面和彻底的测试。缺乏可靠的测试意味着代码中的错误会进入生产环境。更糟糕的是,该应用程序缺乏故障隔离,因为所有模块都在同一个进程中运行。每隔一段时间,在一个模块中的代码错误,例如内存泄漏,将会导致应用程序的所有实例都崩溃。 FTGO开发人员不喜欢在半夜因为生产环境的故障而被叫醒。业务人员也对由此造成的收入损失和丧失客户信任而头疼不已。
需要长期依赖某个可能已经过时的技术栈
FTGO所经历的单体地狱的最终表现,也体现在团队必须长期使用一套相同的技术栈方面。单体架构使得采用新的框架和编程语言变得极其困难。在单体应用上采用新技术或者尝试新技术都是极其昂贵和高风险的,因为这个应用必须被彻底重写。结果就是,开发者被困在了他们一开始选择的这个技术之内。有时候这也就意味着,团队必须维护一个正在被废弃或过时的技术所开发的应用程序。
Spring框架本身保持着持续的演进和更新,同时维持着向后的兼容性。所以理论上来说FTGO可以随着升级。不幸的是,FTGO的应用程序使用了与Spring新版本不兼容的框架,开发团队也挤不出时间来更新这些旧框架。久而久之,这个应用的绝大部分都被卷入了这个已经过时的框架。更不幸的是,FTGO的开发人员一直想尝试类似GoLang和Node.js这样的非JVM类编程语言,然而在单体架构之下,这是做不到的。
1.2 为什么本书与你有关
如果你正在翻看这本书,那么你很可能是软件开发人员、架构师、CTO或工程研发的副总裁。你负责的应用程序已超出其单体架构所能够支撑的范围,就像FTGO的玛丽一样,你正在努力应对软件交付,并想知道如何逃避单体地狱。或许你担心你的组织正在走向单体地狱之路,你想知道如何在为时已晚之前改变方向。如果你需要让手中的软件项目避免陷入单体地狱,那么这本书就是为你所写。
本书花了很多时间来解释微服务架构的概念。无论你现在使用何种技术栈,我的目标都是让你可以轻松地读懂这本书。你所需要的只是熟悉企业应用程序架构设计的基础知识。特别是,你需要了解以下内容:
- 三层架构。
- Web应用程序设计。
- 使用面向对象设计来开发业务逻辑。
- 关系型数据库:SQL和ACID事务的概念。
- 使用消息代理和REST API进行进程间通信。
- 安全,包括身份验证和访问授权。
本书中的代码示例是使用Java和Spring框架编写的。这意味着为了充分利用这些示例,你还需要熟悉Spring框架。
1.3 你会在本书中学到什么
读完这本书后,你会理解和掌握如下知识:
- 微服务架构的基本特点,它的好处和弊端,以及应该在什么情况下使用微服务架构。
- 分布式数据管理的架构模式。
- 针对微服务架构应用程序的有效测试策略。
- 微服务架构应用程序的部署方式。
- 把单体应用重构为微服务架构的策略。
你也会掌握如下技术:
- 使用微服务的架构模式来设计应用程序的架构。
- 为服务开发业务逻辑。
- 使用Saga在进程间维护数据的一致性。
- 实现跨服务的数据查询。
- 更高效地测试微服务架构应用程序。
- 开发生产环境就绪的应用程序,实现安全性、可配置性和可观测性。
- 把现有的单体应用重构为服务。
1.4 拯救之道:微服务架构
玛丽意识到,FTGO应用程序必须迁移为微服务架构。
有趣的是,软件架构其实对功能性需求影响并不大。事实上,在任何架构甚至是一团糟的架构之上,你都可以实现一组用例(应用的功能性需求)。因此,即使是成功的应用程序(例如FTGO),其内部架构也往往是一个大泥球。
架构的重要性在于它影响了应用的非功能性需求,也称为质量属性或者其他的能力(-ilities)。随着FTGO应用的增长,各种质量属性和问题都浮出水面,最显著的就是影响软件交付速度的可维护性、可扩展性和可测试性。
一方面,训练有素的团队可以减缓项目陷入单体地狱的速度。团队成员可以努力维护他们的模块化应用。他们也可以编写全面的自动化测试。但是另一方面,他们无法避免大型团队在单体应用程序上协同工作的问题,也不能解决日益过时的技术栈问题。团队所能做的就是延缓项目陷入单体地狱的速度,但这是不可避免的。为了逃避单体地狱,他们必须迁移到新架构:微服务架构。
今天,针对大型复杂应用的开发,越来越多的共识趋向于考虑使用微服务架构。但微服务到底是什么?不幸的是,微服务这个叫法本身暗示和强调了尺寸。针对微服务架构有多种定义。有些仅仅是在字面意义上做了定义:服务应该是微小的不超过100行代码,等等。另外有些定义要求服务的开发周期必须被限制在两周之内。曾在Netflix工作的著名架构师Adrian Cockcroft把微服务架构定义为面向服务的架构,它们由松耦合和具有边界上下文的元素组成。这个定义不错,但仍旧有些复杂难懂。我们来尝试一个更好的定义。
1.4.1 扩展立方体和服务
我对微服务架构的定义受到了Martin Abbott和Michael Fisher的名著《The Art of Scalability》的启发。这本书描述了一个非常有用的三维可扩展模型:扩展立方体,如图1-3所示。
这个模型描述了扩展一个应用程序的三种维度:X、Y和Z。
X轴扩展:在多个实例之间实现请求的负载均衡
X轴扩展是扩展单体应用程序的常用方法。图1-4展示了X轴扩展的工作原理。在负载均衡器之后运行应用程序的多个实例。负载均衡器在N个相同的实例之间分配请求。这是提高应用程序吞吐量和可用性的好方法。
Z轴扩展:根据请求的属性路由请求
Z轴扩展也需要运行单体应用程序的多个实例,但不同于X轴扩展,每个实例仅负责数据的一个子集。图1-5展示了Z轴扩展的工作原理。置于前端的路由器使用请求中的特定属性将请求路由到适当的实例。例如,应用程序可能会使用请求中包含的userId来路由请求。
在这个例子中,每个应用程序实例负责一部分用户。该路由器使用请求Authorization头部指定的userId来从N个相同的应用程序实例中选择一个。对于应用程序需要处理增加的事务和数据量时,Z轴扩展是一种很好的扩展方式。
Y轴扩展:根据功能把应用拆分为服务
X轴和Z轴扩展有效地提升了应用的吞吐量和可用性,然而这两种方式都没有解决日益增长的开发问题和应用复杂性。为了解决这些问题,我们需要采用Y轴扩展,也就是功能性分解。Y轴扩展把一个单体应用分成了一组服务,如图1-6所示。
服务本质上是一个麻雀虽小但五脏俱全的应用程序,它实现了一组相关的功能,例如订单管理、客户管理等。服务可以在需要的时候借助X轴或Z轴方式进行扩展。例如,订单服务可以被部署为一组负载均衡的服务实例。
我对微服务架构的概括性定义是:把应用程序功能性分解为一组服务的架构风格。请注意这个定义中并没有包含任何与规模有关的内容。重要的是,每一个服务都是由一组专注的、内聚的功能职责组成。我们稍后会详细讨论。
目前而言,我们先来看看为什么微服务架构是模块化的一种形式。
1.4.2 微服务架构作为模块化的一种形式
模块化是开发大型、复杂应用程序的基础。类似FTGO这样的现代应用程序规模太大,很难作为一个整体开发,也很难让一个人完全理解。为了让不同的人开发和理解,大型应用需要拆分为模块。在单体应用中,模块通常由一组编程语言所提供的结构(例如Java的包),或者Java JAR文件这样的构建制品(artifact)来定义。然而,正如FTGO开发者所认识到的,这类实践往往会出现问题,随着时间的推移和反复的开发迭代,单体应用往往会蜕变成一个大泥球。
微服务架构使用服务作为模块化的单元。服务的API为它自身构筑了一个不可逾越的边界,你无法越过API去访问服务内部的类,这与采用Java包的单体应用完全不同。因此模块化的服务更容易随着时间推移而不断演化。微服务架构也带来其他的好处,例如服务可以独立进行部署和扩展。
1.4.3 每个服务都拥有自己的数据库
微服务架构的一个关键特性是每一个服务之间都是松耦合的,它们仅通过API进行通信。实现这种松耦合的方式之一,是每个服务都拥有自己的私有数据库。对于一个线上商店来说,Order Service拥有一个包括ORDERS表的数据库,Customer Service服务拥有一个包含CUSTOMERS表的数据库。在开发阶段,开发者可以修改自己服务的数据库模式,而不必同其他服务的开发者协调。在运行时,服务实现了相互之间的独立。服务不会因为其他的服务锁住了数据库而进入堵塞的状态。
别担心:松耦合不会让Larry Ellison挣更多钱
每一个服务拥有它自己数据库的需求并不意味着每个服务都需要一个独立的数据库服务器。这也就意味着你不用花10倍或者更多的钱购买Oracle数据库的许可。在第2章我们会深入探讨这个主题。
现在我们已经给出了微服务架构的定义,并且描述了它的一些基本特性,让我们来看看这些如何应用于FTGO应用程序。
1.4.4 FTGO的微服务架构
本书的其余部分将深入讨论FTGO应用程序的微服务架构。但首先让我们快速了解将Y轴扩展应用于此应用程序的含义。如果我们将Y轴分解应用于FTGO应用程序,我们将获得如图1-7所示的架构。分解后的应用程序包含许多前端和后端服务。我们还将应用X轴和可能的Z轴扩展,以便在运行时每个服务都有多个实例。
前端服务包括API Gateway和餐馆的Web用户界面(Restaurant Web UI)。API Gateway扮演了一个对外的角色,它提供了供消费者和快递员的移动应用程序使用的REST API,第8章将详细介绍这部分内容。餐馆的网页界面实现了餐馆用来管理菜单和订单流程的Web用户界面。
FTGO应用程序的业务逻辑由众多后端服务组成。每个后端服务都有一个REST API和它自己的私有数据库。后端服务包括以下内容:
- Order Service:管理订单。
- Delivery Service:管理从餐馆到客户之间的订单派送(送餐)。
- Restaurant Service:维护餐馆有关的信息。
- Kitchen Service:管理订单的准备过程。
- Accounting Service:处理账单和付款。
许多服务都与本章前面介绍的FTGO单体应用中的模块一一对应。不同的是,每个服务及其API都有非常清晰的定义。每个都可以独立开发、测试、部署和扩展。此外,该架构在保持模块化方面做得很好。开发人员无法绕过服务的API并访问其内部组件。我将在第13章介绍如何将现有的单体应用程序转换为微服务架构。
1.4.5 微服务架构与SOA的异同
某些针对微服务架构的批评声称它其实就是SOA,并没有新鲜的内容。在某些层面,它们的确有些相似。SOA和微服务架构都是特定的架构风格,它们都以一系列服务的方式来把一个系统组织在一起。但如果深入研究,你就会发现微服务和SOA之间巨大的差异,如
表1-1所示。
SOA和微服务架构通常采用完全不同的技术栈。SOA应用常常选用重量级的技术,例如SOAP和其他类似的WS*标准。SOA常常使用ESB进行服务集成,ESB是包含了业务和消息处理逻辑的智能管道。采用微服务架构设计的应用程序倾向于使用轻量级、开源的技术。服务之间往往采用哑管道(例如消息代理)进行通信,使用类似REST或gRPC这类轻量级协议。
SOA和微服务架构在处理数据的方式上也不尽相同。SOA应用一般都有一个全局的数据模型,并且共享数据库。与之相反,如之前提到的,微服务架构中每个服务都有属于它自己的数据库。更进一步,我们在第2章会提到,每一个服务一般都拥有属于它自己的领域模型。
SOA和微服务架构之间的另一个重要区别,就是服务的尺寸(规模)。SOA善于集成大型、复杂的单体应用程序。微服务架构中的服务虽然不是必须要做到很小,但是通常都比较小。因此SOA应用通常包含和集成若干个大型的服务,微服务架构的应用则常常由数十甚至上百个更小的服务组成。
1.5 微服务架构的好处和弊端
我们首先来审视微服务架构的好处,然后再分析它的弊端。
1.5.1 微服务架构的好处
微服务架构有如下好处:
- 使大型的复杂应用程序可以持续交付和持续部署。
- 每个服务都相对较小并容易维护。
- 服务可以独立部署。
- 服务可以独立扩展。
- 微服务架构可以实现团队的自治。
- 更容易实验和采纳新的技术。
- 更好的容错性。
我们来逐一进行分析。
使大型的复杂应用程序可以持续交付和持续部署
微服务架构最重要的好处是它可以实现大型的复杂应用程序的持续交付和持续部署。如后面1.7节所述,持续交付和持续部署是DevOps的一部分,DevOps是一套快速、频繁、可靠的软件交付实践。高效能的DevOps组织通常在将软件部署到生产环境时面临更少的问题和故障。
微服务架构通过以下三种方式实现持续交付和持续部署:
- 它拥有持续交付和持续部署所需要的可测试性。自动化测试是持续交付和持续部署的一个重要环节。因为每一个服务都相对较小,编写和执行自动化测试变得很容易。因此,应用程序的bug也就更少。
- 它拥有持续交付和持续部署所需要的可部署性。每个服务都可以独立于其他服务进行部署。如果负责服务的开发人员需要部署对该服务的更改,他们不需要与其他开发人员协调就可以进行。因此,将更改频繁部署到生产中要容易得多。
- 它使开发团队能够自主且松散耦合。你可以将工程组织构建为一个小型(例如,两个比萨)团队的集合。每个团队全权负责一个或多个相关服务的开发和部署。如图1-8所示,每个团队可以独立于所有其他团队开发、部署和扩展他们的服务。结果,开发的速度变得更快。
持续交付和持续部署可以为业务带来若干价值:
- 缩短了产品(或新功能)的上市时间,使企业能够快速响应客户的反馈。
- 使企业能够提供当今客户所期望的可靠服务。
- 员工满意度更高,因为开发人员可以因此花费更多时间来提供有价值的功能,而不是四处担任救火队员。
因此,微服务架构已经成为任何依赖于软件技术的企业业务的重要基石。
每个服务都相对较小并容易维护
微服务架构的另一个好处在于:相比之下每个服务都比较小。开发者更容易理解服务中的代码。较小规模的代码库不会把IDE等开发工具拖慢,这样可以提升开发者的工作效率。服务的启动速度也比大型的单体应用快得多,千万别小看这一点,快速启动的服务会提高效率,加速研发(提高调试、部署等环节的效率)。
服务可以独立扩展
服务可以独立扩展,不论是采用X轴扩展的实例克隆,还是Z轴扩展的流量分区方式。此外,每个服务都可以部署在适合它们需求的硬件之上。这跟使用单体架构的部署和硬件选择是迥然不同的:单体应用中组件对硬件的需求不同(例如有些组件是CPU运算密集型的,有些可能需要更多的内存空间),但是这些组件仍旧必须被部署在一起。
更好的容错性
微服务架构也可以实现更好的故障隔离。例如,某个服务中的内存泄漏不会影响其他服务。其他服务仍旧可以正常地响应请求。相比之下,单体架构中的一个故障组件往往会拖垮整个系统。
更容易实验和采纳新的技术
最后,微服务架构可以消除对某项技术栈的长期依赖。原则上,当开发一个新的服务时,开发者可以自由选择适用于这个服务的任何语言和框架。当然,很多公司对此往往有各种限制和规范,但重要的是团队有了选择的权利,而不是被之前选定的技术绑架。
更进一步,因为服务都相对比较小,使用更好的编程语言和技术来重写一项服务变得有可能。这也意味着,如果对一项新技术的尝试以失败而告终,我们可以直接丢弃这部分工作而不至于给整个应用带来失败的风险。这跟单体架构是完全不同的,单体架构之下的技术选型会严重限制后期新技术的尝试。
1.5.2 微服务架构的弊端
当然,没有一项技术可以被称为“银弹”。微服务架构也存在一些显著的弊端和问题。实际上本书的大部分内容就是用来解决这些由微服务架构带来的弊端和问题的。不必被这些弊端和问题吓倒,我会在后续章节把它们逐一解决。
微服务架构的主要弊端和问题如下:
- 服务的拆分和定义是一项挑战。
- 分布式系统带来的各种复杂性,使开发、测试和部署变得更困难。
- 当部署跨越多个服务的功能时需要谨慎地协调更多开发团队。
- 开发者需要思考到底应该在应用的什么阶段使用微服务架构。
我们来逐一进行分析。
服务的拆分和定义是一项挑战
采用微服务架构首当其冲的问题,就是根本没有一个具体的、良好定义的算法可以完成服务的拆分工作。与软件开发一样,服务的拆分和定义更像是一门艺术。更糟糕的是,如果对系统的服务拆分出现了偏差,你很有可能会构建出一个分布式的单体应用:一个包含了一大堆互相之间紧耦合的服务,却又必须部署在一起的所谓分布式系统。这将会把单体架构和微服务架构两者的弊端集于一身。
分布式系统带来的各种复杂性
使用微服务架构的另一个问题是开发人员必须处理创建分布式系统的额外复杂性。服务必须使用进程间通信机制。这比简单的方法调用更复杂。此外,必须设计服务来处理局部故障,并处理远程服务不可用或出现高延迟的各种情况。
实现跨多个服务的用例需要使用不熟悉的技术。每个服务都有自己的数据库,这使得实现跨服务的事务和查询成为一项挑战。如第4章所述,基于微服务的应用程序必须使用所谓的Saga来维护服务之间的数据一致性。第7章将解释基于微服务的应用程序无法使用简单查询从多个服务中检索数据。相反,它必须使用API组合或CQRS视图实现查询。
IDE等开发工具都是为单体应用设计的,它们并不具备开发分布式应用所需要的特定功能支持。编写包含多项服务在内的自动化测试也是很令人头疼的工作。这些都是跟微服务架构直接相关的问题。因此,团队中的开发人员必须具备先进的软件开发和交付技能才能成功使用微服务。
微服务架构还引入了显著的运维复杂性。必须在生产环境中管理更多活动组件:不同类型服务的多个实例。要成功部署微服务,需要高度自动化的基础设施,必须使用以下技术:
- 自动化部署工具,例如Netflix Spinnaker。
- 产品化的PaaS平台,例如Pivotal Cloud
Foundry或Red Hat OpenShift。 - Docker容器编排平台,例如Docker Swarm或Kubernetes。
我会在第12章详细介绍这些部署相关的技术。
当部署跨越多个服务的功能时需要谨慎地协调更多开发团队
使用微服务架构的另外一项挑战在于当部署跨越多个服务的功能时需要谨慎地协调更多开发团队。必须制定一个发布计划,把服务按照依赖关系进行排序。这跟单体架构下批量部署多个组件的方式截然不同。
开发者需要思考到底应该在应用的什么阶段使用微服务架构
使用微服务架构的另一个问题是决定在应用程序生命周期的哪个阶段开始使用这种架构。在开发应用程序的第一个版本时,你通常不会遇到需要微服务架构才能解决的问题。此外,使用精心设计的分布式架构将减缓开发速度。这对初创公司来说可能是得不偿失的,其中最大的问题通常是在快速发展业务模型和维护一个优雅的应用架构之间的取舍。微服务架构使得项目开始阶段的快速迭代变得非常困难。初创公司几乎肯定应该从单体的应用程序开始。
但是稍后,当问题变为如何处理复杂性时,那就是将应用程序功能性地分解为一组服务的时候了。由于盘根错节的依赖关系,你会发现重构很困难。我会在第13章讨论将单体应用程序重构为微服务的策略。
正如你所见,微服务是一把好处和弊端共存的双刃剑。正是因为这些困难,采用微服务架构是一个需要认真思考的决策。然而,类似面向消费者的Web应用程序或者SaaS类的复杂应用程序,微服务架构往往都是它们的正确选择。著名的网站,例如eBay、Amazon.com、Groupon和Gilt都是从单体应用逐步完成了向微服务架构的演化。
在使用微服务架构时,一些问题无法回避,必须得到解决。每个问题都可能存在多种解决办法,同时伴随着各种权衡和取舍。并没有一个完美的解决方案。为了帮助你更好地选型和使用微服务架构,我创建了一套微服务架构的模式语言。在向你传授微服务架构的细节时,我会在本书中反复提到这套模式语言。我们先来看看到底什么是模式语言,以及为什么它这么有用。
1.6 微服务架构的模式语言
架构设计的核心是决策。你需要决策到底是单体架构还是微服务架构更适合你的应用程序。当进行这些决策时,会存在大量的权衡和取舍。如果选择微服务架构,你又会面临一堆要解决的新问题。
一个用来表述多种架构设计的选择方案,并且可用来改进决策的方式,就是使用模式语言。我们先来看看为什么需要模式和模式语言,之后再来揭开微服务架构模式语言的奥秘。
1.6.1 微服务架构并不是“银弹”
早在1986年,《人月神话》(Addison-Wesley Professional,1995)的作者Fred Brooks就曾说:软件工程的世界里没有银弹。换一种说法,并不存在一种或几种技术,可以把你的生产效率提升10倍。然而30多年过去了,开发人员仍旧在充满激情地为他们的银弹进行辩护,总是坚信他们所钟爱的技术会给他们带来显著的效率提升。
很多争论都陷入了Neal Ford所提出的非黑即白的境地(http://nealford.com/memeagora/ 2009/08/05/suck-rock-dichotomy.html)(Suck/Rock)。他们通常都这么说:如果你做这个,某个可爱的小狗就会被杀死,因此你必须采用其他的方式。例如:同步和响应式编程、面向对象和函数式编程、Java和JavaScript,REST和消息,等等。当然,现实中总是有些差异,没有一项技术是银弹。每一项技术都有它的弊端和限制,这些总是被它们的鼓吹者所忽视。因此,某些技术的应用通常都会跟Gartner的光环曲线一样先抑后扬(https://en.wikipedia.org/wiki/Hype_cycle)。
光环曲线采用五个阶段来描述新兴技术的发展:其中的过热期,又被称为期望释放的顶峰,代表了人们对新技术的迷恋和崇拜,紧接着而来的是谷底期,又被称为失望的山谷,反映了人们在尝试之后对新技术的失望,光环曲线的最后阶段才是成熟期,又被称为生产力的高地,是指人们理解了新技术的优缺点之后开始理性地应用它。
微服务也同样没有幸免于银弹现象。这类架构是否适用于你的应用程序其实取决于很多因素。因此,盲目地鼓吹和使用微服务架构是非常不好的。当然,把微服务架构拒之千里之外似乎也不是什么明智的举动。就像很多事情一样,我们需要先“研究研究”,用英文来说,就是:it depends!
人们往往被情绪因素所驱动,这也是为什么会有这么多关于技术的两极分化和过度粉饰的争论。Jonathan Haidt在他的经典著作《The Righteous Mind: Why Good People Are Divided by Politics and Religion19》中采用大象和骑大象的人做了一个比喻,人的思维就如同大象和骑大象的人,大象代表了人脑情绪化的部分。它完成了绝大多数的决策。骑大象的人代表了大脑的理性部分,他有些时候可以影响大象的举动,大多数时候是用来为大象的决策举动进行判断。
我们,作为软件开发社区的一员,需要克服我们情绪化的本能,找到一种讨论和应用技术的更好方法。这个方法便是采用模式(Pattern)的形态。模式是一个客观的工具,在通过模式的形态描述一项技术时,必须包括它的弊端。现在,我们就来看看模式的形态。
1.6.2 模式和模式语言
模式是针对特定上下文中发生的问题的可重用解决方案。这个想法起源于现实世界中的建筑架构设计,并且已被证明针对软件架构设计同样行之有效。模式的概念是由Christopher Alexander创造的,他是一位现实世界中的建筑架构师。他创造了模式语言的概念:解决
特定领域内问题的相关模式的集合。他的著作《A Pattern Language: Towns, Buildings, Construction》描述了一种由253种模式组成的建筑结构模式语言。该模式的范围从宏观问题的解决方案,例如在哪里定位城市(“靠近水源”)到建筑设计的具体问题,例如设计一个房间(“确保房间两边都有阳光”)。这些模式中的每一个都通过具体的方案来解决问题,大到城市的布局,小到窗户的位置。
Christopher Alexander的著作启发了软件行业采用类似的模式和模式语言概念来解决设计和架构问题。《Design Patterns: Elements of Reusable Object-Oriented Software》(Addison-Wesley Professional 1994年出版)由Erich Gamma,Richard Helm,Ralph Johnson和John Vlissides合著,是面向对象设计模式的集合。该书在软件开发人员中普及了模式的概念。自从20世纪90年代中期开始,软件开发人员记录了许多软件模式。软件模式通过定义一组互相协作的软件元素来解决软件架构或设计问题。
例如,让我们想象一下,你正在构建必要的银行应用程序来支持各种透支政策。每个政策都定义了对余额的限制,以及账户和透支账户的费用。你可以使用“策略模式”(Strategy pattern)来解决这个问题,这是来自《设计模式》一书中众所周知的模式。策略模式定义的解决方案由三部分组成:
- 被称为Overdraft的策略接口,封装了具体的透支规则和算法。
- 一个或者多个具体的策略类,用来对应具体的场景。
- 使用这些规则的Account类。
策略模式是面向对象的设计模式,因此解决方案的元素是类。在本节的后面部分,我将介绍高层设计模式,它们的解决方案由互相协作的服务构成。
模式有价值的一个原因是模式必须描述它所适用的上下文场景。一个解决方案可能适用于一些场景,但不一定适用于另外的场景,这样的观点对于典型的技术讨论而言是更进一步了。例如,能够解决Netflix这样大规模用户系统的解决方案,对于一些较小的系统来说,可能不是最好的方法。
但是,模式的价值远远超出了要求架构师考虑问题的背景。它迫使架构师认真描述解决方案的其他关键但经常被忽视的方面。常用的模式结构包括三个重要部分:
- 需求(Forces)。
- 结果上下文(Resulting context)。
- 相关模式(Related patterns)。
让我们从需求开始学习。
需求:必须解决的问题
需求部分描述了必须解决的问题和围绕这个问题的特定上下文环境。需求有时候是互相冲突的,所以不能指望把它们全部都解决(必须取舍)。哪一个需求更重要,取决于它的上下文。你必须把需求按优先级进行排序。例如,像代码必须易于理解和代码必须有好的性能,类似这样的两个需求在某些情况下就是冲突的。采用响应式风格编写的代码性能往往比那些同步代码的性能好很多,但是这些代码也更难以读懂。把所有的需求明确列出是非常有帮助的,因为它可以清晰展现哪些问题需要被(优先)解决。
结果上下文:采用模式后可能带来的后果
结果上下文部分描述了采用这个模式的结果,它包含三个部分:
- 好处:这个模式的好处和它解决了什么需求。
- 弊端:这个模式的弊端和它没有解决哪些需求。
- 问题:使用这个模式所引入的新问题。
结果上下文提供了更加完整、从不偏不倚的视角来描述的解决方案,这有助于更好的决策。
相关模式:5种不同类型的关系
相关模式部分描述了这个模式和其他模式之间的关系。模式之间存在5种关系:
- 前导(Predecessor):前导模式是催生这个模式的需求的模式,例如,微服务架构模式是除单体架构模式以外整个模式语言中所有模式的前导模式。
- 后续(Successor):后续模式是指用来解决当前模式引入的新问题的模式,例如,如果你采纳了微服务架构模式,你需要一系列的后续模式来解决诸如服务发现、断路器等微服务带来的新问题。
- 替代(Alternative):当前模式的替代模式,提供了另外的解决方案,例如,单体架构和微服务架构就是互为替代的模式,它们都是应用的架构风格。你可以选择其一。
- 泛化(Generalization):针对一个问题的一般性解决方案。例如,在第12章中你会了解到“每主机单个服务”这个模式存在多种不同的技术实现。
- 特化(Specialization):针对特定模式的具体解决方案。例如,在第12章中你会了解到将服务部署为容器模式是针对“每主机单个服务”的具体解决方案。
此外,你可以把解决类似问题的模式组成一组模式。对相关模式的明确描述为如何有效解决特定问题提供了有价值的指导。图1-9显示了如何以可视方式表示模式之间的关系。
图1-9所展示的模式之间的不同关系包括如下几类:
- 代表了前导和后续的关系。
- 对于同一个问题的替代解决方案。
- 一个模式是另一个模式的特化。
- 针对特定问题领域的所有模式。
通过这些关系相关的模式集合形成所谓的模式语言。模式语言中的模式共同解决特定领域中的问题。我创建的微服务架构模式语言是微服务相互关联的软件架构和设计模式的集合。现在就让我们来看看这个模式语言。
1.6.3 微服务架构的模式语言概述
微服务架构的模式语言是一组模式,可帮助架构师使用微服务架构构建应用程序。
图1-10显示了模式语言的结构。模式语言首先帮助架构师决定是否使用微服务架构。它描述了单体架构和微服务架构,以及它们的好处和弊端。然后,如果微服务架构非常适合当前的应用程序,那么模式语言可以帮助架构师通过解决各种架构和设计问题来有效地使用它。
这套模式语言由若干组模式构成。在图1-10的左侧是应用程序架构模式组,包括单体架构模式和微服务架构模式。这些模式我们在本章中都已经有所讨论。其余的模式语言包括了一组如何解决采用微服务架构后引入的新问题的模式。
这些模式被分为三组:
- 基础设施相关模式组:这些模式解决通常是在开发环节跟基础设施有关的问题。
- 应用基础设施相关模式组:这些模式解决应用层面的基础设施相关问题。
- 应用相关模式组:这些模式解决开发人员面对的具体技术和架构问题。
这些模式根据所解决问题的不同可进行更进一步的分组。我们先看看其中主要的几组模式。
服务拆分的相关模式
决定如何把系统分解为一组服务,这项工作从本质上来讲是一门艺术,但是即使这样,我们仍旧有一些策略可以遵循。我在服务拆分相关的模式中提出了一些策略,可以用于定义应用程序的架构。如图1-11所示。
我会在第2章着重介绍这些模式。
通信的相关模式
使用微服务架构构建的应用程序是分布式系统。因此,进程间通信(IPC)是微服务架构的重要组成部分。架构师必须就服务彼此之间以及与外部世界进行通信做出各种架构和设计决策。图1-12显示了通信模式,它们分为以下5组:
- 通信风格:使用哪一类进程间通信机制?
- 服务发现:客户端如何获得服务具体实例(如HTTP请求)的IP地址?
- 可靠性:在服务不可用的情况下,如何确保服务之间的可靠通信?
- 事务性消息:如何将消息发送、事件发布这样的动作与更新业务数据的数据库事务集成?
- 外部API:应用程序的客户端如何与服务进行通信?
我会在第3章介绍前4组模式:通信风格、服务发现、可靠性和事务性消息,在第8章讨论外部API模式。
实现事务管理的数据一致性相关模式
如之前提到的,为了确保松耦合,每个服务都必须拥有它自己的数据库。不幸的是,每个服务都有独立的数据库会引入一些大麻烦。例如,我会在第4章中解释为什么我们常用的两步式提交(two phase commit,2PC)分布式事务机制在微服务架构之类场景下就不再适用。取而代之,应用程序需要使用Saga模式来确保数据的一致性。图1-13展示了数据一致性有关的模式。
我会在第4~6章中详细讨论这些模式。
在微服务架构中查询数据的相关模式
服务和数据库一一对应还会带来另外一个挑战:有些查询需要从多个服务的数据源获取数据(传统应用采用SQL JOIN的方式完成)。服务的数据仅可以通过API的方式访问,所以我们不能直接针对服务的数据库执行分布式查询。图1-14展示了跟实现查询有关的一些模式。
有些时候我们可以使用API组合模式,逐一调用服务的API然后把所有的返回聚合在一起。多数情况之下,你需要使用称之为命令查询职责隔离(CQRS)的方式,来维护一些重要和常用的查询数据视图。在第7章,我会深入探讨实现这些查询的方法。
服务部署的相关模式
部署一个单体应用往往不是一件简单的事情,但总体上来说还是一个比较直观的操作,因为毕竟只有一个应用实体需要被部署。你需要考虑的只是如何在负载均衡器后面运行这个应用的多份实例。
然而,部署基于微服务的应用程序就要复杂得多。通常应用由各种异构的语言和框架开发的数十甚至上百个服务组成,有很多动态的部分需要被考虑。图1-15展示了跟部署有关的一些模式。
传统(手工)方式的应用程序部署,也就是把应用程序(如WAR文件)复制到服务器上,这样的做法不再适用于微服务架构了。你需要一个高度自动化部署的基础设施。理想情况下,你需要有一个部署平台,包括一个简单的界面(命令行或者图形用户界面都可以)来部署和管理这些服务。这些部署平台往往都是基于虚拟机、容器或者Serverless技术的。我在第12章会详细介绍这些部署方式的差异。
可观测性的相关模式
应用运维的一项重要工作就是搞明白应用在运行时的一些行为,同时能够根据错误的请求或者高延迟等故障进行诊断排错。理解和诊断一个单体应用并不是一项容易的工作,但毕竟它的请求和处理都是针对一个实例,以相对简单的方式完成的。每一个入站请求都通过负载均衡指派到了一个具体的应用实例,然后实例向数据库发起若干请求,最后返回结果。如果需要,你可以通过查阅该应用实例的日志文件的方式理解整个请求的过程。
与之相反,理解和诊断微服务架构下发生的问题往往是一项令人头疼的工作。在把最终结果返回给客户端之前,一项请求往往会在多个服务之间跳转,也就是说光看一个日志文件是解决不了问题的。有关高延迟的问题就更加令人抓狂,因为这会涉及多种可能的原因。
以下模式可用来设计具备可观测性的服务:
- 健康检查API:可以返回服务健康状态的API。
- 日志聚合:把服务产生的日志写入一个集中式的日志服务器,这个服务器可以提供日志搜索,也可以根据日志情况触发报警。
- 分布式追踪:为每一个外部请求分配一个唯一的ID,用于在各个服务之间追踪外部请求。
- 异常跟踪:把程序异常发送到异常跟踪服务,这个服务会排除重复异常,给开发者发送告警并且跟踪每一个异常的解决。
- 应用指标:供维护使用的指标,例如计数器等,导出到指标服务器。
- 审计日志:记录用户的行为。
我会在第11章深入讨论可观测模式。
实现服务自动化测试的相关模式
微服务架构让单一的服务测试变得容易,因为相比单体应用,每一个服务都变得更小了。但与此同时,重要的是测试不同的服务是否协同工作,同时避免使用复杂、缓慢和脆弱的端到端测试来测试多个服务。以下是通过单独测试服务来简化测试的模式:
- 消费端驱动的契约测试:验证服务满足客户端所期望的功能。
- 消费端契约测试:验证服务的客户端可以正常与服务通信。
- 服务组件测试:在隔离的环境中测试服务。
在第9章和第10章中,我们会介绍这些跟测试有关的模式。
解决基础设施和边界问题的相关模式
在微服务架构中,每个服务都必须实现许多跟基础设施相关的功能,包括可观测性模式和服务发现模式。还必须实现外部化配置模式,该模式在运行时向服务提供数据库凭据等配置参数。在开发新服务时,从头开始重新实现这些功能是在太费时间了。一种更好的方法是在处理这些问题时应用微服务基底模式,在这样的现有成熟的基底框架之上构建服务。第11章将详细描述这些模式。
安全相关的模式
在微服务架构中,用户身份验证的工作通常由API Gateway完成。然后,它必须将有关用户的信息(例如身份和角色)传递给它调用的服务。常见的解决方案是应用访问令牌模式。API Gateway将访问令牌(例如JWT,即JSON Web令牌)传递给服务,这些服务可以验证令牌并获取有关用户的信息。第11章将更详细地讨论访问令牌模式。
不必惊讶,这些模式的目的就是解决采用新架构之后浮现的种种问题。为了成功地开发软件,你必须选择合适的架构。但架构不是你唯一需要关注的领域,你还必须思考流程和组织。
1.7 微服务之上:流程和组织
对于大型和复杂的应用程序,微服务架构往往是最佳的选择。然而,除了拥有正确的架构之外,成功的软件开发还需要在组织、开发和交付流程方面做一些工作。图1-16展示了架构、流程和组织之间的关系:
我们已经谈过了微服务架构,现在来看看组织和流程。
1.7.1 进行软件开发和交付的组织
成功往往意味着研发团队规模的扩大。一方面,这是个好事,因为人多力量大。但是团队大了以后,正如Fred Brooks在《人月神话》这本书中提到的,沟通成本会随着团队的规模呈O(N2)的速度上升。如果团队太大,由于沟通成本过高,往往会使得团队的效率降低。想想看,如果每天早上的站会规模达到20人会是怎样?
解决之道是把大团队拆分成一系列小团队。每个团队都足够小,人员规模为8~12人。每个团队都有一个明确的职责:开发并且可能也负责运维一个或者多个服务,这些服务实现了一个或多个业务能力。这些团队都是跨职能的。他们可以独立地完成开发、测试和部署等任务,而不需要频繁地与其他团队沟通或者协调。
逆向的康威定律
为了在使用微服务架构时有效地交付软件,你需要考虑康威定律(https://en.wikipedia. org/wiki/Conway%27s_law):
设计系统的组织……往往被组织的架构所限制,最终设计的结果是这些组织的沟通结构的副本。
—梅尔文·康威
换句话说,应用程序的架构往往反映了开发它的组织的结构。因此,反向应用康威定律并设计你的企业组织(www.thoughtworks.com/radar/techniques/inverse-conway-maneuver
),使其结构与微服务的架构一一对应。通过这样做,可以确保你的开发团队与服务一样松耦合。
若干个小团队的效率显然要高于一个单一的大团队。正如在1.5.1节中我们曾讨论过的,微服务架构使得团队可以实现某种程度的“自治”。每个团队都可以开发、部署和运维扩展他们负责的服务,而不必与其他团队协调。更进一步,当出现了某个服务故障或没有满足SLA等要求时,对应的责任人(团队)也非常清楚。
而且,开发组织的可扩展性更高。你可以通过添加团队来扩展组织。如果单个团队变得太大,则将其拆分并关联到各自负责的服务。由于团队松散耦合,你可以避免大型团队的沟通开销。因此,你可以在不影响工作效率的情况下添加人员。
1.7.2 进行软件开发和交付的流程
采用微服务架构以后,如果仍旧沿用瀑布式开发流程,那就跟用一匹马来拉法拉利跑车没什么区别—我们需要充分利用微服务带来的各种便利。如果你希望通过微服务架构来完成一个应用程序的开发,那么采用类似Scrum或Kanban这类敏捷开发和部署实践就是必不可少的。同时也需要积极实践持续交付和持续部署,这是DevOps中的关键环节。
Jez Humble(https://continuousdelivery.com)
把持续交付定义为:
持续交付能够以可持续的方式安全、快速地将所有类型的更改(包括新功能、配置更改、错误修复和实验)交付到生产环境或用户手中。
持续交付的一个关键特征是软件总是随时可以交付的。它依赖于高水平的自动化,包括自动化测试。在将代码自动部署到生产环境的过程中,持续部署把持续交付提升到了一个新的水准。实施持续部署的高绩效组织每天多次部署到生产环境中,生产中断的次数要少得多,并且可以从发生的任何事情中快速恢复(https://puppet.com/resources/whitepaper/state-of-devops-report)。
如前面的1.5.1节所述,微服务架构直接支持持续交付和持续部署。
快速推进同时不中断任何事情
持续交付和持续部署(以及更一般地说,DevOps)的目标是快速可靠地交付软件。评估软件开发的四个有用指标如下:
- 部署频率:软件部署到生产环境中的频率。
- 交付时间:从开发人员提交变更到变更被部署的时间。
- 平均恢复时间:从生产环境问题中恢复的时间。
- 变更失败率:导致生产环境问题的变更提交百分比。
在传统组织中,部署频率低,交付的时间很长。特别是开发人员和运维人员通常都会在维护窗口期间熬夜到最后一刻。相比之下,DevOps组织经常发布软件,通常每天多次发布,生产环境问题要少得多。例如,亚马逊在2014年每隔11.6秒就将代码更改部署到生产环境中(www.youtube.com/watch?v=dxk8b9rSKOo)
,Netflix的一个软件组件的交付时间为16分钟(https://medium.com/netflixtechblog/how-we-build-code-at-netflix-c5d9bd727f15)。
1.7.3 采用微服务架构时的人为因素
采用微服务架构以后,不仅改变了技术架构,也改变了组织结构和开发的流程。归根到底,这是对工作环境中的人(正如之前提到的,情绪化的生物)进行的一系列改变。如果忽略人们的情绪,那么采纳微服务架构将会是一个非常纠结和折腾的过程。FTGO的首席技术官玛丽和其他的管理层,正面临着如何改变FTGO软件开发方式的挑战。
William和Susan Bridges的畅销书《Managing Transitions》(Da Capo Lifelong Books,2017,https://wmbridges.com/books)介绍了转型(transition)的概念,其中阐述了人们如何对变化做出情绪化的反应。它包括以下三个阶段。
1.结束、失落和放弃:当人们被告知某种变化,这类变化会把他们从舒适区中拉出,这类情绪开始滋生和蔓延。人们会念叨失去之前的种种好处。例如,当被重组到一个新的跨职能团队时,人们会想念他们之前的同事。再比如,对于负责全局数据建模的团队来说,每个服务团队负责自己的数据建模,这对他们是一种威胁。
2.中立区:处理新旧工作方式交替过程中,人们普遍会对新的工作方式无所适从。人们开始纠结并必须要学习处理新工作的方式。
3.新的开始:最终阶段,人们开始发自内心地热情拥抱新的工作方式,并且开始体验到新工作方式所带来的种种好处。
本书介绍了如何管理转型过程中每个阶段的问题,提高转型的成功率。FTGO显然正在单体地狱中煎熬,急切地需要转型到微服务架构。他们也需要对组织结构和开发流程做出调整。为了成功地实现这一切,FTGO必须认真面对这些转型模式和所有可能的情绪化反应。
在下一章,我们将学习软件架构的目标和如何把应用程序拆分成服务。
本章小结
- 单体架构模式将应用程序构建为单个可部署单元。
- 微服务架构模式将系统分解为一组可独立部署的服务,每个服务都有自己的数据库。
- 单体架构是简单应用的不错选择,微服务架构通常是大型复杂应用的更好选择。
- 微服务架构使小型自治团队能够并行工作,从而加快软件开发的速度。
- 微服务架构不是银弹:它存在包括复杂性在内的诸多弊端。
- 微服务架构模式语言是一组模式,可帮助你使用微服务架构构建应用程序。它可以帮助你决定是否使用微服务架构,如果你选择微服务架构,模式语言可以帮助你有效地应用它。
- 你需要的不仅仅是通过微服务架构来加速软件交付。成功的软件开发还需要DevOps和小而自治的团队。
- 不要忘记采纳微服务过程中的人性层面。你需要考虑员工的情绪才能成功转换到微服务架构。