应用场景
随着业务的增长,单个数据库已经无法完成业务需求。需要对业务进行拆分。不同的业务逻辑可能操作不同的数据库。这时,系统中的某些操作与其他操作有依赖关系。为了保证数据操作的业务完整性,我们要求某些操作与其关联系统要么全部操作成功,要么全部操作失败。这就要求我们要在整个系统层面实现事务控制。
分布式事务的常用解决方案
二阶段提交算法
在分布式系统中,虽然单独节点可以知道自己节点操作的成败,但是不能知道其他节点的成败状况。为了解决这个问题,二阶段提交算法在分布式系统中引入了一个中间协调者。分布式系统各节点与中间协调者通信,中间协调者感知各节点的操作状态,然后根据汇总结果协调其他节点的成败。
二阶段提交算法分为两个阶段,准备阶段,提交阶段
准备阶段操作过程为:
- 协调者向每一个参与节点发起事务询问,等待各个节点响应。
- 参与节点执行事务,不提交事务
- 并将执行结果反馈给协调者
提交阶段过程为:
对于准备阶段,一般情况会出现三种情况
- 所有节点全部正常执行了事务
- 一个或者多个节点执行事务出现异常
- 协调者等待节点响应超时
针对第一种情况,协调者会对所有参与节点发出commit请求,具体步骤如下:
- 协调者分别向参与节点发出commit请求
- 参与者执行commit,并释放事务资源
- 参与者向协调者返回事务执行结果
针对二三情况,协调者会向所有参与节点发出rollback请求,具体操作如下:
- 协调者分别向参与者发出rollback请求
- 参与者执行回滚,释放资源
- 参与者将回滚结果反馈给协调者
主要缺点
- 单点故障,在整个体系中,协调者处于比较核心的位置。在运行过程中,如果协调者出现了故障,将导致整个系统不可用。如果在提交阶段,协调者不能正常收发消息,那么整个系统中的参与者将处于阻塞状态。
- 效率问题,在系统事务同步的过程中,整个系统参与者都处于阻塞阶段。不能参与其他操作,这样会影响到系统的效率。
- 数据不一致问题,在提交阶段,如果协调者出现故障,使得一部分参与者接收到commit,另一部分接受者没有接收到commit。将导致参与节点的数据出现不一致问题。
三阶段提交算法
与二阶段提交算法相比,三阶段主要增加了与询问阶段,增加系统的可用性。
三阶段提交共分can-commit,pre-commit,do-commit
状态机方案:
假如有若干个服务顺序执行,并且每个服务都提供配套的回滚服务;后续服务如果执行失败,回调前续服务的回滚服务。对于超时情况,这个比较复杂,因为链接超时可能有两种情况
- 前置服务发送消息时出现超时,这种情况后续服务没有执行操作。
- 前置服务已经成功发送消息,并且后续服务也执行了请求。只是后续服务在返回时出现超时。这种情况下,后续服务其实已经执行了操作。
为了解决这个问题,我们可以在后续服务中增加一些操作记录表,一旦后续服务执行了前置服务的请求,更新记录数据。这样如果出现请求超时,可以通过后续服务进行查询确认后续服务是否已经执行了请求操作。
主要缺点
- 请求链中,如果出现了crash,已经宕机的服务不能自己恢复。
- 这个方案要求前置设计提供回滚接口,一是增加开发量,二是不可能要求每一个服务开发者都能提供回滚接口。
消息驱动的一致性方案
方案1 消息数据与前置服务业务数据保存在一个数据库中
假如有若干服务进行顺序执行,在执行前置服务成功后,将消息以异步的方式写入消息数据表中。后置服务可以轮询查询消息更新状态,发现有新消息,则进行后续消息业务逻辑。如果消费失败,后续业务满足幂等,可以进行重试。增设后续服务操作记录日志,可以不要求后续服务幂等。假设有两个服务A,B
|
优点
- 能满足业务一致性,性能较好
- 将消息数据与业务数据保存到一个数据库中的好处是,插入消息和业务DML可以很方便的实现原子性
主要缺点
- 消息跟业务共用数据库资源。使得对业务进行拆分变得困难。
- 系统没有隔离性,消息一旦产生就必须被消费,如果消息无法消费,需要人工介入。系统维护成本较高。
方案2:将消息数据与业务数据解耦,分库
执行前置业务前先记录操作消息数据,这时可以标识消息数据的状态为不可消费。当前置业务操作完毕,业务消息将消息置为可消费状态。假设有两个服务A,B
|
优点
- 能满足业务一致性,性能较好
- 消息与业务数据分开存储,实现消息业务的解耦。方便业务分层扩展。
主要缺点
- 一次操作,要将消息发送两次,一次插入消息,一次更新消息状态;增加了系统的运行成本。
- 系统没有隔离性,消息一旦产生就必须被消费,如果消息无法消费,需要人工介入。系统维护成本较高。