This the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

微服务架构

1 - 模式概览

设计目标

  • 缩减成本:降低设计、实现和维护 IT 服务的总体成本。
  • 加快发布:加快服务从想法到部署的落地速度。
  • 增强弹性:提升服务网络的弹性。
  • 开启可见性:支持为服务和网络提供更好的可见性。

设计原则

  • 可扩展性
  • 可用性
  • 韧性
  • 灵活性
  • 自治性—独立自主性
  • 去中心化治理
  • 故障隔离
  • 自动装配
  • 持续交付—基于 DevOps

模式概览

NAME

分解

按业务功能分解

说白了,微服务就是要应用单一职责原则,把服务改造成松耦合式的。它可以按照业务功能进行分解。定义和业务功能相对应的服务。业务功能是一个来自业务架构建模 [2] 的概念。它是一个企业为了创造价值而要去做的某些事情。一个业务功能往往对应于一个业务对象,比如:

  • 订单管理负责订单
  • 客户管理则是负责客户

按问题子域分解

按照业务功能来分解一个应用程序可能会是一个不错的开始,但是你终将会遇到所谓的“神类”,它很难再被分解。这些类将在多个服务之间都是通用的。可以定义一些和领域驱动设计(DDD)里面的子域相对应的服务。DDD 把应用程序的问题空间 —— 也即是业务 —— 称之为域。一个域由多个子域组成。每个子域对应业务的各个不同部分。

子域可以分为如下几类:

  • 核心:业务的核心竞争力以及应用程序最有价值的部分
  • 支撑:和业务有关但并不是一个核心竞争力。这些可以在内部实现也可以外包
  • 通用:不特定于业务,而且在理想情况下可以使用现成的软件实现

比如一个订单管理的子域包括:

  • 产品目录服务
  • 库存管理服务
  • 订单管理服务
  • 配送管理服务

按事务/2PC模式分解

你可以通过事务分解服务。然后,这样一来系统里将会存在多个事务。事务处理协调器[3]是分布式事务处理的重要参与者之一。分布式事务包括两个步骤:

  • 准备阶段 —— 在这个阶段,事务的所有参与者都准备提交并通知协调员他们已准备好完成事务
  • 提交或回滚阶段 —— 在这个阶段,事务协调器向所有参与者发出提交或回滚命令

2PC 的问题在于,和单个微服务的运行时间相比,它显得相当慢。即便这些微服务跑在相同的网络里,它们之间的事务协调也确实会减慢系统速度,因此这种方法通常不适用于高负载情况。

绞杀者模式:Strangler Pattern

上面三种,我们看到的这几个设计模式都是用来分解绿场(Greenfield)的应用程序,但是往往我们所做的工作中有 80% 是针对灰场(Brownfield)应用程序,它们是一些大型的单体应用程序(历史遗留的代码库)。

绞杀者模式可以解决这类问题。它会创建两个单独的应用程序,它们并排跑在同一个 URI 空间里。随着时间的流逝,直到最后,新重构的应用程序会“干掉”或替换原有的应用程序,此时就可以关掉那个老的单体应用程序。绞杀应用程序的步骤分别是转换、共存和消除[4]:

  • 转换:Transform,使用现代方法创建一个并行的全新站点。
  • 共存:Coexist,让现有站点保留一段时间。把针对现有站点的访问重定向到新站点,以便逐步实现所需功能。
  • 消除:Eliminate,从现有站点中删除旧功能。

隔仓模式:Bulkhead Pattern

让一个应用程序的元素和池子相对隔离,这样一来,其他应用程序将可以继续正常工作。这种模式被称为“隔舱”,因为它类似于船体的分段分区。根据使用者负载和可用性要求,将服务实例分成不同的组。这种设计有助于隔离故障,并允许用户即使在故障期间仍可为某些使用者维持服务。

边车模式:Sidecar Pattern

该模式将一个应用程序的组件部署到一个单独的处理器容器里以提供隔离和封装。它还允许应用程序由异构的组件和技术组成。这种模式被称为边车模式(Sidecar),因为它类似于连接到摩托车的侧边车。在该模式中,侧边车会附加到父应用程序,并为该应用程序提供功能支持。Sidecar 还与父应用程序共享相同的生命周期,并与父应用程序一起创建和退出。Sidecar 模式有时也称为 sidekick 模式,这是我们在文章中列出的最后一个分解模式。

集成

网关模式:API Gateway

当一个应用程序被分解成多个较小的微服务时,这里会出现一些需要解决的问题:

  • 存在不同渠道对多个微服务的多次调用
  • 需要处理不同类型的协议
  • 不同的消费者可能需要不同的响应格式

API 网关有助于解决微服务实现引发的诸多问题,而不仅限于上述提到的这些。

  • API 网关是任何微服务调用的单一入口点
  • 它可以用作将请求路由到相关微服务的代理服务
  • 它可以汇总结果并发送回消费者
  • 该解决方案可以为每种特定类型的客户端创建一个细粒度的 API
  • 它还可以转换协议请求并做出响应
  • 它也可以承担微服务的身份验证/授权的责任。

聚合器模式:Aggregator Pattern

将业务功能分解成几个较小的逻辑代码段后就有必要考虑如何协同每个服务返回的数据。不能把这个职责留给消费者。

聚合器模式有助于解决这个问题。**它讨论了如何聚合来自不同服务的数据,然后将最终响应发送给消费者。**这里有两种实现方式[6]:

  1. 一个组合微服务将调用所有必需的微服务,合并数据,然后在发送回数据之前对其进行转换合成。
  2. 一个 API 网关还可以将请求划分成多个微服务,然后在将数据发送给使用者之前汇总数据。

如果要应用一些业务逻辑的话,建议选择一个组合式的微服务。除此之外,API 网关作为这个问题的解决方案已经是既定的事实标准。

代理模式

针对 API 网关,我们只是借助它来对外公开我们的微服务。引入 API 网关后,我们得以获得一些像安全性和对 API 进行分类这样的 API 层面功能。在这个例子里,API 网关有三个 API 模块:

  1. 移动端 API,它实现了 FTGO 移动客户端的 API

  2. 浏览器端 API,它实现了在浏览器里运行的 JavaScript 应用程序的 API

  3. 公共API,它实现了一些第三方开发人员需要的 API

网关路由模式

API 网关负责路由请求。一个 API 网关通过将请求路由到相应的服务来实现一些 API 操作。当 API 网关接收到请求时,它会查询一个路由映射,该路由映射指定了将请求路由到哪个服务。一个路由映射可以将一个 HTTP 方法和路径映射到服务的 HTTP URL。这种做法和像 NGINX 这样的 Web 服务器提供的反向代理功能一样。

链式服务模式

单个服务或者微服务将会有多级依赖,举个例子:Sale 的微服务依赖 Product 微服务和 Order 微服务。链式微服务设计模式将帮助你提供合并后的请求结果。microservice-1 接收到请求后,该请求随后与 microservice-2 进行通信,还有可能正在和 microservice-3 通信。所有这些服务都是同步调用。

分支模式

一个微服务可能需要从包括其他微服务在内的多个来源获取数据。分支微服务模式是聚合器和链式设计模式的混合,并允许来自两个或多个微服务的同时请求/响应处理。调用的微服务可以是一个微服务链。分支模式还可用于根据你的业务需求调用不同的微服务链或单个链。

客户端UI组合模式

通过分解业务功能/子域来开发服务时,负责用户体验的服务必须从多个微服务中提取数据。在一个单体世界里,过去只有一个从 UI 到后端服务的调用,它会检索所有数据然后刷新/提交 UI 页面。但是,现在不一样了。

对于微服务而言,我们必须把 UI 设计成一个具有屏幕/页面的多个板块/区域的框架。每个板块都将调用一个单独的后端微服务以提取数据。诸如 AngularJS 和 ReactJS 之类的框架可以帮助我们轻松地实现这一点。

这些屏幕称为单页应用程序(SPA)。每个团队都开发一个客户端 UI 组件,比如一个 AngularJS 指令,该组件实现其服务的页面/屏幕区域。UI 团队负责通过组合多个特定服务的 UI 组件来实现构建页面/屏幕的页面框架。

数据库

给微服务定义数据库架构时,我们需要考虑以下几点:

  1. 服务必须是松耦合的。这样它们可以独立开发,部署和扩展

  2. 业务事务可能会强制跨越多个服务的不变量

  3. 一些业务事务需要查询多个服务的数据

  4. 为了可扩展性考虑,数据库有时候必须是可复制和共享的

  5. 不同服务存在不同的数据存储要求

每个服务一套数据库

为了解决上述问题,必须为每个微服务设计一个数据库。它必须仅专用于该服务。应当只能通过微服务的 API 访问它。其他服务无法直接访问它。比如,针对关系型数据库,我们可以采用每个服务使用单独的专用表(private-tables-per-service),每个服务单独的数据库模式(schema-per-service)或每个服务单独的数据库服务器(database-server-per-service)。

服务间共享数据库

我们已经说过,在微服务里,为每个服务分配一套单独的数据库是理想方案。采用共享数据库在微服务里属于反模式。但是,如果应用程序是一个单体应用而且试图拆分成微服务,那么反正规化就不那么容易了。在后面的阶段里,我们可以转到每个服务一套数据库的模式,直到我们完全做到了这一点。服务之间共享数据库并不理想,但是对于上述情况,它是一个切实可行的解决方案。大多数人认为这是微服务的反模式,但是对于灰场应用程序,这是将应用程序分解成更小逻辑部分的一个很好的开始。值得一提的是,这不应当应用于绿场应用程序。

命令和查询职责分离 (CQRS)

一旦实现了每个服务分配单独一套数据库(database-per-service),自然就会产生查询需求,这需要联合来自多个服务的数据。然而这是不可能的。CQRS 建议将应用程序分成两部分 —— 命令端和查询端。

  • 命令端处理创建,更新和删除请求
  • 查询端通过使用物化视图来处理查询部分

这通常会搭配事件驱动模式(event sourcing pattern)一起使用,一旦有任何数据更改便会创建对应的事件。通过订阅事件流,我们便可以让物化视图保持更新。

事件驱动

绝大多数应用程序需要用到数据,典型的做法就是应用程序要维护当前状态。例如,在传统的创建,读取,更新和删除(CRUD)模型中,典型的数据流程是从存储中读取数据。它也包含了经常使用事务导致锁定数据的限制。

事件驱动模式[7]定义了一种方法,用于处理由一系列事件驱动的数据操作,每个事件都记录在一个 append-only 的存储中。应用程序代码向事件存储发送一系列事件,这些事件命令式的描述了对数据执行的每个操作,它们会被持久化到事件存储。每个事件代表一组数据更改(例如,AddedItemToOrder)。

这些事件将保留在充当记录系统的一个事件存储里。事件存储发布的事件的典型用途是在应用程序触发的一些动作更改实体时维护这些实体的物化视图,以及与外部系统集成。例如,一个系统可以维护一个用于填充 UI 部分所有客户订单的物化视图。当应用程序添加新订单,添加或删除订单中的项目以及添加运输信息时,描述这些更改的事件将会得到处理并用于更新物化视图。下图展示了该模式的一个概览。

NAME

Saga 模式

当每个服务都有它们自己的数据库,并且一个业务事务跨越多个服务时,我们该如何确保各个服务之间的数据一致性呢? 每个请求都有一个补偿请求,它会在请求失败时执行。这可以通过两种方式实现:

  • 编舞(Choreography) —— 在没有中央协调的情况下,每个服务都会生成并侦听另一个服务的事件,并决定是否应该采取措施。编舞是一种指定两个或多个参与方的方案。任何一方都无法控制对方的流程,或者对这些流程有任何可见性,无法协调他们的活动和流程以共享信息和值。当需要跨控制/可见性域进行协调时,请使用编舞的方式。参考一个简单场景,你可以把编舞看作和网络协议类似。它规定了各方之间可接受的请求和响应模式。

    NAME
  • 编排(Orchestration) —— 一个编排器(对象)会负责 saga 的决策和业务逻辑排序。此时你可以控制流程中的所有参与者。当它们全部处于一个控制域时,你可以控制该活动的流程。当然,这通常是你被指派到一个拥有控制权的组织里制定业务流程。

    NAME

可观测性

日志聚合

考虑一个应用程序包含多个服务的用例。请求通常跨越多个服务实例。每个服务实例均采用标准格式生成日志文件。我们需要一个集中式的日志记录服务,该服务可以汇总每个服务实例的日志。用户可以搜索和分析日志。他们可以配置在某些消息出现在日志中时触发告警。例如,PCF 就有日志聚合器,它在应用侧从 PCF 平台的每个组件(router、controller、diego等)收集日志。AWS Cloud Watch 也是这样做的。

性能指标

当服务组合由于引入了微服务架构而增加时,保持对事务的监控就变得尤为关键了,如此一来就可以监控这些模式,而当有问题发生时便会发送告警。

此外,需要一个度量服务来收集有关单个操作的统计信息。它应当聚合一个应用服务的指标数据,它会用来报告和告警。这里有两种用于汇总指标的模型:

  • 推送 —— 服务将指标推送到指标服务,例如 NewRelic,AppDynamics
  • 提取 —— 指标服务从服务中提取指标,例如 Prometheus

分布式链路追踪

在微服务架构里,请求通常跨越多个服务。每个服务通过跨越多个服务执行一个或多个操作来处理请求。在排障时,有一个 Trace ID 是很有帮助的,我们可以端对端地跟踪一个请求。

解决方案便是引入一个事务ID。可以采用如下方式:

  • 为每个外部请求分配一个唯一的外部请求ID
  • 将外部请求ID传递给处理该请求链路的所有服务
  • 在所有日志消息中加入该外部请求ID

健康检查

实施微服务架构后,服务可能会出现启动了但是无法处理事务的情况。每个服务都需要有一个可用于检查应用程序运行状况的 API 端点,例如 /health。该 API 应该检查主机的状态,与其他服务/基础设施的连接以及任何其他特定的逻辑。

横切关注点

外部配置

一个服务通常还会调用其他服务和数据库。对于dev,QA,UAT,Prod等每个环境而言,API 端点的 URL 或某些配置属性可能会有所不同。这些属性中的任何一个更改都可能需要重新构建和重新部署服务。

为避免代码修改,可以使用配置。把所有配置放到外面,包括端点 URL 和证书。应用程序应该在启动时或运行时加载它们。这些可以在启动时由应用程序访问,也可以在不重新启动服务器的情况下进行刷新。

服务发现模式

在微服务出现时,我们需要在调用服务方面解决一些问题。

借助容器技术,IP地址可以动态地分配给服务实例。每次地址更改时,消费端服务都会中断并且需要手动更改。

对于消费端服务来说,它们必须记住每个上游服务的 URL ,这就变成紧耦合了。

为此,需要创建一个服务注册中心,该注册表将保留每个生产者服务的元数据和每个服务的配置。服务实例在启动时应当注册到注册中心,而在关闭时应当注销。服务发现有两种类型:

  • 客户端:例如:Netflix Eureka
  • 服务端:例如:AWS ALB
NAME

熔断器模式

一个服务通常会通过调用其他服务来检索数据,而这时候下游服务可能已经挂了。这样的话,有两个问题:首先,请求将继续抵达挂了的服务,耗尽网络资源,并且降低性能。其次,用户体验将是糟糕且不可预测的。

消费端服务应通过代理来调用远程服务,该代理的表现和一个电流断路器类似。当连续的故障数超过阈值时,断路器将跳闸,并且在超时期间内,所有调用远程服务的尝试都会立即失败。超时到期后,断路器将允许有限数量的测试请求通过。如果这些请求成功,断路器则将恢复正常运行。否则,如果发生故障的话,超时时间则将再次重新开始计算。如果某些操作失败概率很高的话,采取此模式有助于防止应用程序在故障发生后仍然不断尝试调用远程服务或访问共享资源。

NAME

蓝绿部署模式

使用微服务架构时,一个应用可以被拆分成许多个微服务。如果我们采用停止所有服务然后再部署改进版本的方式的话,宕机时间将是非常可观的,并且会影响业务。同样,回滚也将是一场噩梦。 蓝绿部署模式可以避免这种情况。

实施蓝绿部署策略可以用来减少或消除宕机。它通过运行两个相同的生产环境,Blue 和Green 来实现这一目标。 假设 Green 是现有的活动实例,Blue 是该应用程序的新版本。在任何时候,只有一个环境处于活动状态,该活动环境为所有生产流量提供服务。所有云平台均提供了用于实施蓝绿部署的选项。

NAME

参考资料

2 - 微服务灾难

2014 年 Martin Fowler 发表了一篇关于微服务的文章,当时,我所在的团队正在构建面向服务的架构。这篇文章以及随后的炒作几乎影响了世界上所有的软件团队。那时,“Netflix OSS 栈”是世界上最酷的东西,它可以让世界各地的工程师在分布式系统中使用 Netflix 的经验。六年多过去了,如果我们现在来看看软件工程的工作,就会发现,其中大部分都是关于微服务的架构的。

炒作驱动开发

在 2010 年代的早期,很多组织都面临着软件开发周期的挑战。与其他 50、100 或 200 名工程师一起工作的人,他们在开发环境、QA 过程和程序部署方面都很困难。Martin Fowler 的《Continuous Delivery》(译注:尚无中译本)一书给许多团队带来了曙光,他们开始意识到,他们那“雄伟”的单体应用正给他们带来组织问题。所以,微服务对软件工程师很有吸引力。在一个大项目中引入持续交付或部署,而不是一开始就引入,更具有挑战性。

于是,团队开始拆分三个、十个、一百个微服务。其中大部分都使用**“JSON over HTTP”**(其他人可能会称之为 RESTful)API 来在这些组件之间实现远程调用。人们对 HTTP 协议非常熟悉,这看起来是一种将单体应用转换成小程序块的简单方法。这时,团队在 15 分钟之内就开始将代码部署到生产环境中。再也没有“哦,团队 A 破坏了 CI 管道,我不能部署我的代码”这样的情况了,这种感觉棒极了!

但是,大多数工程师都忘了,在解决软件架构层面的组织问题的同时,他们也引入了许多复杂性。分布式系统的谬误变得越来越明显,并很快让这些团队感到头疼。甚至那些已经在做客户机 / 服务器架构的公司,当他们的系统中有超过 10 个移动部分时,也会出现这种情况。

现实的反击

做出重大的架构改变并非没有成本。团队开始认识到,共享数据库是一种单点故障。后来他们意识到,他们各自的领域创造了一个全新的世界:最终的一致性就是一件事。在你提取数据的服务失败后该怎么办?很多问题开始堆积如山。高速开发速度的承诺被寻找错误、事件和数据一致性问题等压得喘不过气。另外一个问题是,工程师需要一种集中的日志和可观察性解决方案,在几十个服务中发现并纠正这些缺陷。

灾难:服务规模过小

随着开发人员创造力的爆发,每天都能创造出新的服务。一项新功能?咣当,让我们开始服务吧!突然之间,20 名工程师组成了维护 50 项服务的小组。一人负责一项服务还不够!一般而言,代码的问题在于它会“腐烂”。维护每一项服务都是要付出代价的。想象一下,在你的服务团队中传播一个库的升级。再想象一下,这些服务开始于不同的时间点,具有不同的架构、业务逻辑和所使用的框架之间的纠葛。那是多么可怕啊!解决这些问题的方法当然是有的。其中大部分都不能使用,而其他一些则需要花费很多 FTE 工作。

另外一种感觉是,我被告知,在服务 A 中部署新功能,并且在服务 B 中同时部署,或者当人们开始编写服务以生成 CSV 时。为什么会有人引入网络跳转,以产生世界上已知的文件格式?这东西谁来维护?有些团队正在受服务之苦。更糟的是,它在开发过程中会产生许多摩擦。与仅仅在 IDE 中查看一个项目不同,人们需要一次打开多个项目才能了解所有这些混乱的情况。

灾难:开发环境

我已经记不清有多少次有人走近我说:

“嘿,João。你有时间吗?我们需要改善开发环境了!大家都在抱怨这些事,可是都没用!”

这一问题涉及各个层面。移动开发者不需要在开发环境中开发功能就可以实现,或者后端开发者想要尝试他们的服务而不会破坏任何业务流程。如果有人想在生产之前在移动应用中测试整个过程,这也是一个问题。

跨分布式系统的开发环境存在一些问题,尤其是规模方面:

  1. 在云供应商中启动 200 个服务需要花费多少钱?你能做到吗?你是否能够启动运行他们所需的基础设施呢?
  2. 这需要多长时间呢?加入一个移动工程师开始开发一项功能,在给定的版本中有一组服务,当这些服务完成之后,有 10 个新版本被部署到生产中,那会怎样?
  3. 测试数据怎么样?你是否拥有所有服务的测试数据?在整个 Fleet 中都保持一致,所以用户和其他实体相匹配?
  4. 当你开发一个多租户、多区域的应用时,如何配置和功能标志?怎样跟上生产进度?若同时更改缺省值呢?

这些只是冰山一角而已。你可能会考虑将工程技术应用于这个问题。那也许行得通。但是,我怀疑大多数组织是否有足够大的规模来完成这项工作。这样做既麻烦又费钱。

灾难:端到端测试

不难想象,端到端测试和开发环境有相似的问题。在此之前,使用虚拟机或容器创建新的开发环境相对简单。同样,使用 Selenium 创建测试套件非常简单,它可以在部署新版本之前通过业务流并判断它们是否在工作。有了微服务,即使我们能够解决以上关于构建环境的所有问题,我们也不能再次宣布系统正在运行。我们至多可以这样说,运行特定版本的服务和特定配置的系统可以在特定的时间点上正常工作。真是大不相同啊!

要让人们相信我们只能进行几次这样的测试是非常困难的。而且在持续集成(Continuous Integration)流程中运行这些测试并不够。它们应该持续运行。它们应该针对生产运行情况发出相应的警报。我已经分享了无数次 Cindy Sridharan 的文章《在生产中测试,安全的方法》(Testing in production, the safe way),试图让人们理解我的观点。

灾难:巨大的共享数据库

一种简单的方法就是继续使用共享数据库,这样就可以避免单体应用,同时保证数据的一致性。这种方法不会增加操作负荷,而且可以轻松地一步一步地切割单体应用。但它也有相当大的缺点。这不仅是一个明显的单点故障,而且违背了面向服务架构的一些原则。你是否为每项服务创建一个用户?你是否具有细粒度的权限,以便服务 A 只能读写特定的表?假如某人不小心删除了索引怎么办?怎样知道有多少服务使用了不同的表?那扩容呢?

解决这些问题本身就变成了一个全新的难题。在技术上,这可能不是一个无关紧要的小问题,因为数据库经常比软件寿命长。用数据复制来解决问题——不管是 Kafaka、AWS DMS 还是其他什么——都需要你的工程团队理解数据库的细节,以及如何处理重复时间等等。

灾难:API 网关

在面向服务的架构中,API 网关是一种典型模式。它们帮助解耦后端与前端消费者。在实施端点聚合、速率限制或跨系统认证方面,它们也有用。近来,业界倾向于 backend-for-frontend(BFF,服务于前端的后端)的架构,将网关部署到前端的每个消费者群体(iOS、Android、Web 或桌面应用),从而解耦它们的进化。

和世界上的任何东西一样,人们开始有了新的创造性用例。有时这只是一个小技巧,使移动应用能够向后兼容。突然间,你的 API 网关变成了一个单点故障,因为人们发现在一个地方进行认证更加容易,其中还包含一些出乎意料的业务逻辑。现在,你不再有一个获得所有流量的单体应用,而是有一个自己开发的 Spring Boot 服务来或许所有的流量!会出什么问题呢?工程人员很快意识到这是个错误,但是由于存在大量的定制,有时候他们不能用它来取代无状态的、可扩展的定制。

当使用未分页的端点或返回大量响应时,就会导致 API 网关灾难。又或者,如果你在没有后备机制的情况下进行聚合,仅仅调用一次 API 就会“烧毁”你的网关。

灾难:超时、重试、弹性

分布式系统经常处于局部故障模式。如果服务 A 不能与服务 B 取得联系,会发生什么?咱们可以再试一次,对吗?但是这很快让我们陷入了困惑之中。我见过有些团队使用断路器,然后对下游服务进行 HTTP 调用时会超时。尽管这可能是一种正常的反应,为我们争取了一些时间来解决问题,但是它会产生二阶效应。所有这些请求都将被断路器取消,因为它们太长,在断路器上的时间太长。随着流量的增加,会有越来越多的请求进入队列,结果会比你希望修复的更糟。工程师们都在努力理解队列理论,理解为什么会出现超时现象。同样的事情发生在团队开始讨论 HTTP 客户端的线程池等问题时。尽管对这些东西进行配置本身就是一种艺术,但基于直觉来设置数值会使你陷入严重的停机状态。

在从失败中恢复的过程中,一个棘手的问题是并非所有的失败都一样。有些情况下,我们会希望我们的消费者是等幂的。但是这意味着我们应该积极的决定每一种失败情况下该怎么做。消费者是否等幂?能否重试这个调用?在认识到存在巨大的数据完整性问题之前,我已经看到许多工程师忽视了这些,因为它们只是“边缘情况”。

即使你设置了后备机制,重试也比所有这些更加复杂。假设你的移动应用有 500 万用户,而更新用户首选项的消息总线暂时无法运行。你创建了一个支持这种情况的后备机制,该机制通过 HTTP API 调用用户的首选项服务。你应该知道我在说什么吧。如今,该服务突然出现了巨大的流量尖峰,可能无法应付所有的流量。更糟糕的是:你的服务可能接收到所有这些新请求,但是如果重试机制不能实现指数退避和抖动,那么你就可能遇到来自移动应用的分布式拒绝服务。

看到所有这些灾难,你还喜欢分布式系统吗?

要是我告诉你,我只是写下了我所看到的灾难中的一小部分呢?分布式系统很难掌握,而且大多数软件工程师只是在最近才持续接触到它们。

好消息是,对于我所说的很多灾难,我们都能找到解决方案,行业已经创造除了更好的工具,使得除了美国五大科技巨头(Facebook、苹果、亚马逊、Netflix、谷歌)之外的其他组织都能解决这些问题。

我还是喜欢分布式系统,而且我还是觉得微服务是一个解决组织问题的好方法。但是,当我们把失败看作“边缘案例”或者我们认为不可能发生的事时,问题就出现了。在一定范围内,这些边缘案例成为新常态,我们应该加以应对。

原文链接:https://world.hey.com/joaoqalves/disasters-i-ve-seen-in-a-microservices-world-a9137a51

3 - 故障容错模式

软件故障的容错方法如果用一句话来简单概况的话也简单:通过定义规则来容忍系统缺陷。但这样的定义未免过于大而空,我们需要切实有效可落地的方式。下面介绍9种常用的处理方式。

容错、熔断、隔离?

  • “隔离”是一种异常检测机制,常用的检测方法是请求超时、流量过大等。一般的设置参数包括超时时间、同时并发请求个数等。

  • “熔断”是一种异常反应机制,“熔断”依赖于“隔离”。熔断通常基于错误率来实现。一般的设置参数包括统计请求的个数、错误率等。

  • “容错”是一种异常处理机制,“容错”依赖于“熔断”。熔断以后,会调用“容错”的方法。一般的设置参数包括调用容错方法的次数等。

Process Pairs

也就是最简单的backup方案,保证系统在某一个时刻总能有一个进程来处理客户的输入请求,能处理短暂的软件错误。

Graceful Degradation

就是我们常说的降级,在系统遭遇某个错误之后不提供完整功能,只给用户开放部分基础能力,此解决方案通常是上面的backup方案持续性不work的时候采取的保护措施。

Selective Retry

选择性重试也是可选的方案之一,它主要适用于是突发式高负载资源短缺的场景,例如,网络瞬时打满峰值不可访问或者内存资源短缺,重试能够增加资源分配成功的可能性。

State Handling

在系统不能提供服务后,又要保证client的无状态属性。服务端需要持续保存当前的状态,用于故障后的重试。

Linking Process

有些程序进程是相互依赖的,如果某个进程出错,其他依赖的进程需要侦测到错误,明确做相应的处理,通常是结束全部依赖进程。

Checkpoint

周期性的保存进程的状态。如果需要保证数据正确,回滚到最近保存的状态即可,只是会有部分的数据丢失。

Update Lost

上面方案的补充版,在两个checkpoint之间系统故障,需要保存客户请求,在rollback前一个版本之后重新处理这些请求。

Process Pools

使用资源预分配技术,按照经验设定好某些请求资源的需求量,为程序分配合适的资源。就像我们为某个任务分配线程池大小一样。

Micro reboot

通过解耦系统组件,使得系统在遭遇故障时,只需要重启需要的组件,而不必重启整个系统。核心是组件和数据分离,数据的处理通过持久化存储的方式保证一致。