分布式事务:Saga 数据一致性

分布式事务实现方式

  1. 主要有二阶提交协议(Two Phase Commitment Protocol)、三阶提交协议(Three Phase Commitment Protocol)和Paxos算法。
  2. X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。 二阶提交协议和三阶提交协议就是根据这一思想衍生出来的。
  3. Paxos算法。
  4. Saga实现。

二阶提交

  1. 有两个阶段第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
  2. 在准备阶段,由协调者传递带有事务的信息给其他参与者,并且询问它们是否可以提交事务,然后等待返回结果。
  3. 提交阶段,只要任何一个参与者返回执行失败,那么就中断事务,协调者向所有参与者发送rollback请求,参与者进行回滚。
  4. 存在的问题:
    1. 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
    2. 单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
    3. 数据不一致。在二阶段提交的阶段中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
    4. 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

三阶提交

  1. 3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
  2. CanCommit:协调者发出含有事务的请求,询问参与者是否能提交事务,然后等待返回结果。
  3. PreCommit:只要有任何一个参与者返回错误或超时,则到第三布进行事务中断操作。如果都正常返回,就到第三步执行事务提交。
  4. DoCommit:如果接受到是中断操作,那么就向所有参与者发送中断请求,让他们执行回滚。
  5. 存在的问题:
    1. 相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
  • tips: 了解了2PC和3PC之后,无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。

复杂的Paxos算法 -- 网上已经有很多写Paxos算法的文章了,没有必要去赘述,这里做个简单介绍

  1. paxos算法解决的是最终一致性问题。它包含了两个角色 => 提议者,接收者
  2. 举个例子,假设一个集群有多个节点,Paxos如何让这些节点达成数据一致呢? 具体做法主要有两步:
  3. 第一个阶段是选举,还没有提出提议。在这个阶段,我们大家(多个节点 -- 接收者)从提议者中选举出能带领我们做出提议的领导,这里使用了序列号来标识谁更具有当选资格(序列号越高,选举资格越高)。
  4. 第二个阶段主要根据第一阶段的结果,明确接受选举出来节点的提议,并明确提议的内容。
  • 这里的序列号非常重要,无论在哪个阶段,序列号小的,都会被拒绝选举他。在第一阶段中,一旦接受者已经接受了之前当选的提议者提议,那后面再来找这个接受者提议者,即便第一阶段当选,也要强制修改自己的提议为之前当选领袖的提议,然后他会在第二阶段提出之前领袖的提议,以力争让大家的意见趋同。如果接受者之前没有接受过任何提议,那新选出的提议者就可以提出自己的提议了。

参考:第一篇文章原理说得很透彻,第二篇知乎文章的例子非常通俗易懂

  1. 分布式系统Paxos算法
  2. 如何浅显易懂地解说 Paxos 的算法?

Saga(未完结)

要求与限制
  1. 在分布式系统中由于网络请求可能的延时,需要Saga调用的服务支持幂等。 即多次的saga调用造成的结果都是相同的。
  2. 只保证ACD,不保证数据的隔离性
    1. 同时操作一个资源出现数据语义不一致的情况
    2. 同时操作一条数据造成操作覆盖
    3. 脏读取,比如去修改某数据过程中,另外一条saga来读取,造成误读未修改前数据
    4. 模糊读取,即在读取数据过程中,一条saga修改了数据,造成两次读取不一致
    5. 解决办法:从业务层面入手加入一Session以及锁的机制来保证能够串行化操作资源。也可以在业务层面先去隔离这部分资源, 最后在业务操作的过程中可以通过及时读取当前状态的方式获取到最新的更新。
  3. 当出现多个Saga事务时,某个事务执行出错,如何能保证有效的回退?
  4. 向前恢复:通过对Saga事务的监管,管理人员查看事务是否成功,进行通过设置自动重试次数或手动重试。
  5. 向后回退:通过手动添加补充事件,让每个微服务有失败后的补偿机制。
  6. Saga事务的编排:待补充

Choerodon(猪齿鱼) 的Saga解决方案 -- 借助Kafka

  1. 在 Saga 中,猪齿鱼为 Saga 分配了一个orchestrator作为事务管理器,当服务启动时,将服务中所有的 SagaTask 注册到管理器中。当一个 Saga 实例启动时,服务消费者就可以通过轮询的方式主动拉取到该实例对应的Saga数据,并执行对应的业务逻辑。执行的状态可以通过事务管理器进行查看,展现在界面上。
  2. 这里的幂等性是通过将记录执行实例来实现的,当每执行一条SagaTask就会在实例事件中进行记录,下一次如果还有这样的实例产生便不会执行。
  3. 数据隔离性的解决方案就简化为单个服务的事务管理,比如:当A微服务与B微服务在某一时刻都需要对C这一服务中的数据进行改变时,那么这里将会产生两条Saga数据。之后C拉取到这些Saga数据,并去数据库中进行操作(这里就相当于把数据的隔离交给了数据库来处理)
  4. 猪齿鱼目前实现的是向前恢复策略。后续若有更好的改进策略也会及时分享。
  5. 事务的编排:待补充
参考:

Choerodon猪齿鱼平台中的微服务数据一致性解决方案 作者:董凡

猜你喜欢

转载自juejin.im/post/5d6a066af265da03b31be515