沃尔玛架构翻新:如何保证微服务领域的业务连续性和灾难恢复

沃尔玛在美国几乎所有州及全球许多国家/地区提供杂货店提货和配送服务。沃尔玛的集成配送系统由应用程序和后端系统组成,使全球员工可以满足各地商店的全渠道电商订单需求。

电商环境下的订单交付

近年来,这套系统的业务量取得了巨大的增长:

引用来源:https://techcrunch.com/2019/08/13/walmart-tops-u-s-online-grocery-market-with-62-more-customers-than-next-nearest-rival/

为了支持如此大的规模,我们决定对这套产品进行现代化改造和架构翻新。

同时,有一项关键要求是保持业务的连续性。系统中的任何生产问题都会影响全球各地的客户。这套系统"不能"在"保证的时间表"之外的时间下线。

灾难恢复(DR)

分布式计算的谬论(https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing),是L Peter Deutsch(https://en.wikipedia.org/wiki/L_Peter_Deutsch)等人在描述人们对分布式系统所做的错误假设时观察到的一系列结果。在云世界中,基础架构栈更加密集,并且包含许多不受我们控制的组件。这意味着其中一些谬论会更加明显。

当一套云部署宕机时,我们需要系统从“另一个位置”继续为客户提供服务。灾难恢复(DR)是一种设计结构,允许这套服务以及相关的基础架构组件(如消息代理和数据库等)在另一个数据中心继续运行。

高层架构

像这样的系统通常被设计为多个微服务的形式,这些微服务使用消息传递和API协作,以实现所需的行为。每个服务都有一个属于自己的数据库——从而加强了关注点隔离和清晰的合约。为了便于讨论,下图描绘了这种高层架构的概况:

一些“前端”服务从应用接受请求,然后与其他“功能”服务一起工作以驱动系统的用例。“事件驱动”这一术语也适用于这些类型的系统,因为每种服务之间都是松散耦合的,并且只响应事件(消息)。

这种微服务的部署方式称为“环(Ring)”。

复写

DR解决方案的第一个模式是远程数据中心中数据(数据库)的可用性。最朴素/简单的方法是,对主区域中DB的写操作也会写入远程区域。具体而言,当本地和远程DB都保存了写入的数据时,DB写操作才算完成。这里的问题是:

  • 这些写操作跨越了WAN链接,并且不提供严格的延迟SLA(服务级别协议)。因此DB写入时间会延长且无法保证。
  • 现在,系统可用性的公式中包含了其他一些组件:远程DB,本地DB和远程DB之间的网络链接。复合系统的可靠性永远比各个组件要小,这意味着系统整体的可靠性会下降。

为了避免牺牲性能和可用性,通常使用的模式是“异步复制”;也就是说,当本地DB提交时DB写入就完成了,随后事务会被“运送”到远程DB。在远程备用站点上,事务会与主DB中发生的对应事务异步保存。

例如,以下图表和引用描述了SQL Server使用的分布式可用性组(DAG)技术:

引用来源:https://docs.microsoft.com/zh-cn/sql/database-engine/availability-groups/windows/distributed-availability-groups

你可能会注意到消息传递没有被复制——这是因为事务性分布式系统很难将复制的消息传递状态与DB状态拼接在一起。如下文所述,使用DB重播消息要容易得多。

走进微服务的世界

在微服务架构中,每个服务都有自己的DB集。它们的复制全都是彼此独立的:

异步复制

这里的问题是,系统整体状态自身分散在多个服务中,但是多个微服务的事务传送没有沿着“清晰一致的路线”进行协调。因此,远程复制的DB集中某个特定的“即时”/快照上的状态集合可能是不一致的,或者在灾难发生时不可用!

为了说明这个问题,请考虑以下流程:

在这里两个DB发生了变异,当它们在远程数据中心中复制时,两个远程DB可以处于4种可能的状态:

+---+-----------------+--------------------+
| # |    ServiceX     | CapabilityServiceP |
+---+-----------------+--------------------+
| 1 | ObjectA deleted | ObjectB deleted    |
| 2 | ObjectA present | ObjectB deleted    |
| 3 | ObjectA deleted | ObjectB preset     |
| 4 | ObjectA present | ObjectB preset     |
+---+-----------------+--------------------+

第一种和最后一种场景都是没问题的(是的,即使“丢失数据”也可以),因为整个系统现在处于“已知的最后一种一致状态”上,然后我们就可以建立机制恢复一致性了。更糟糕的情况是系统实际上处于一种不一致的状态——也就是场景2和3。 在这种跨服务事务复制中可以浮现出3个问题:

  1. 损坏的引用:ServiceX中的状态指向了CapabilityServiceP中不存在的状态。因此,当应用(客户端)发起了一个流程,迫使ServiceX调用CapabilityServiceP时,接下来的交互可能会中断——在这里后者并不存在针对前者所述实体的引用。
  2. 脑裂(Split-brain):很多时候,诸如CapabilityServiceP之类的服务提供了整个系统状态的统一视图,其具体做法是显示不同应用程序的物化视图。在DR期间,可能发生的一种情况是物化视图拥有某些数据,但事实来源却没有。这将导致“我在搜索页面上看到了这个项目,但是当我转到详细信息页面时却跳出来了404!”之类的问题。
  3. 悬空引用(Dangling Reference):这种现象在内存管理中通常是这样描述的:“父” ServiceX没有对对象(“OrderB”)的引用,但是“子”CapabilityServiceP具有有关“OrderB”的记录。反规范化(重复信息以避免连接并提升性能)会加剧这种状况。如果编码不正确,这些悬空的引用可能会导致许多难以调试的问题。例如在上面的示例中,如果CapabilityServiceP用来给出订单数量的估算值,那么估算的结果就会是错误的。

过去人们在备份的语境中也研究过这个问题,如标题为“微服务的持续灾难恢复:BAC定理”中的这篇IEEE论文所述。BAC代表备份—可用性—一致性,由Pardon和Cesare Pautasso与Olaf Zimmermann共同创立。本质上它是著名的CAP定理的派生,具体内容是:

“在备份整个微服务架构时,不可能同时具有可用性和一致性。”

引用来源:https://ieeexplore.ieee.org/document/8327550

现在我们已经找到了问题所在,下面来看看该如何解决它。

协调

使微服务集达到已知的最后一种一致状态上的主要设计模式名为协调(Reconciliation)。本质上讲,每个服务都会保留一个实体变更日志(EML),其与时间戳一起变化。提货服务的EML如下所示:

+---------+------------+----------+---------------+---------+---------------------+
| OrderId | EntityType | EntityId |   Operation   | Service |      Timestamp      |
+---------+------------+----------+---------------+---------+---------------------+
| abc13   | ORDER      | NA       | PICK_COMPLETE | SELF    | 2019-01-16 15:16:45 |
| abc123  | Container  | CO123    | CREATE        | STORAGE | 2019-01-16 15:13:45 |
+---------+------------+----------+---------------+---------+---------------------+

这种构造也称为记录前写入(Write-Ahead-LoG,WAL),在Cassandra这样的数据库中用于类似的持久性保障用途。

有了它以后,在故障转移到远程站点时,指定的“bully”微服务会重播最近n分钟内的突变。这里的n是可调参数,涵盖了DB复制技术提供的“有限过期(Bounded Staleness)”保证。在这里描述的系统中,重播通常意味着重建和重新发送消息,同时其他服务会侦听。每个“non-bully"/下游服务会消费相关消息并构建其状态。

幂等

协调工作的关键要求是幂等(Idempotency),就是说服务需要能够处理重复的消息而不会更改最终的结果状态。不管怎样,这成为了消息驱动微服务事实上的需求,因为消息传递系统(broker)提供了“至少一次”语义。因为各种边缘状况,消息在到达消费者的途中可能会重复。

注意:Kafka之类的某些系统会宣传自己的“只一次”语义,但它们没强调的附加条件是这只在范围很窄的架构中才成立——不过这个话题又得写一篇文章来展开了 :P。

高层解决方案

那么我们总结一下到目前为止的讨论:

  • 这套微服务必须部署在多个位置(“云”)。我们将每套部署称为一个“环”。
  • 复制和协调与其他构造(例如基于GSLB的负载平衡和一个Monitor)一起,共同形成了最终的解决方案。

高层架构

Monitor是一种健康状态监视和命令解决方案。它具有2级架构——由一个Worker和Master架构组成。Worker位于环中,并从环中的服务收集统计信息,如CPU /内存利用率、API延迟、DB延迟和磁盘吞吐量等。它通过每个组件和其他监视信标发布的健康状态检查API来监控这些信息。

有许多框架(如Spring Boot Actuators)可用来模板化针对各个服务的健康状态检查。可以用一个小型包装库来增强这里的工作,这个库会对常见组件(如消息传递和DB)运行健康状态检查。这种库可以使服务的测量报告保持一致——如下所示:

{
  "app": {
    "state": "UP",
    "name": "servicex",
    "id": "8c13a2@servicex"
  },
  "db": {
    "status": "UP"
  },
  "broker": {
    "status": "UP"
  }
}

Monitor Worker使用这些信号来判断服务的健康状态。然后它将摘要和详细报告发送给Monitor Master。接下来Monitor Master将在全球范围内拼接环的单窗格视图,并在需要时命令DR启动故障转移。

基础指标示例——这里是Elasticsearch

业务指标示例——API请求和响应时间

根据这些指标,规则就可以判断出环是否处于健康状态。

所有API均会通过基于GSLB的负载均衡器。这样以来就可以实现基于DNS的故障转移——应用无需更改其配置的FQDN(全限定域名),而只需更改FQDN引用的VIP即可转到故障转移站点上。基本上,应用每次查找DNS时,GSLB系统都会提供服务的虚拟IP地址(VIP),指向这个应用上下文中的当前活动/健康集群(此处的上下文指的是国家等信息)。

启动DR故障转移后,Monitor master将启动一个工作流程,如下所示:

  1. 将所有流量重定向到一个临时503服务器上,停止对故障主环的API调用
  2. 执行DB故障转移
  3. 运行协调
  4. 调整GSLB以将FQDN指向故障转移环

值得一提的是,并非所有故障都需要DR。故障转移的成本很高,我们需要按下文所述有选择地按下开关。

应用弹性

触发故障转移后,对后端服务的API调用将经历一段时间的中断。当然,我们已将故障转移时间限制在一定范围内,但在这段时间内仍然会破坏客户体验。

为了帮助解决这一问题及其他一些问题(例如网络不稳定),应用被设计为更具弹性,以便用户可以在后端断线的情况下继续使用这些应用。

下面在高层级别上描述了所采用的模式:

应用弹性

我们基本上使用了以下构造:

  • 预取——在启动时获取所有需要的资源(例如图片)
  • 本地存储——将详细信息存储在应用持久性存储上,以便这些信息在应用重启时可用
  • 后台处理——这是应用和后端使用的一个协议,该协议将数据存储在本地存储中,并与后端同步。这包括标识每个UI事务,并在后端将事务拼接为一个有序列表。

当然,这里有很多细节被掩盖了——特别是诸如可靠性、重新传输、幂等性和应用本身被破坏情况下的业务流程之类的东西。这些会在将来的文章中具体阐述。

结果——RPO和RTO

DR解决方案的两个关键指标是恢复点目标(RPO)和恢复时间目标(RTO)。在关键任务应用中两者都是至关重要的,且需要针对不同的用例具体调整。

通过上面的设计,我们能够做到"即时"RTO和RPO。实际的工作流程需要花费几分钟的时间才能完成,但是由于应用在故障转移期间具有弹性,因此"感知"的RPO和RTO是即时的!

当然,在实际的用例中,某些流量会被阻塞,直到后端再次启动为止。但是这类情况的数量很少,即使在整个后端都崩溃的情况下,这一解决方案也允许客户继续使用应用。

成本优化

上面的设计描述了一种主动—被动架构。正常操作时不动用远程部署。上面的设计会使用环的“配对”,并允许双主动行为来优化成本。

每个环都有一个“配对”,当发生故障转移时,配对的存储将连接到一个幸存环。如下所示:

主动—主动环

致谢

我们的员工非常喜爱这套系统,他们创作了一首很棒的说唱歌曲来表达自己的感受:

https://youtu.be/JRlTtnb7CZU

♫♫“点开这个应用”♫♫

这项工作是沃尔玛实验室许多工程师合作努力的结果。主要贡献者包括(按字母顺序排列)Abiy Hailemichael、Igor Yarkov、Kislaya Tripathi、Nitesh Jain和Noah Paci。

作者介绍

Jyotiswarup Raiturkar是沃尔玛实验室电子商务部分的首席架构师,还是软件工程师/投资人。

原文链接Business Continuity & Disaster Recovery in the Microservices world

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/rSDDGJHeEj9fzrYM1WyG

扫码关注云+社区

领取腾讯云代金券