5.3.1 可靠事件队列

2008 年,eBay 的系统架构师 Dan Pritchett 在 ACM 发表了论文 “Base: An Acid Alternative“[1],论文中 Dan Pritchett 基于实践总结出一种独立于 ACID 获得强一致性之外的、通过引入消息队列和幂等来达成一致性目的系统化技术手段,并提出了最终一致性的概念。

从论文的名字中就可以看出:最终一致性的概念与 ACID 强一致性对立。因为 ACID 在英文中有的”酸“的含义,这个模型明显刻意拼凑成 BASE(BASE 英文中有碱的含义)。有 ACID vs BASE(酸 vs 碱)这个计算机浑然天成的梗,Dan Pritchett 论文被广泛传播,BASE 理论和最终一致性也被大家熟悉。

BASE

BASE 分别是 Basically Available(基本可用)、Soft State(软状态)和 Eventually Consistent(最终一致性)三个短语的缩写。其核心思想即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性

Dan Pritchett 在论文中的提出的实现最终一致性技术手段非常有价值,总结可称为基于可靠事件队列的事件驱动模式,可靠事件队列关键在于可靠事件的投递和避免事件重复消费(幸运的是现在流行的消息中间件都实现了事件的持久化和最少一次 的投递模式,此外幂等性实现也有非常成熟的方案,所以这些要求也不再是问题)。

下面笔者以一个具体的案例说明 Dan Pritchett 提出的”可靠事件队列“的具体做法。譬如有这么一个电商系统,下单需要 3 个服务支持:支付服务(银行扣款)、库存服务(扣除购买商品的库存)、积分服务(为用户增加积分)。下单流程最核心、出错影响最大的服务优先处理,即:支付扣款 -> 仓库出库 -> 为用户增加积分,整个过程如图所示。

  • 用户向商店发送一个交易请求,譬如购买一支价值 ¥100 的钢笔。
  • 支付服务创建一个本地的扣款事务,如果扣款成功,则在自己的数据库内建立一张消息表,表内如下结构:事务ID,扣款¥100(状态:已完成),仓库出库(状态:待进行),赠送积分(状态:待进行)。
  • 在系统内建立一个消息服务,定时轮询消息表,将状态是”进行中“的消息同时发送到库存和积分服务节点中。这个时候会产生以下几种情况:
    • 仓库服务和积分服务都顺利完成了出库和加分的工作,向支付服务返回执行结果,支付服务把消息改为”已完成“。整个事务顺利完成,最终实现一致性。
    • 仓库服务和积分服务至少有一个因网络问题,未能收到来自支付服务的消息。此时,由于支付服务存储的消息状态一直处于“进行中”,所以消息服务器将在每次轮询的时候持续地向未响应的服务重复发送消息。这个重复的操作决定着所有被消息服务器发送的消息操作都必须具备幂等性(幂等性设计可以参考 5.3.4) 节内容。如此,出库以及增加积分的动作只会被处理一次。此过程持续自动重复至双方通信恢复正常。
    • 仓库服务和积分服务因某个原因无法完成处理,譬如仓库发现商品无货,此时,仍然继续持续发送消息,直至操作成功(譬如补充库存),或者被人工介入终止。由此可见,可靠消息队列方式只要第一步成功了,后续就没有失败回滚的概念,只许成功,不许失败

以上这种依靠持续重试来保证可靠性的解决方案实际并不是 Dan Pritchett 的独创,它在计算机的其他领域中被频繁使用,也有了专有的名称 -- ”最大努力交付“(Best-Effort Delivery)。而可靠事件队列还有一种更普遍的形式,被称为”最大努力一次提交“(Best-Effort 1PC),指的是将最有可能出错的业务以本地事务的方式完成后,采用不断重试的方式来促使同一个分布式事务中其他关联的业务全部完成


  1. 参见 https://queue.acm.org/detail.cfm?id=1394128 ↩︎

总字数:1199
Last Updated:
Contributors: isno