沃尔玛在美国几乎所有州及全球许多国家 / 地区提供杂货店提货和配送服务。沃尔玛的集成配送系统由应用程序和后端系统组成,使全球员工可以满足各地商店的全渠道电商订单需求。
电商环境下的订单交付
近年来,这套系统的业务量取得了巨大的增长:
引用来源: https://techcrunch.com/2019/08/13/walmart-tops-u-s-online-grocery-market-with-62-more-customers-than-next-nearest-rival/
为了支持如此大的规模,我们决定对这套产品进行现代化改造和架构翻新。
同时,有一项关键要求是保持业务的连续性。系统中的任何生产问题都会影响全球各地的客户。这套系统 " 不能 " 在 " 保证的时间表 " 之外的时间下线。
分布式计算的谬论( 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 写操作才算完成。这里的问题是:
为了避免牺牲性能和可用性,通常使用的模式是“异步复制”;也就是说,当本地 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 个问题:
过去人们在备份的语境中也研究过这个问题,如标题为“微服务的持续灾难恢复: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。
那么我们总结一下到目前为止的讨论:
高层架构
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 将启动一个工作流程,如下所示:
值得一提的是,并非所有故障都需要 DR。故障转移的成本很高,我们需要按下文所述有选择地按下开关。
触发故障转移后,对后端服务的 API 调用将经历一段时间的中断。当然,我们已将故障转移时间限制在一定范围内,但在这段时间内仍然会破坏客户体验。
为了帮助解决这一问题及其他一些问题(例如网络不稳定),应用被设计为更具弹性,以便用户可以在后端断线的情况下继续使用这些应用。
下面在高层级别上描述了所采用的模式:
应用弹性
我们基本上使用了以下构造:
当然,这里有很多细节被掩盖了——特别是诸如可靠性、重新传输、幂等性和应用本身被破坏情况下的业务流程之类的东西。这些会在将来的文章中具体阐述。
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