微服务实战

作者摩根·布鲁斯 保罗·A.佩雷拉分类计算机-计算机综合
推荐值76.2 %来源微信读书
笔记数量104评论数量 1
简介:《微服务实战》主要介绍如何开发和维护基于微服务的应用。本书源自作者从日常开发中得到的积累和感悟,其中给出的案例覆盖从微服务设计到部署的各个阶段,能够带给你真实的沉浸式体验。通过阅读本书,你不仅能够了解用微服务搭建高效的持续交付流水线的方法,还能够运用Kubernetes、Docker以及GoogleContainerEngine进一步探索书中的示例。
  • 微服务生产环境具备以下六大基础功能。(1)部署目标或者运行平台,也就是服务所运行的地方,
  • 微服务架构的流行、DevOps实践(如基础设施即代码,infrastructure as code)的广泛采用以及使用云服务提供商来运行应用的情况日益增多,这三者同时存在并非巧合。这些实践能够让服务快速迭代和部署,从而使微服务架构成为一种可扩展和可行的方案。

第9章 基于容器和调度器的部署

  • 容器并不是使用微服务的必要条件。开发者可以使用许多方法来部署服务,比如使用我们在第8章中提到的单虚拟机单服务模型。但是借助调度程序,容器提供了一种特别优雅和灵活的方法来满足我们的两个部署目标:速度和自动化

9.2 集群部署

  • 对于微服务来说,调度器是一个引人注目的部署平台,因为它在理论上简化了对任意数量的独立服务的伸缩扩容、健康检查和发布的管理。它在确保有效利用底层基础设施的同时做到了这一点。从整体来看,容器调度程序工作流的工作过程如下所示。

第10章 构建微服务交付流水线

  • 。部署流水线是构建持续交付的核心基石。

10.1 让部署变得平淡

  • 理想的微服务部署流程应该满足两大目标。其一,节奏安全——新服务和变更的部署速度越快,开发者迭代开发和向终端用户交付成品的速度也就越快。部署工作应该尽可能提高安全性,开发者应该尽一切可能对给定的变更进行验证,确保其不会对服务的稳定性产生负面影响。其二,一致性——不管底层采用哪种技术栈,各个服务都采用同一套部署流程有助于降低技术隔离,并使操作更具可预测性和可扩展性。

第四部分 可观测性和所有权

第11章 构建监控系统

11.1 稳固的监控技术栈

  • 会把重点放在监控、度量指标和告警上。在第12章,我们会讲解日志和链路追踪,它们共同构成了可观测性。
  • 我们列出了一套监控系统技术栈的各个组成部分:度量指标、日志和链路追踪。
  • 我们讨论了架构层次:客户端、边界层、服务层和平台层。也应该把这4层都监控起来,因为开发者不可能在完全隔离的情况下确定一个特定组件的执行情况。网络问题最有可能影响服务的运行。如果开发者只在服务层面上收集度量指标,那么所能知道的也就只能是这个服务现在没有请求可处理。除此之外,对于问题出现的原因一无所知。而如果开发者同时还收集了基础设施层的度量指标,就可以了解到那些极有可能影响其他众多组件的问题
  • 在从面向用户的系统中收集度量指标时,开发者应该关注四大黄金标志:时延、错误量、通信量和饱和度。
  • 发者可以为每个服务创建多个展示详细信息的仪表盘,但是要保留最上层的仪表盘来展示最重要的信息。这个仪表盘应该能够方便开发者一眼判断出服务是否运行正常。它展示的应是服务的整体情况,其他更深入的信息应该出现更具体的仪表盘中。
  • 者应该聚焦于最重要的部分,如响应时间、错误量和通信量。它们是可观测性的基石。开发者还应当为每个指标设定合适的百分位:99分位、95分位、75分位,等等。对

第12章 使用日志和链路追踪了解系统行为

12.2 生成一致的、结构化的、人类可读的日志

  • 开发者都应该在要记录的数据中尽可能多地使用唯一的标识符。当开发者交叉引用来自多个数据源的数据时,请求ID、用户ID和其他唯一标识符是非常重要的。有了这些标识符,开发者就能够非常高效地将不同数据源的数据分组。
  • 开发者想要以人类可读的格式来生成日志记录,但是这些日志同时又需要使机器易于解析。
  • 在显式调用logger模块时,开发者只需要声明所期望的等级以及所要记录的消息,这个消息以键值对的形式包含了消息文本和一个uuid。日志记录中的所有其他信息都是由Logstash自动收集和添加的,不需要人为显式声明。

12.3 为SimpleBank配置日志基础设施

  • 在配置完日志聚合功能后,所有服务就开始同时向中心化系统发送度量指标和日志信息了,这能够提高系统的可观测性

12.4 服务间的跟踪交互

  • penTracing API是一个与供应商无关的分布式链路追踪开放标准。许多分布式跟踪系统(如Dapper、Zipkin、HTrace、X-Trace)都提供了链路追踪功能,但使用的是互不兼容的API。选择其中一个系统通常意味着可能要与使用不同编程语言的系统紧密耦合到一起,从而形成一个解决方案。OpenTracing的目的是为链路追踪的信息收集提供一组约定的、标准化的API。类库可用于不同的语言和框架。
  • trace是由单个或多个span组成的有向无环图(DAG),这些span的边称为reference。trace用于聚合和关联整个系统的执行流。为此,需要传播一些信息。一个trace记录整个流程。
  • 每个span包含如下信息:操作名称、起始时间戳和完成时间戳、零个或者多个span标签(键值对)、零个或多个span日志(带时间戳的键值对)、span上下文(context)以及引用零个或多个span的参考(通过span上下文)。

第13章 微服务团队建设

13.1 建设高效团队

  • 确保沟通渠道保持可控(图13.1展示了沟通关系的增长)。这有助于增强团队的活力和协作,同时缓解冲突。对于团队的“合适规模”存在许多探索,比如JeffBezos的两块披萨规则或Michael Lopp的7+/−3公式。
  • (2)清楚地描述了团队的责任和义务,同时提高了独立性和灵活性。
  • 方面,如果团队之间的边界重叠且责任不明确,则可能加剧紧张情势;另一方面,各个独立的团队仍然需要相互协作来交付整个应用
  • 康威定律展现了团队与系统之间的关联关系。……设计系统的组织受到限制所以设计出来的架构方案等价于组织的沟通结构。

13.2 团队模型

  • 在本节,我们会考察两种组织团队的方式:按职能组织团队和跨职能组织团队,并会讨论这两种组织
  • (1)在按职能划分的方式中,我们按照专业来将人员分组,并采用职能化的汇报线方式,最后将这些人员分配到有时间限制的项目中。大多数组织投入资金支持的项目都是为了实现特定的需求,并有时间限制。衡量项目是否成功的依据就是他们有无准时交付并完成需求。(2)跨职能搭建的团队是由具有不同技能集的人员组成的团队,通常与长期的产品目标或远大的使命保持一致。在需求范围内,可以自由安排项目的优先级,并根据需要构建的功能完成这些任务。衡量团队是否成功的依据通常是其对业务关键性能指标(KPI)和结果的影响。
  • 缺乏自治。这些团队之间是紧密耦合的,而不是自治的。他们的工作优先级是在其他地方制订的,当工作需要跨多个团队时,团队被阻塞和开发工作被阻碍的可能性就会增加。这将导致交付时间变长、返工、有质量问题和延期。如果不能与他们正在构建的系统架构保持一致,那么团队将无法在不被其他团队阻碍的情况下开发出自己的应用。
  • 没有长期责任感。面向项目的方式不利于对所开发的代码或产品质量承担起长期责任。
  • 通过优化专业知识,按职能划分的方法旨在消除重复劳动以及由技能欠缺导致的效率低下,从而降低总体成本。但这会造成僵局:增加团队摩擦,降低实现组织目标的进度。这并不好,微服务架构旨在加快进度和减少摩擦。
  • 由具有不同专长和角色的人员组成,旨在实现特定的业务目标。我们可以将这些团队称为市场驱动型团队
  • 团队与业务价值保持一致将体现在所开发的应用中
  • 我们在Amazon所使用的细粒度服务方法中,服务代表的不仅是软件结构,同时还代表了组织结构。这些服务拥有强大的所有权模型,再加上团队规模较小,使得创新变得非常容易。在某种意义上,开发者可以把这些服务看作大公司内部的小型初创企业。其中每一个服务都需要强烈关注它们的客户是谁,不管它们是外部的还是内部的。—— Werner Vogels
  • 跨职能的团队应该有一个使命,这个使命是鼓舞人心的:它给团队提供了奋斗目标,但也设定了团队职责的边界
  • 构建一个微服务平台——部署流程、底座、工具操作和监控系统——对于可持续和快速地构建一个良好的微服务应用是至关重要的。当刚开始使用微服务时,构建应用的团队通常也负责平台的构建任务(图13.9)。随着时间的推移,这个平台需要满足多个团队的需求,在此阶段,读者可能会开始建立一个平台团队(图13.10)。
  • 可以缓解这些问题。我们成功地应用了Spotify的分会和协会模式[插图]。这些是社区实践:分会根据职能专长(如移动开发)对人员进行分组[插图]。协会围绕一个跨职能的主题分享实践,比如性能、安全。

第一部分 概述

第1章 微服务的设计与运行

1.1 什么是微服务应用

  • 可以看到,微服务有三大关键特性。(1)每个微服务只负责一个功能。这个功能可能是业务相关的功能,也可能是共用的技术功能,比如与第三方系统(如证券交易所)的集成。(2)每个微服务都拥有自己的数据存储,如果有的话。这能够降低服务之间的耦合度,因为其他服务只能通过这个服务提供的接口来访问它们自己所不拥有的数据。(3)微服务自己负责编排和协作(控制消息和操作的执行顺序来完成某些有用的功能),既不是由连接微服务的消息机制来完成的,也不是通过另外的软件功能来完成的。
  • 除了这三大特性,微服务还有两个基本特性。(1)每个微服务都是可以独立部署的。如果做不到这一点,那么到了部署阶段,微服务应用还是一个庞大的单体应用。(2)每个微服务都是可代替的。每个微服务只具备一项功能,所以这很自然地限制了服务的大小。同样,这也使得每个服务的职责或者角色更加易于理解。
  • 服务与传统的面向服务架构(SOA)在思想上的一个关键区别就是微服务负责协调系统中的各个操作,而SOA类型的服务通常使用企业服务总线(ESB)或者更复杂的编排标准来将应用本身与消息和流程编排拆分开。在SOA模型下,服务通常缺乏内聚性,因为业务逻辑会不断地被添加到服务总线上,而非服务本身。
  • 在《可扩展性的艺术》(The Art of Scalability)一书中,阿尔伯特(Abbott)和费舍尔(Fisher)定义了一个被称为“扩展立方体”的三维扩展方案
  • 单体应用一般都是通过水平复制进行扩展的:部署多个完全相同的应用实例。这种方式也称作饼干模具(cookie-cutter)扩展或X轴扩展。相反,微服务应用是一个Y轴扩展的例子,我们将整个系统分解为不同的功能模块,然后针对每个模块自己特有的需求来进行扩展。
  • 同样,自治和可独立部署意味着工程师可以分别管理这些微服务所对应的资源需
  • 微服务应用具备一些很有意思的技术特性:按照单一功能来开发服务可以让架构师能够在规模和职责上很自然地划定界限;自治性使得开发者可以独立地对这些服务进行开发、部署和扩容。
  • 支撑微服务开发的五大文化和架构原则为:自治性、可恢复性、透明性、自动化和一致性。
  • 为了保证自治性,开发者需要将服务设计得松耦合、可独立部署。
  • 尽管将应用拆分成多个服务能够隔离故障,但它还是会存在多点故障的问题。同样,当故障发生的时候,开发者需要能够解释到底发生了什么问题以避免连锁反应。这包括设计层面的(在可能的情况下支持异步交互以及适当地使用熔断器和超时),还包括运维层面的(比如,使用可验证的持续交付技术和对系统活动进行稳定可靠地监控)。
  • 。不管在什么时候,系统都应该是透明的、可观测的,这样既可以发现问题,也可以对问题进行诊断。
  • 微服务架构的流行与两种趋势是同时发生的—— 一种趋势是DevOps技术得到主流接纳,其中的典型就是基础设施即代码(infrastructure-as-code)技术;另一种趋势是完全通过API进行编程的基础设施环境(如AWS和Azure)的兴起。
  • 软件开发的目标是持续地缩短交付周期来产生积极的商业价值
  • 因此,我们相信一个良好的复杂系统应该能在整个生命周期内将冲突和风险这两个因素的影响最小化。
  • 随着单体应用越来越大,下面这些几个因素会导致冲突。(1)变更周期耦合在一起,导致协作障碍并增大回滚的风险。(2)在没有严格规范的团队中,软件模块和上下文边界含混不清,导致组件之间产生意料之外的紧耦合。(3)应用的大小成为痛点:持续集成作业、系统发布(甚至是本地应用启动)都会变得越来越慢
  • 小的服务也是持续交付的重要推动者。在大型应用中,部署的风险是很高的,而且涉及漫长的回归和验收周期。通过部署更小的功能元素,开发者可以降低每次独立部署的潜在风险,更好地隔离对线上系统的改动

1.2 微服务的挑战

  • 设计和运行微服务应用时,开发者会遇到很多挑战,如下所示
  • (1)识别和划定微服务范围需要大量专业的业务领域知识。(2)正确识别服务间的边界和契约是很困难的,而且一旦确定,是很难对它们进行改动的。(3)微服务是分布式系统,所以需要对状态、一致性和网络可靠性这些内容做出不同的假设。(4)跨网络分发系统组件以及不断增长的技术差异性,会导致微服务出现新的故障形式。(5)越来越难以了解和验证在正常运行过程中会发生什么事情。
  • (3)可预测:准确反映了所有实现的真实表现。
  • 微服务并不能消除风险,而是将这个成本移到了系统生命周期的后半阶段:降低了开发过程中的冲突,但是增加了运维阶段系统部署、验证以及监控的复杂度。
  • 但是在不断变化的解耦系统中,清楚地了解整体的情况可能变得极度困难,这又使得问题诊断和支持变得更具有挑战性。当出现故障时,开发者需要通过一系列的方式来跟踪系统实际发生的行为(调用了哪个服务、顺序是什么以及输出是什么),但是还需要一些途径来了解系统应该发生的行为。
  • 最后,工程师会面对微服务的两大运维挑战:可观测性和多点故障。

1.3 微服务开发生命周期

  • 如果做不到这一点的话,开发者将要投入大量的精力来“通下水道”[在英文中“通下水道”(plumbing)用来比喻一些价值不大的脏活累活],而不能为客户创造任何价值。
  • 我们已经听说过可靠的部署是很“单调”的。“单调”的意思不是说乏味无聊,而是说没有事故发生。

1.4 有责任感和运维意识的工程师文化

  • 康威定律在某种层次上表达了类似的含义:设计系统的组织……都是受到约束的,其设计出来的方案只是这些组织的沟通结构的翻版。

第2章 SimpleBank公司的微服务

2.3 开发新功能

  • 开发一个最小可行产品(Minimum Viable Product,MVP)是非常重要的第一步。
  • ,开发者会意识到以机器可读的格式将服务交互的接口标准化的显著好处,比如,REST API可以使用Swagger/OpenAPI。

2.5 将功能发布到生产环境中

  • 巴士系数[插图]是用来衡量由团队成员之间的知识未被分享所造成的风险大小的指标,源自于“以防他们被巴士撞了”这句话,也被称作货车系数(truck factor)。巴士系数越低,团队的风险越大。
  • 如,对于关键服务,开发者会保证在99.99%的可用性基础上,95%的请求能够在100毫秒内返回。如果没有达到这些阈值,就应该向服务所有者发送告警

第二部分 设计

第3章 微服务应用的架构

  • 通常如何将微服务应用设计为四层结构——平台层、服务层、边界层和客户端层。

3.1 整体架构

  • 架构师应该通过两种方式来指导开发:第一,准则——为了实现更高一层的技术目标或者组织目标,团队要遵循的一套指南;第二,概念模型——系统内部相互联系以及应用层面的模式的抽象模型。

3.5 服务边界

  • 边界层还可以实现一些其他面向客户端的功能:认证和授权——验证API客户端的身份和权限;限流——对客户端的滥用进行防卫;缓存——降低后端整体的负载;日志和指标收集——可以对客户端的请求进行分析和监控。
  • 服务于前端的后端(BFF)模式是API网关模式的一种变形。尽管API网关模式很简洁,但是它也存在一些缺点。如果API网关为多个客户端应用充当组合点的角色,它承担的职责就会越来越多。

第5章 微服务的事务与查询

  • 之前只需要在数据库层面进行关联的查询操作现在需要调用多个服务才能实现。在某些使用场景中,这还是能够接受的,但是当数据集特别大时,这种方案就会变得非常麻烦。

5.1 分布式应用的事务一致性

  • 种常见的方案就是使用二阶段提交(two phase commit,2PC)协议

5.2 基于事件的通信

  • 这种方式称为编排。每个服务可以在不了解整个流程结果的情况下响应各种事件,独立执行各种操作。这些服务就如同舞蹈演员一般:他们知道每一段音乐的舞步和要做的动作,不需要有人显式地请求或者命令他们,就会按照音乐的变化给出相应的反应。相应地,这种设计方式解除了服务之间的耦合,提升了各个服务的独立性,并且简化了独立部署变更的复杂度。

5.3 Saga

  • 在Saga中,每一步的操作都是由前一个步骤所触发的。
  • 在Saga中,我们会用补偿操作来撤销之前的操作,并让系统恢复到更一致些的状态。系统不保证一定会恢复到最初的状态;
  • 这种回滚形式的目的是让系统在语义上达到一致,而非数学意义上的一致。系统将一个操作回滚后并不一定能恢复到和之前完全一样的状态。
  • 在构建反映真实世界环境的微服务时,预见到失败场景并做相应准备是很重要的一块内容,操作的隔离性反而没那么至关重要。在设计微服务时,我们需要把补偿考虑在内,以确保整个应用有足够的恢复能力。

5.4 分布式世界中的查询操作

  • 缓存失效是众所周知的难题。[插图]
  • 一个系统如果想要最大限度地提升可用性基本上都是靠补偿操作和重试——就像Saga那样。从架构的角度看,高可用性通常更容易实现一些,因为这能够降低协作成本,更加易于构建可扩展的应用。
  • 埃里克·布鲁尔(Eric Brewer)最近发表的一篇文章CAP Twelve Years Later: How the "Rules" Have Changed对这种场景做了很好的概述。
  • 这种命令-查询职责分离(CQRS)模式是一种应用于这种场景的通用模型,它显式地将系统[插图]中的读(查询)和写(命令)进行分离。
  • CQRS将服务分成了命令和查询两部分,每一部分分别由不同的数据存储来提供支持
  • ,在CQRS模式中,服务的命令状态天然地会先于查询状态而得到更新,由于这种复制延迟(replication lag)的存在使得开发者需要考虑最终一致性。
  • 而在另外一些系统中,确保用户查询不到无效的状态是非常重要的工作。在这种场景中,开发者可以采用图5.20所示的3种策略:乐观更新、轮询以及发布-订阅。
  • 这种方式依赖于开发者拥有更新界面的所有信息,或者开发者能够根据输入数据得出这些信息。所以在用于一些简单场景时,这种方式效果最好。
  • 我们还可以将CQRS技术推广到其他使用场景中,比如数据分析和报表。

第6章 设计高可靠服务

6.1 可靠性定义

  • 服务正常运行时间的百分比
  • 在设计服务时,我们需要采取一种防御型的方式来满足3个目标:对于无法避免的故障要降低其发生率;对于无法预测的故障要控制其连锁影响,不要产生系统层面的影响;故障发生后,能够快速恢复(理想情况下可以自动恢复)。

6.3 设计可靠的通信方案

  • 我们会探讨一些技术,以确保在协作方服务不可用时服务依旧能够最大限度地正确运行:重试;后备方案(fallback)、缓存和优雅降级;超时和最后期限;熔断器;通信代理(communication broker)。
  • 我们期望获取数据的调用是幂等的(idempotent),也就是对目标系统的状态没有影响,并且是可以重复的[插图]。
  • Python中,开发者可以使用开源类库tenacity来在API客户端(holdings/clients.py文件中的MarketDataClient类)的对应方法上添加装饰器,如果方法抛出异常,则自动执行重试操作
  • 但是如果故障是持续性的——比如,如果market-data服务的承载力下降了,后续的请求会使这一问题进一步恶化并导致系统稳定性下降
  • 血崩
    但是如果故障是持续性的——比如,如果market-data服务的承载力下降了,后续的请求会使这一问题进一步恶化并导致系统稳定性下降
  • 我们该如何在不扩大系统故障范围的前提下通过重试技术来提高服务的可恢复性呢?首先,我们可以在这些重试操作之间使用一个变化的时间间隔来保证这些请求分布更加均匀,降低重试的那部分请求负载的频率。这就是所谓的指数退避(exponential back-off)策略,它的目的是给系统提供一个较低负载的时间,以便于恢复正常。
  • 作为替代,指数退避应该包含一个随机元素——抖动(jitter)来将重试分散到一个更恒定的速率,避免过多的大量重试同时执行[插图]。
  • 为了容忍间歇性的依赖型错误,重试是一种有效的策略。但是,在使用过程中,我们需要格外小心,以免导致底层问题进一步恶化或耗费不必要的资源:永远要限制重试的总次数;使用带抖动的指数退避策略来均匀地分配重试请求和避免进一步加剧负载;仔细考虑哪些错误情况应该触发重试,以及哪些重试不大可能成功、哪些重试永远不会成功。
  • 如果某个服务的依赖项出现了故障,我们可以考虑4种后备方案:优雅降级、缓存、功能冗余和桩数据
  • 路器需要发送试验性的请求,以判断连接是否恢复到健康状态。在这个试验状态中,断路器处于半开状态(half open):如果调用成功,断路器就会闭合;否则,继续保持断开状态

6.4 最大限度地提高服务可靠性

  • ,负载均衡器的作用有两个:第一,确定底层的哪些实例是健康的,是能够处理服务请求的;第二,将请求路由给不同底层的服务实例。
  • 负载均衡器的作用有两个:第一,确定底层的哪些实例是健康的,是能够处理服务请求的;第二,将请求路由给不同底层的服务实例。
  • 全面的测试工作能够确保不管发生可预测的还是不可预测的故障,我们所选择的设计方案都是有效的。测试工作包括压力测试和混沌测试
  • 对容量和扩容规划技术的深入探讨超出了本书的范围,但是在Abbott和Fisher的可伸缩性的艺术(The Art of Scalability)(Addison-Wesley Professional, 2015)(ISBN:978 -0134032801)一书对此有很好的概述。
  • 混沌测试会倒逼着微服务应用在生产环境中出现故障。通过引入不稳定性因素以及故障,混沌测试可以精确模拟真实系统的故障,同时也让工程团队得到训练,使得他们能够处理这些故障。

6.5 默认安全

  • 一种常见的确保服务间正确通信的方式是强制使用特定的类库:一套实现了断路器、重试和后备方案等常见的交互模式的类库

第7章 构建可复用的微服务框架

7.1 微服务底座

  • 开发者同样应该确保这个框架遵循了可观测性标准以及对基础设施相关代码的抽象

第三部分 部署

第8章 微服务部署

  • 由于微服务应用是以部署单元级别进行演进的,因此部署新服务的成本必须小到可以忽略不计,能够让工程师快速创新、引进新内容并向用户交付价值。

8.1 部署的重要性

  • 在软件系统的生命周期中,部署是风险最大的时刻。和现实世界最贴切的类比就是换轮胎——而且这辆车还在以约160km/h的速度飞驰着。没有哪个公司能够不受这一风险的影响:比如Google的网站可靠性(site reliability)团队认为大概有70%的服务不可用是由于对生产环境的修改导致的。
  • 在理想世界中,部署是“无聊的”,这里的“无聊”不是说这个过程不令人兴奋,而是说没有事故发生。我们已经见到过太多的团队——不管是单体应用的团队还是微服务的团队——他们部署软件的经历让人感到有难以置信的压力。如果采用微服务,这意味着开发者发布的组件数量更多、频率更快,这是否也意味着开发者正在将更多的风险和不稳定性引入到系统中呢?

8.2 微服务生产环境

1.1 什么是微服务应用

可以看到,微服务有三大关键特性。(1)每个微服务只负责一个功能。这个功能可能是业务相关的功能,也可能是共用的技术功能,比如与第三方系统(如证券交易所)的集成。(2)每个微服务都拥有自己的数据存储,如果有的话。这能够降低服务之间的耦合度,因为其他服务只能通过这个服务提供的接口来访问它们自己所不拥有的数据。(3)微服务自己负责编排和协作(控制消息和操作的执行顺序来完成某些有用的功能),既不是由连接微服务的消息机制来完成的,也不是通过另外的软件功能来完成的。
除了这三大特性,微服务还有两个基本特性。(1)每个微服务都是可以独立部署的。如果做不到这一点,那么到了部署阶段,微服务应用还是一个庞大的单体应用。(2)每个微服务都是可代替的。每个微服务只具备一项功能,所以这很自然地限制了服务的大小。同样,这也使得每个服务的职责或者角色更加易于理解。
服务与传统的面向服务架构(SOA)在思想上的一个关键区别就是微服务负责协调系统中的各个操作,而SOA类型的服务通常使用企业服务总线(ESB)或者更复杂的编排标准来将应用本身与消息和流程编排拆分开。在SOA模型下,服务通常缺乏内聚性,因为业务逻辑会不断地被添加到服务总线上,而非服务本身。
在《可扩展性的艺术》(The Art of Scalability)一书中,阿尔伯特(Abbott)和费舍尔(Fisher)定义了一个被称为“扩展立方体”的三维扩展方案
单体应用一般都是通过水平复制进行扩展的:部署多个完全相同的应用实例。这种方式也称作饼干模具(cookie-cutter)扩展或X轴扩展。相反,微服务应用是一个Y轴扩展的例子,我们将整个系统分解为不同的功能模块,然后针对每个模块自己特有的需求来进行扩展。
同样,自治和可独立部署意味着工程师可以分别管理这些微服务所对应的资源需
微服务应用具备一些很有意思的技术特性:按照单一功能来开发服务可以让架构师能够在规模和职责上很自然地划定界限;自治性使得开发者可以独立地对这些服务进行开发、部署和扩容。
支撑微服务开发的五大文化和架构原则为:自治性、可恢复性、透明性、自动化和一致性。
为了保证自治性,开发者需要将服务设计得松耦合、可独立部署。
尽管将应用拆分成多个服务能够隔离故障,但它还是会存在多点故障的问题。同样,当故障发生的时候,开发者需要能够解释到底发生了什么问题以避免连锁反应。这包括设计层面的(在可能的情况下支持异步交互以及适当地使用熔断器和超时),还包括运维层面的(比如,使用可验证的持续交付技术和对系统活动进行稳定可靠地监控)。
。不管在什么时候,系统都应该是透明的、可观测的,这样既可以发现问题,也可以对问题进行诊断。
微服务架构的流行与两种趋势是同时发生的—— 一种趋势是DevOps技术得到主流接纳,其中的典型就是基础设施即代码(infrastructure-as-code)技术;另一种趋势是完全通过API进行编程的基础设施环境(如AWS和Azure)的兴起。
软件开发的目标是持续地缩短交付周期来产生积极的商业价值
因此,我们相信一个良好的复杂系统应该能在整个生命周期内将冲突和风险这两个因素的影响最小化。
随着单体应用越来越大,下面这些几个因素会导致冲突。(1)变更周期耦合在一起,导致协作障碍并增大回滚的风险。(2)在没有严格规范的团队中,软件模块和上下文边界含混不清,导致组件之间产生意料之外的紧耦合。(3)应用的大小成为痛点:持续集成作业、系统发布(甚至是本地应用启动)都会变得越来越慢
小的服务也是持续交付的重要推动者。在大型应用中,部署的风险是很高的,而且涉及漫长的回归和验收周期。通过部署更小的功能元素,开发者可以降低每次独立部署的潜在风险,更好地隔离对线上系统的改动

1.2 微服务的挑战

设计和运行微服务应用时,开发者会遇到很多挑战,如下所示
(1)识别和划定微服务范围需要大量专业的业务领域知识。(2)正确识别服务间的边界和契约是很困难的,而且一旦确定,是很难对它们进行改动的。(3)微服务是分布式系统,所以需要对状态、一致性和网络可靠性这些内容做出不同的假设。(4)跨网络分发系统组件以及不断增长的技术差异性,会导致微服务出现新的故障形式。(5)越来越难以了解和验证在正常运行过程中会发生什么事情。
(3)可预测:准确反映了所有实现的真实表现。
微服务并不能消除风险,而是将这个成本移到了系统生命周期的后半阶段:降低了开发过程中的冲突,但是增加了运维阶段系统部署、验证以及监控的复杂度。
但是在不断变化的解耦系统中,清楚地了解整体的情况可能变得极度困难,这又使得问题诊断和支持变得更具有挑战性。当出现故障时,开发者需要通过一系列的方式来跟踪系统实际发生的行为(调用了哪个服务、顺序是什么以及输出是什么),但是还需要一些途径来了解系统应该发生的行为。
最后,工程师会面对微服务的两大运维挑战:可观测性和多点故障。

1.3 微服务开发生命周期

如果做不到这一点的话,开发者将要投入大量的精力来“通下水道”[在英文中“通下水道”(plumbing)用来比喻一些价值不大的脏活累活],而不能为客户创造任何价值。
我们已经听说过可靠的部署是很“单调”的。“单调”的意思不是说乏味无聊,而是说没有事故发生。

1.4 有责任感和运维意识的工程师文化

康威定律在某种层次上表达了类似的含义:设计系统的组织……都是受到约束的,其设计出来的方案只是这些组织的沟通结构的翻版。

2.3 开发新功能

开发一个最小可行产品(Minimum Viable Product,MVP)是非常重要的第一步。
,开发者会意识到以机器可读的格式将服务交互的接口标准化的显著好处,比如,REST API可以使用Swagger/OpenAPI。

2.5 将功能发布到生产环境中

巴士系数[插图]是用来衡量由团队成员之间的知识未被分享所造成的风险大小的指标,源自于“以防他们被巴士撞了”这句话,也被称作货车系数(truck factor)。巴士系数越低,团队的风险越大。
如,对于关键服务,开发者会保证在99.99%的可用性基础上,95%的请求能够在100毫秒内返回。如果没有达到这些阈值,就应该向服务所有者发送告警

第3章 微服务应用的架构

通常如何将微服务应用设计为四层结构——平台层、服务层、边界层和客户端层。

3.1 整体架构

架构师应该通过两种方式来指导开发:第一,准则——为了实现更高一层的技术目标或者组织目标,团队要遵循的一套指南;第二,概念模型——系统内部相互联系以及应用层面的模式的抽象模型。

3.5 服务边界

边界层还可以实现一些其他面向客户端的功能:认证和授权——验证API客户端的身份和权限;限流——对客户端的滥用进行防卫;缓存——降低后端整体的负载;日志和指标收集——可以对客户端的请求进行分析和监控。
服务于前端的后端(BFF)模式是API网关模式的一种变形。尽管API网关模式很简洁,但是它也存在一些缺点。如果API网关为多个客户端应用充当组合点的角色,它承担的职责就会越来越多。

第5章 微服务的事务与查询

之前只需要在数据库层面进行关联的查询操作现在需要调用多个服务才能实现。在某些使用场景中,这还是能够接受的,但是当数据集特别大时,这种方案就会变得非常麻烦。

5.1 分布式应用的事务一致性

种常见的方案就是使用二阶段提交(two phase commit,2PC)协议

5.2 基于事件的通信

这种方式称为编排。每个服务可以在不了解整个流程结果的情况下响应各种事件,独立执行各种操作。这些服务就如同舞蹈演员一般:他们知道每一段音乐的舞步和要做的动作,不需要有人显式地请求或者命令他们,就会按照音乐的变化给出相应的反应。相应地,这种设计方式解除了服务之间的耦合,提升了各个服务的独立性,并且简化了独立部署变更的复杂度。

5.3 Saga

在Saga中,每一步的操作都是由前一个步骤所触发的。
在Saga中,我们会用补偿操作来撤销之前的操作,并让系统恢复到更一致些的状态。系统不保证一定会恢复到最初的状态;
这种回滚形式的目的是让系统在语义上达到一致,而非数学意义上的一致。系统将一个操作回滚后并不一定能恢复到和之前完全一样的状态。
在构建反映真实世界环境的微服务时,预见到失败场景并做相应准备是很重要的一块内容,操作的隔离性反而没那么至关重要。在设计微服务时,我们需要把补偿考虑在内,以确保整个应用有足够的恢复能力。

5.4 分布式世界中的查询操作

缓存失效是众所周知的难题。[插图]
一个系统如果想要最大限度地提升可用性基本上都是靠补偿操作和重试——就像Saga那样。从架构的角度看,高可用性通常更容易实现一些,因为这能够降低协作成本,更加易于构建可扩展的应用。
埃里克·布鲁尔(Eric Brewer)最近发表的一篇文章CAP Twelve Years Later: How the "Rules" Have Changed对这种场景做了很好的概述。
这种命令-查询职责分离(CQRS)模式是一种应用于这种场景的通用模型,它显式地将系统[插图]中的读(查询)和写(命令)进行分离。
CQRS将服务分成了命令和查询两部分,每一部分分别由不同的数据存储来提供支持
,在CQRS模式中,服务的命令状态天然地会先于查询状态而得到更新,由于这种复制延迟(replication lag)的存在使得开发者需要考虑最终一致性。
而在另外一些系统中,确保用户查询不到无效的状态是非常重要的工作。在这种场景中,开发者可以采用图5.20所示的3种策略:乐观更新、轮询以及发布-订阅。
这种方式依赖于开发者拥有更新界面的所有信息,或者开发者能够根据输入数据得出这些信息。所以在用于一些简单场景时,这种方式效果最好。
我们还可以将CQRS技术推广到其他使用场景中,比如数据分析和报表。

6.1 可靠性定义

服务正常运行时间的百分比
在设计服务时,我们需要采取一种防御型的方式来满足3个目标:对于无法避免的故障要降低其发生率;对于无法预测的故障要控制其连锁影响,不要产生系统层面的影响;故障发生后,能够快速恢复(理想情况下可以自动恢复)。

6.3 设计可靠的通信方案

我们会探讨一些技术,以确保在协作方服务不可用时服务依旧能够最大限度地正确运行:重试;后备方案(fallback)、缓存和优雅降级;超时和最后期限;熔断器;通信代理(communication broker)。
我们期望获取数据的调用是幂等的(idempotent),也就是对目标系统的状态没有影响,并且是可以重复的[插图]。
Python中,开发者可以使用开源类库tenacity来在API客户端(holdings/clients.py文件中的MarketDataClient类)的对应方法上添加装饰器,如果方法抛出异常,则自动执行重试操作
但是如果故障是持续性的——比如,如果market-data服务的承载力下降了,后续的请求会使这一问题进一步恶化并导致系统稳定性下降
✍️
血崩
但是如果故障是持续性的——比如,如果market-data服务的承载力下降了,后续的请求会使这一问题进一步恶化并导致系统稳定性下降
我们该如何在不扩大系统故障范围的前提下通过重试技术来提高服务的可恢复性呢?首先,我们可以在这些重试操作之间使用一个变化的时间间隔来保证这些请求分布更加均匀,降低重试的那部分请求负载的频率。这就是所谓的指数退避(exponential back-off)策略,它的目的是给系统提供一个较低负载的时间,以便于恢复正常。
作为替代,指数退避应该包含一个随机元素——抖动(jitter)来将重试分散到一个更恒定的速率,避免过多的大量重试同时执行[插图]。
为了容忍间歇性的依赖型错误,重试是一种有效的策略。但是,在使用过程中,我们需要格外小心,以免导致底层问题进一步恶化或耗费不必要的资源:永远要限制重试的总次数;使用带抖动的指数退避策略来均匀地分配重试请求和避免进一步加剧负载;仔细考虑哪些错误情况应该触发重试,以及哪些重试不大可能成功、哪些重试永远不会成功。
如果某个服务的依赖项出现了故障,我们可以考虑4种后备方案:优雅降级、缓存、功能冗余和桩数据
路器需要发送试验性的请求,以判断连接是否恢复到健康状态。在这个试验状态中,断路器处于半开状态(half open):如果调用成功,断路器就会闭合;否则,继续保持断开状态

6.4 最大限度地提高服务可靠性

,负载均衡器的作用有两个:第一,确定底层的哪些实例是健康的,是能够处理服务请求的;第二,将请求路由给不同底层的服务实例。
负载均衡器的作用有两个:第一,确定底层的哪些实例是健康的,是能够处理服务请求的;第二,将请求路由给不同底层的服务实例。
全面的测试工作能够确保不管发生可预测的还是不可预测的故障,我们所选择的设计方案都是有效的。测试工作包括压力测试和混沌测试
对容量和扩容规划技术的深入探讨超出了本书的范围,但是在Abbott和Fisher的可伸缩性的艺术(The Art of Scalability)(Addison-Wesley Professional, 2015)(ISBN:978 -0134032801)一书对此有很好的概述。
混沌测试会倒逼着微服务应用在生产环境中出现故障。通过引入不稳定性因素以及故障,混沌测试可以精确模拟真实系统的故障,同时也让工程团队得到训练,使得他们能够处理这些故障。

6.5 默认安全

一种常见的确保服务间正确通信的方式是强制使用特定的类库:一套实现了断路器、重试和后备方案等常见的交互模式的类库

7.1 微服务底座

开发者同样应该确保这个框架遵循了可观测性标准以及对基础设施相关代码的抽象

第8章 微服务部署

由于微服务应用是以部署单元级别进行演进的,因此部署新服务的成本必须小到可以忽略不计,能够让工程师快速创新、引进新内容并向用户交付价值。

8.1 部署的重要性

在软件系统的生命周期中,部署是风险最大的时刻。和现实世界最贴切的类比就是换轮胎——而且这辆车还在以约160km/h的速度飞驰着。没有哪个公司能够不受这一风险的影响:比如Google的网站可靠性(site reliability)团队认为大概有70%的服务不可用是由于对生产环境的修改导致的。
在理想世界中,部署是“无聊的”,这里的“无聊”不是说这个过程不令人兴奋,而是说没有事故发生。我们已经见到过太多的团队——不管是单体应用的团队还是微服务的团队——他们部署软件的经历让人感到有难以置信的压力。如果采用微服务,这意味着开发者发布的组件数量更多、频率更快,这是否也意味着开发者正在将更多的风险和不稳定性引入到系统中呢?

8.2 微服务生产环境

微服务生产环境具备以下六大基础功能。(1)部署目标或者运行平台,也就是服务所运行的地方,
微服务架构的流行、DevOps实践(如基础设施即代码,infrastructure as code)的广泛采用以及使用云服务提供商来运行应用的情况日益增多,这三者同时存在并非巧合。这些实践能够让服务快速迭代和部署,从而使微服务架构成为一种可扩展和可行的方案。

第9章 基于容器和调度器的部署

容器并不是使用微服务的必要条件。开发者可以使用许多方法来部署服务,比如使用我们在第8章中提到的单虚拟机单服务模型。但是借助调度程序,容器提供了一种特别优雅和灵活的方法来满足我们的两个部署目标:速度和自动化

9.2 集群部署

对于微服务来说,调度器是一个引人注目的部署平台,因为它在理论上简化了对任意数量的独立服务的伸缩扩容、健康检查和发布的管理。它在确保有效利用底层基础设施的同时做到了这一点。从整体来看,容器调度程序工作流的工作过程如下所示。

第10章 构建微服务交付流水线

。部署流水线是构建持续交付的核心基石。

10.1 让部署变得平淡

理想的微服务部署流程应该满足两大目标。其一,节奏安全——新服务和变更的部署速度越快,开发者迭代开发和向终端用户交付成品的速度也就越快。部署工作应该尽可能提高安全性,开发者应该尽一切可能对给定的变更进行验证,确保其不会对服务的稳定性产生负面影响。其二,一致性——不管底层采用哪种技术栈,各个服务都采用同一套部署流程有助于降低技术隔离,并使操作更具可预测性和可扩展性。

11.1 稳固的监控技术栈

会把重点放在监控、度量指标和告警上。在第12章,我们会讲解日志和链路追踪,它们共同构成了可观测性。
我们列出了一套监控系统技术栈的各个组成部分:度量指标、日志和链路追踪。
我们讨论了架构层次:客户端、边界层、服务层和平台层。也应该把这4层都监控起来,因为开发者不可能在完全隔离的情况下确定一个特定组件的执行情况。网络问题最有可能影响服务的运行。如果开发者只在服务层面上收集度量指标,那么所能知道的也就只能是这个服务现在没有请求可处理。除此之外,对于问题出现的原因一无所知。而如果开发者同时还收集了基础设施层的度量指标,就可以了解到那些极有可能影响其他众多组件的问题
在从面向用户的系统中收集度量指标时,开发者应该关注四大黄金标志:时延、错误量、通信量和饱和度。
发者可以为每个服务创建多个展示详细信息的仪表盘,但是要保留最上层的仪表盘来展示最重要的信息。这个仪表盘应该能够方便开发者一眼判断出服务是否运行正常。它展示的应是服务的整体情况,其他更深入的信息应该出现更具体的仪表盘中。
者应该聚焦于最重要的部分,如响应时间、错误量和通信量。它们是可观测性的基石。开发者还应当为每个指标设定合适的百分位:99分位、95分位、75分位,等等。对

12.2 生成一致的、结构化的、人类可读的日志

开发者都应该在要记录的数据中尽可能多地使用唯一的标识符。当开发者交叉引用来自多个数据源的数据时,请求ID、用户ID和其他唯一标识符是非常重要的。有了这些标识符,开发者就能够非常高效地将不同数据源的数据分组。
开发者想要以人类可读的格式来生成日志记录,但是这些日志同时又需要使机器易于解析。
在显式调用logger模块时,开发者只需要声明所期望的等级以及所要记录的消息,这个消息以键值对的形式包含了消息文本和一个uuid。日志记录中的所有其他信息都是由Logstash自动收集和添加的,不需要人为显式声明。

12.3 为SimpleBank配置日志基础设施

在配置完日志聚合功能后,所有服务就开始同时向中心化系统发送度量指标和日志信息了,这能够提高系统的可观测性

12.4 服务间的跟踪交互

penTracing API是一个与供应商无关的分布式链路追踪开放标准。许多分布式跟踪系统(如Dapper、Zipkin、HTrace、X-Trace)都提供了链路追踪功能,但使用的是互不兼容的API。选择其中一个系统通常意味着可能要与使用不同编程语言的系统紧密耦合到一起,从而形成一个解决方案。OpenTracing的目的是为链路追踪的信息收集提供一组约定的、标准化的API。类库可用于不同的语言和框架。
trace是由单个或多个span组成的有向无环图(DAG),这些span的边称为reference。trace用于聚合和关联整个系统的执行流。为此,需要传播一些信息。一个trace记录整个流程。
每个span包含如下信息:操作名称、起始时间戳和完成时间戳、零个或者多个span标签(键值对)、零个或多个span日志(带时间戳的键值对)、span上下文(context)以及引用零个或多个span的参考(通过span上下文)。

13.1 建设高效团队

确保沟通渠道保持可控(图13.1展示了沟通关系的增长)。这有助于增强团队的活力和协作,同时缓解冲突。对于团队的“合适规模”存在许多探索,比如JeffBezos的两块披萨规则或Michael Lopp的7+/−3公式。
(2)清楚地描述了团队的责任和义务,同时提高了独立性和灵活性。
方面,如果团队之间的边界重叠且责任不明确,则可能加剧紧张情势;另一方面,各个独立的团队仍然需要相互协作来交付整个应用
康威定律展现了团队与系统之间的关联关系。……设计系统的组织受到限制所以设计出来的架构方案等价于组织的沟通结构。

13.2 团队模型

在本节,我们会考察两种组织团队的方式:按职能组织团队和跨职能组织团队,并会讨论这两种组织
(1)在按职能划分的方式中,我们按照专业来将人员分组,并采用职能化的汇报线方式,最后将这些人员分配到有时间限制的项目中。大多数组织投入资金支持的项目都是为了实现特定的需求,并有时间限制。衡量项目是否成功的依据就是他们有无准时交付并完成需求。(2)跨职能搭建的团队是由具有不同技能集的人员组成的团队,通常与长期的产品目标或远大的使命保持一致。在需求范围内,可以自由安排项目的优先级,并根据需要构建的功能完成这些任务。衡量团队是否成功的依据通常是其对业务关键性能指标(KPI)和结果的影响。
缺乏自治。这些团队之间是紧密耦合的,而不是自治的。他们的工作优先级是在其他地方制订的,当工作需要跨多个团队时,团队被阻塞和开发工作被阻碍的可能性就会增加。这将导致交付时间变长、返工、有质量问题和延期。如果不能与他们正在构建的系统架构保持一致,那么团队将无法在不被其他团队阻碍的情况下开发出自己的应用。
没有长期责任感。面向项目的方式不利于对所开发的代码或产品质量承担起长期责任。
过优化专业知识,按职能划分的方法旨在消除重复劳动以及由技能欠缺导致的效率低下,从而降低总体成本。但这会造成僵局:增加团队摩擦,降低实现组织目标的进度。这并不好,微服务架构旨在加快进度和减少摩擦。
由具有不同专长和角色的人员组成,旨在实现特定的业务目标。我们可以将这些团队称为市场驱动型团队
团队与业务价值保持一致将体现在所开发的应用中
我们在Amazon所使用的细粒度服务方法中,服务代表的不仅是软件结构,同时还代表了组织结构。这些服务拥有强大的所有权模型,再加上团队规模较小,使得创新变得非常容易。在某种意义上,开发者可以把这些服务看作大公司内部的小型初创企业。其中每一个服务都需要强烈关注它们的客户是谁,不管它们是外部的还是内部的。—— Werner Vogels
跨职能的团队应该有一个使命,这个使命是鼓舞人心的:它给团队提供了奋斗目标,但也设定了团队职责的边界
构建一个微服务平台——部署流程、底座、工具操作和监控系统——对于可持续和快速地构建一个良好的微服务应用是至关重要的。当刚开始使用微服务时,构建应用的团队通常也负责平台的构建任务(图13.9)。随着时间的推移,这个平台需要满足多个团队的需求,在此阶段,读者可能会开始建立一个平台团队(图13.10)。
可以缓解这些问题。我们成功地应用了Spotify的分会和协会模式[插图]。这些是社区实践:分会根据职能专长(如移动开发)对人员进行分组[插图]。协会围绕一个跨职能的主题分享实践,比如性能、安全。

阅读明细

维度指标
累积阅读天数23天
最长连续阅读天数5天
单日阅读最久46分 (2021/12/20)
阅读笔记条数105条
日期阅读时长
2021/12/2046分
2021/12/2127分
2021/12/228分
2021/12/232分
2021/12/2416分
2021/12/2617分
2021/12/275分
2021/12/2822分
2021/12/295分
2021/12/309分
2022/01/0115分
2022/01/0225分
2022/01/0314分
2022/01/0439分
2022/01/062分
2022/01/0710分
2022/01/0831分
2022/01/0923分
See all book notesSee all book notes