Files
CategoryResourceRepost/极客时间专栏/左耳听风/弹力设计/46 | 弹力设计篇之“补偿事务”.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

157 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="46 | 弹力设计篇之“补偿事务”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/2c/d6/2c10416326c5ec6952f40cf8ebe5f3d6.mp3"></audio>
前面,我们说过,分布式系统有一个比较明显的问题就是,一个业务流程需要组合一组服务。这样的事情在微服务下就更为明显了,因为这需要业务上一致性的保证。也就是说,如果一个步骤失败了,那么要么回滚到以前的服务调用,要么不断重试保证所有的步骤都成功。
这里如果需要强一致性那在业务层上就需要使用“两阶段提交”这样的方式。但是好在我们的很多情况下并不需要这么强的一致性而且强一致性的最佳保证基本都是在底层完成的或是像之前说的那样Stateful的Sticky Session那样在一台机器上完成。在我们接触到的大多数业务中其实只需要最终一致性就够了。
# ACID 和 BASE
谈到这里有必要先说一下ACID和BASE的差别。传统关系型数据库系统的事务都有ACID属性即原子性Atomicity、一致性Consistency、隔离性Isolation又称独立性、持久性Durability。下面我逐一做下解释
<li>
**原子性**整个事务中的所有操作要么全部完成要么全部失败不可能停滞在中间某个环节。事务在执行过程中发生错误会被回滚Rollback到事务开始前的状态就像这个事务从来没有执行过一样。
</li>
<li>
**一致性**:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
</li>
<li>
**隔离性**:两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时中间某一时刻的数据。两个事务不会发生交互。
</li>
<li>
**持久性**:在事务完成以后,该事务对数据库所做的更改便持久地保存在数据库之中,并不会被回滚。
</li>
事务的ACID属性保证了数据库的一致性比如银行系统中转账就是一个事务从原账户扣除金额以及向目标账户添加金额这两个数据库操作的总和构成一个完整的逻辑过程是不可拆分的原子操作从而保证了整个系统中的总金额没有变化。
然而这对于我们的分布式系统来说尤其是微服务来说这样的方式是很难满足高性能要求的。我们都很熟悉CAP理论——在分布式的服务架构中一致性Consistency、可用性Availability、分区容忍性Partition Tolerance在现实中不能都满足最多只能满足其中两个。
所以为了提高性能出现了ACID的一个变种BASE。
<li>
**Basic Availability**:基本可用。这意味着,系统可以出现暂时不可用的状态,而后面会快速恢复。
</li>
<li>
**Soft-state**:软状态。它是我们前面的“有状态”和“无状态”的服务的一种中间状态。也就是说,为了提高性能,我们可以让服务暂时保存一些状态或数据,这些状态和数据不是强一致性的。
</li>
<li>
**Eventual Consistency**:最终一致性,系统在一个短暂的时间段内是不一致的,但最终整个系统看到的数据是一致的。
</li>
可以看到BASE系统是允许或是容忍系统出现暂时性问题的这样一来我们的系统就能更有弹力。因为我们知道在分布式系统的世界里故障是不可避免的我们能做的就是把故障处理当成功能写入代码中这就是Design for Failure。
BASE的系统倾向于设计出更加有弹力的的系统这种系统的设计特点是要保证在短时间内就算是有数据不同步的风险我们也应该允许新的交易可以发生而后面我们在业务上将可能出现问题的事务给处理掉以保证最终的一致性。
举个例子,网上卖书的场景。
ACID的玩法就是大家在买同一本书的过程中每个用户的购买请求都需要把库存锁住等减完库存后把锁释放出来后续的人才能进行购买。于是在ACID的玩法下我们在同一时间不可能有多个用户下单我们的订单流程需要有排队的情况这样一来我们就不可能做出性能比较高的系统来。
BASE的玩法是大家都可以同时下单这个时候不需要去真正地分配库存然后系统异步地处理订单而且是批量的处理。因为下单的时候没有真正去扣减库存所以有可能会有超卖的情况。而后台的系统会异步地处理订单时发现库存没有了于是才会告诉用户你没有购买成功。
BASE这种玩法其实就是亚马逊的玩法因为要根据用户的地址去不同的仓库查看库存这个操作非常耗时所以不想做成异步的都不行。
在亚马逊上买东西,你会收到一封邮件说,系统收到你的订单了,然后过一会儿你会收到你的订单被确认的邮件,这时候才是真正地分配了库存。所以,有某些时候,你会遇到你先收到了下单的邮件,过一会又收到了没有库存的致歉的邮件。
有趣的是ACID的意思是酸而BASE却是碱的意思因此这是一个对立的东西。其实从本质上来讲ACID强调的是一致性CAP中的C而碱BASE强调的是可用性CAP中的A
# 业务补偿
有了上面对ACID和BASE的分析我们知道在很多情况下我们是无法做到强一致的ACID的。特别是我们需要跨多个系统的时候而且这些系统还不是由一个公司所提供的。比如在我们的日常生活中我们经常会遇到这样的情况就是要找很多方协调很多事而且要保证我们每一件事都成功否则整件事就做不到。
比如,要出门旅游, 我们需要干这么几件事。第一,向公司请假,拿到相应的假期;第二,订飞机票或是火车票;第三,订酒店;第四,租车。这四件事中,前三件必需完全成功,我们才能出行,而第四件事只是一个锦上添花的事,但第四件事一旦确定,那么也会成为整个事务的一部分。这些事都是要向不同的组织或系统请求。我们可以并行地做这些事,而如果某个事有变化,其它的事都会跟着出现一些变化。
设想下面的几种情况。
<li>
我没有订到返程机票,那么我就去不了了。我需要把订到的去程机票,酒店、租到的车都给取消了,并且把请的假也取消了。
</li>
<li>
如果我假也请好了,机票,酒店也订好了,只是车没租到,那么并不影响我出行这个事,整个事还是可以继续的。
</li>
<li>
如果我的飞机因为天气原因取消或是晚点了,那么我被迫要去调整和修改我的酒店预订和租车的预订。
</li>
从人类的实际生活当中,我们可以看出,上述的这些情况都是天天在发生的事情。所以,我们的分布式系统也是一样的,也是需要处理这样的事情——就是当条件不满足,或是有变化的时候,需要从业务上做相应的整体事务的补偿。
一般来说,业务的事务补偿都是需要一个工作流引擎的。亚马逊是一个超级喜欢工作流引擎的公司,这个工作流引擎把各式各样的服务给串联在一起,并在工作流上做相应的业务补偿,整个过程设计成为最终一致性的。
对于业务补偿来说,首先需要将服务做成幂等性的,如果一个事务失败了或是超时了,我们需要不断地重试,努力地达到最终我们想要的状态。然后,如果我们不能达到这个我们想要的状态,我们需要把整个状态恢复到之前的状态。另外,如果有变化的请求,我们需要启动整个事务的业务更新机制。
所以,一个好的业务补偿机制需要做到下面这几点。
<li>
要能清楚地描述出要达到什么样的状态(比如:请假、机票、酒店这三个都必须成功,租车是可选的),以及如果其中的条件不满足,那么,我们要回退到哪一个状态。这就是所谓的整个业务的起始状态定义。
</li>
<li>
当整条业务跑起来的时候,我们可以串行或并行地做这些事。对于旅游订票是可以并行的,但是对于网购流程(下单、支付、送货)是不能并行的。总之,我们的系统需要努力地通过一系列的操作达到一个我们想要的状态。如果达不到,就需要通过补偿机制回滚到之前的状态。**这就是所谓的状态拟合**。
</li>
<li>
对于已经完成的事务进行整体修改,可以考虑成一个修改事务。
</li>
其实,在纯技术的世界里也有这样的事。比如,线上运维系统需要发布一个新的服务或是对一个已有的服务进行水平扩展,我们需要先找到相应的机器,然后初始化环境,再部署上应用,再做相应的健康检查,最后接入流量。这一系列的动作都要完全成功,所以,我们的部署系统就需要管理好整个过程和相关的运行状态。
# 业务补偿的设计重点
业务补偿主要做两件事。
1. 努力地把一个业务流程执行完成。
1. 如果执行不下去,需要启动补偿机制,回滚业务流程。
所以,下面是几个重点。
<li>
因为要把一个业务流程执行完成,需要这个流程中所涉及的服务方支持幂等性。并且在上游有重试机制。
</li>
<li>
我们需要小心维护和监控整个过程的状态,所以,千万不要把这些状态放到不同的组件中,最好是一个业务流程的控制方来做这个事,也就是一个工作流引擎。所以,这个工作流引擎是需要高可用和稳定的。这就好像旅行代理机构一样,我们把需求告诉它,它会帮我们搞定所有的事。如果有问题,也会帮我们回滚和补偿的。
</li>
<li>
补偿的业务逻辑和流程不一定非得是严格反向操作。有时候可以并行,有时候,可能会更简单。总之,设计业务正向流程的时候,也需要设计业务的反向补偿流程。
</li>
<li>
我们要清楚地知道,业务补偿的业务逻辑是强业务相关的,很难做成通用的。
</li>
<li>
下层的业务方最好提供短期的资源预留机制。就像电商中的把货品的库存预先占住等待用户在15分钟内支付。如果没有收到用户的支付则释放库存。然后回滚到之前的下单操作等待用户重新下单。
</li>
# 小结
好了我们来总结一下今天分享的主要内容。首先我介绍了ACID和BASE两种不同级别的一致性。在分布式系统中ACID有更强的一致性但可伸缩性非常差仅在必要时使用BASE的一致性较弱但有很好的可伸缩性还可以异步批量处理大多数分布式事务适合BASE。
要实现BASE事务需要实现补偿逻辑因为事务可能失败此时需要协调各方进行撤销。补偿的各个步骤可以根据具体业务来确定是串行还是并行。由于补偿事务是和业务强相关的所以必须实现在业务逻辑里。下篇文章中我们讲述重试设计。希望对你有帮助。
也欢迎你分享一下你的分布式服务用到了怎样的一致性?你是怎么实现补偿事务的?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)