Skip to content

Commit

Permalink
fix typo
Browse files Browse the repository at this point in the history
  • Loading branch information
isno committed Jun 23, 2024
1 parent 0ebbbe3 commit 69cfd3f
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 9 deletions.
9 changes: 5 additions & 4 deletions distributed-transaction/BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@

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

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

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

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

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

假设有这么一个电商系统,下单需要 3 个服务支持:支付服务(银行扣款)、库存服务(扣除购买商品的库存)、积分服务(为用户增加积分)。下单过程中**最核心、出错影响最大的服务优先处理**,即:支付扣款 -> 仓库出库 -> 为用户增加积分,整个过程如图 6-2 所示。

:::center
![](../assets/BASE.svg)
图 5-2 可靠事件队列模型
:::

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


以上这种依靠持续重试来保证可靠性的解决方案在计算机的其他领域中也被频繁使用,它还有专有的名称 —— “最大努力交付(Best-Effort Delivery)”。

可靠事件队列还有一种更普遍的形式,被称为“最大努力一次提交(Best-Effort 1PC)”,指的是**将最有可能出错的业务以本地事务的方式完成后,采用不断重试的方式来促使同一个分布式事务中其他关联的业务全部完成**
Expand Down
14 changes: 9 additions & 5 deletions distributed-transaction/TCC.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 5.3.2 TCC

可靠消息队列模型虽然能保证最终结果的相对可靠性,过程也足够简单,但整个过程完全没有隔离性可言,如果你的业务需要隔离,那你就应该考虑下面将要介绍一种适合强隔离性的分布式事务模型 —— TCC(Try,Confirm,Cancel)。
可靠消息队列模型虽然能保证最终结果的相对可靠性,过程也足够简单,但整个过程完全没有隔离性可言,如果你的业务需要隔离,那你就应该考虑下面这种更适合强隔离性的分布式事务模型 —— TCC(Try,Confirm,Cancel)。

TCC 事务模型最早出现在 2007 年数据库专家 Pat Helland 发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》论文中[^1],不过该论文中 TCC 还是以 Tentative-Confirmation-Cancellation 作为名称,在国内经历阿里程立博士的传道之后,TCC 逐渐被大家广为了解并接受。

Expand All @@ -10,14 +10,16 @@ TCC 是一种对业务侵入性较强的事务方案,要求业务处理过程
2. **Confirm 阶段**:如果所有分支的 Try 都成功了,则走到 Confirm 阶段。Confirm 真正执行业务,使用 Try 阶段预留的业务资源来完成处理。
3. **Cancel 阶段**:如果所有分支的 Try 有一个失败了,则走到 Cancel 阶段,释放 Try 阶段预留的业务资源。

按照 TCC 的协议规定,Confirm 和 Cancel 阶段只返回成功,不会返回失败,如果由于网络问题或者服务器临时故障,那么事务管理器会进行重试直至最终成功,**所以这两个阶段的操作要求具备幂等性**。笔者继续以前篇下单为案例,说明 TCC 事务处理流程,不过我们稍微简化下逻辑,案例中去除不那么重要的积分服务,只保留支付、仓库服务。
按照 TCC 的协议规定,Confirm 和 Cancel 阶段只返回成功,不会返回失败,如果由于网络问题或者服务器临时故障,那么事务管理器会进行重试直至最终成功,**所以这两个阶段的操作要求具备幂等性**

这里,我继续以前篇下单的过程为案例,说明 TCC 事务处理流程,不过我们稍微简化下逻辑,案例中去除不那么重要的积分服务,只保留支付、仓库服务。

:::center
![](../assets/TCC.svg)
![](../assets/TCC.svg)<br/>
图 5-3 TCC 事务模型
:::

1. 用户向商店发送一个交易请求,譬如购买价值 ¥100 的某个商品。
1. 用户向商店发送一个交易请求,如购买价值 ¥100 的某个商品。
2. 创建事务,生成事务 ID,记录在活动日志中,进入 Try 阶段。
- 支付服务:检查业务可行性,若可行,将用户的 100 元设置为冻结状态,通知下一步进行 Confirm 阶段;若不可行,通知下一步进入 Cancel 阶段。
- 仓库服务:检查业务可行性,若可行,将仓库该商品的其中一条库存设置为冻结状态,通知下一步进行 Confirm 阶段;若不可行,通知下一步进入 Cancel 阶段。
Expand All @@ -30,6 +32,8 @@ TCC 是一种对业务侵入性较强的事务方案,要求业务处理过程
- 仓库服务:取消业务操作,释放被冻结的库存。
6. 如果第 5 步全部完成,事务宣告以失败回滚结束,如果第 5 步中有任何异常,将根据活动日志中的记录,重复执行异常方的 Cancel 阶段,进行最大努力交付尝试。

由上述操作过程可见,TCC 其实有点类似 2PC 的准备阶段和提交阶段,但 TCC 位于用户代码层面,而不是在基础设施层面,这为它的实现带来了较高的灵活性,可以根据需要设计资源锁定的粒度。不过呢,感知各个阶段的执行情况以及推进执行下一个阶段需要编写大量的逻辑代码,不仅仅是调用一下 Confirm/Cancel 那么简单。通常的情况,我们不需要靠裸编码来实现 TCC,而是使用某些分布式事务中间件(譬如 Seata、ByteTCC)来降低编码工作,提升开发效率。
由上述操作过程可见,TCC 其实有点类似 2PC 的准备阶段和提交阶段,但 TCC 位于用户代码层面,而不是在基础设施层面,这为它的实现带来了较高的灵活性,可以根据需要设计资源锁定的粒度。

不过,感知各个阶段的执行情况以及推进执行下一个阶段需要编写大量的逻辑代码,不仅仅是调用一下 Confirm/Cancel 那么简单。通常的情况,我们不需要靠裸编码来实现 TCC,而是使用某些分布式事务中间件(譬如 Seata、ByteTCC)来降低编码工作,提升开发效率。

[^1]: 参见 http://adrianmarriott.net/logosroot/papers/LifeBeyondTxns.pdf

0 comments on commit 69cfd3f

Please sign in to comment.