分布式锁& 分布式事务

分布式事务

什么是分布式事务?

分布式事务模型介绍:

分布式事务是指涉及多个参与方的事务操作,这些参与方可以位于不同的物理节点或不同的系统之间,需要保证所有参与方的操作要么全部成功,要么全部失败,保持数据的一致性。

事务参与者「资源管理器(Resource Manager, RM):与事务参与者同义」:例如每个数据库就是一个事务参与者。
事务协调者「事务管理器(Transaction Manager, TM):与事务协调者同义」:访问多个数据源的服务程序。
在分布式事务模型中,一个 TM 管理多个 RM,即一个服务程序访问多个数据源;TM 是一个全局事务管理器,协调多方本地事务的进度,使其共同提交或回滚,最终达成一种全局的 ACID 特性。

分布式事务的实现方式有哪些?

常见的分布式事务实现方式包括两阶段提交(2PC)、三阶段提交(3PC)、TCC(Try-Confirm-Cancel)事务模型、消息队列等。

两阶段提交和三阶段提交的区别是什么?

两阶段提交是一种同步阻塞的协议,需要参与方在协调者的指导下进行操作,存在单点故障和性能瓶颈的问题;三阶段提交在两阶段提交的基础上引入了超时机制,减少了阻塞时间,但仍然存在阻塞和单点故障的问题。
二阶段提交(Two-Phase Commit,简称2PC)和三阶段提交(Three-Phase Commit,简称3PC)是在分布式系统中用于保证多个参与者之间数据一致性的协议。

  • 二阶段提交(2PC):
  1. 准备阶段(Prepare Phase):协调者(Coordinator)向所有参与者(Participants)发送事务准备请求,并等待参与者的响应。参与者执行事务的预备操作,将准备就绪的消息发送给协调者。
  2. 提交阶段(Commit Phase):如果所有参与者都准备就绪,则协调者向所有参与者发送提交请求,参与者执行事务的提交操作,并向协调者发送确认消息。协调者在收到所有确认消息后,决定是否提交事务。
  • 三阶段提交(3PC):
  1. CanCommit阶段(准备阶段):协调者向所有参与者发送CanCommit请求,参与者执行本地事务的预备操作,并将准备就绪的消息发送给协调者。
  2. PreCommit阶段(预提交阶段):协调者等待所有参与者的准备就绪消息。如果所有参与者都准备就绪,则协调者向所有参与者发送PreCommit请求,参与者执行事务的预提交操作,并将结果反馈给协调者。
  3. DoCommit阶段(提交阶段):协调者在收到所有参与者的反馈后,根据反馈结果决定是否提交事务。如果所有参与者都反馈成功,则协调者发送DoCommit请求,参与者执行事务的提交操作。否则,协调者发送DoAbort请求,参与者执行事务的回滚操作。

三阶段提交相对于二阶段提交引入了预提交阶段,可以在出现网络异常或参与者故障的情况下更好地保证事务的一致性。然而,三阶段提交仍然存在某些特殊情况下的阻塞和不确定性问题。
选择使用二阶段提交还是三阶段提交取决于系统的具体需求和性能要求。二阶段提交简单直接,但在某些故障情况下可能导致事务无法完成。三阶段提交引入了额外的阶段,提高了容错性,但增加了复杂性和延迟。根据实际情况进行权衡和选择。

2PC 是一种实现分布式事务的简单模型,这两个阶段是:

  1. 投票阶段:
    a、协调者(Coordinator,即事务管理器)会向事务的参与者(Cohort,即本地资源管理器)发起执行操作的 CanCommit 请求,并等待参与者的响应。
    b、参与者接收到请求后,会执行请求中的事务操作,记录日志信息(包含事务执行前的镜像),同时锁定当前记录。参与者执行成功,则向协调者发送“Yes”消息,表示同意操作;若不成功,则发送“No”消息,表示终止操作。
    c、 当所有的参与者都返回了操作结果(Yes 或 No 消息)后,系统进入了提交阶段。
  2. 提交阶段:
    如果各个参与者回复的都是 yes,则协调者向所有参与者发起事务提交操作,然后所有参与者收到后各自执行本地事务提交操作并向协调者发送 ACK;如果任何一个参与者回复 no 或者超时,则协调者向所有参与者发起事务回滚操作,然后所有参与者收到后各自执行本地事务回 滚操作并向协调者发送 ACK。
  3. 缺点:
    单点故障:一旦协调者出现问题,那么整个第二阶段提交流程将无法运转,更为严重的是,协调者在阶段二中出现问题的话,那么其他的参与者将会处于锁定事务资源的状态中,而无法继续完成事务操作。
    数据不一致:在二阶段提交的阶段二中,即提交事务提交的时候,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交,从而导致数据不一致。

TCC事务模型是如何解决分布式事务的?

TCC事务模型采用了预留资源、确认执行和取消执行的三个阶段,通过业务逻辑的划分和状态的管理来实现分布式事务的一致性。

TCC(Try-Confirm-Cancel)事务模型是一种在分布式系统中处理事务的模式,通过将事务操作分解为三个阶段来实现事务的一致性。下面详细介绍TCC事务模型的每个阶段及其作用:

  1. 尝试(Try)阶段:

在尝试阶段,事务参与者尝试预留和锁定资源,执行一些检查和准备工作,但并不对资源进行真正的修改。
如果所有参与者的尝试操作都成功,表示资源的预留和锁定都可用,进入确认阶段。
如果任何一个参与者的尝试操作失败,表示资源不可用或其他问题,事务会进行回滚或采取相应的补偿操作。
2. 确认(Confirm)阶段:

在确认阶段,事务参与者执行真正的资源更新操作,将之前预留的资源状态进行更新。
如果所有参与者的确认操作都成功,表示事务执行成功,可以提交事务。
如果任何一个参与者的确认操作失败,表示事务执行出现问题,需要进行回滚或采取相应的补偿操作。
3. 取消(Cancel)阶段:

在取消阶段,事务参与者执行之前进行的资源预留和锁定的取消操作,将资源状态还原到尝试阶段之前的状态。
如果所有参与者的取消操作都成功,表示事务执行失败,可以进行回滚操作。
如果任何一个参与者的取消操作失败,可能需要采取其他补偿措施或人工介入来修复系统状态。
TCC事务模型通过将事务拆分为尝试、确认和取消三个阶段,允许在每个阶段执行相关的业务逻辑和状态检查,从而实现分布式事务的一致性和可靠性。

TCC事务模型的优点包括:

高度灵活性:可以根据业务需求定义每个阶段的逻辑和操作。
显式的补偿逻辑:通过取消阶段的补偿操作,可以对之前执行的操作进行回滚或补偿,确保数据的一致性。
容错性和可扩展性:即使在参与者失败或网络故障的情况下,也可以通过补偿操作来保持系统的一致性。
然而,TCC事务模型也有一些局限性:

开发复杂性:实现TCC事务模型需要对业务逻辑进行细致的划分和设计,需要考虑各个阶段的执行顺序、异常处理等。
并发性能:TCC模型在高并发情况下可能存在资源竞争

分布式事务的强一致性和最终一致性有什么区别?

强一致性要求事务的所有参与方在事务完成后立即达到一致的状态,而最终一致性则允许在一定时间内存在数据不一致的情况,但最终会达到一致的状态。

如何保证分布式事务的可靠性?

分布式事务 其实就是分布式系统下的事务处理,可靠性问题说白了就是分布式系统下的可靠性是如何保证的。
可以采用消息队列来确保事务消息的可靠投递和处理,使用分布式锁来保证资源的互斥访问,进行合理的重试和补偿机制来处理事务中的异常情况。

分布式事务是指涉及多个节点或参与者的事务操作,这些节点可能分布在不同的物理位置或网络环境中。在分布式系统中,保障事务的可靠性是一个关键问题,因为节点之间的通信可能会出现延迟、故障或数据不一致等问题。以下是几种保障分布式事务可靠性的方法:

两阶段提交(Two-Phase Commit,2PC):2PC是一种经典的分布式事务协议,通过协调器节点来确保所有参与者节点在事务提交之前达成一致。在2PC中,协调器节点会发送预提交请求给所有参与者,并等待它们的响应。如果所有参与者都准备好提交,协调器节点发送提交请求;否则,发送中止请求。2PC保证了分布式事务的原子性,但在协调器节点发生故障时可能会出现阻塞。

三阶段提交(Three-Phase Commit,3PC):3PC是对2PC的改进,旨在解决2PC中的阻塞问题。与2PC不同,3PC引入了一个预提交阶段,在这个阶段,协调器节点询问所有参与者节点是否准备好提交事务。如果所有参与者都准备好,进入提交阶段;否则,进入中止阶段。3PC通过引入超时机制和额外的中间状态,减少了协调器节点故障时的阻塞问题。

基于消息队列的异步提交:这种方法将分布式事务的提交过程异步化,通过将提交请求放入消息队列中,由后台进程或服务来处理。在这种模式下,参与者节点将事务操作的结果发送到消息队列,后台进程负责收集和处理这些结果,以决定是否提交事务。这种方法降低了对协调器节点的依赖,并提供了较高的可靠性和扩展性。

分布式一致性协议:一致性协议是一种用于保证分布式系统一致性的机制,如Paxos、Raft等。这些协议通过在节点之间进行投票和选举来达成一致性,并确保分布式系统中的所有节点达成一致的状态。使用一致性协议可以在保障可靠性的同时,提供高可用性和容错性。

乐观并发控制(Optimistic Concurrency Control,OCC):OCC是一种在分布式环境中处理事务冲突的方法。它假设事务之间的冲突较少发生,并允许并发执行。OCC使用版本控制和冲突检测机制来确保数据的一致性。在事务提交之前,OCC会检查事务期间是否有其他事务修改了相同的数据,如果存在冲突,则中止当前事务并进行回滚操作。

分布式锁:分布式锁是一种在分布式系统中协调并发访问的机制。它可以用于保护共享资源,避免并发访问造成的数据不一致性。分布式锁可以基于各种技术实现,如ZooKeeper、Redis等。通过获取分布式锁,事务可以确保在执行关键操作时,其他事务不会同时访问相同的资源。

数据复制和容错:为了提高分布式系统的可靠性,可以使用数据复制和容错机制。数据复制可以将数据存储在多个节点上,当一个节点出现故障时,其他节点可以继续提供服务。容错技术,如冗余备份和故障恢复,可以保障在节点故障或网络故障时,系统能够自动地恢复正常运行。

分布式事务的优缺点是什么?

优点包括可以实现跨系统的事务操作,提高系统的可扩展性和并发性;缺点包括引入了复杂性和性能开销,存在单点故障和网络通信的风险。

空回滚(Empty Rollback)和防悬挂(Hang Prevention)是什么?

  1. 空回滚(Empty Rollback):空回滚指的是在事务回滚时,由于事务并没有进行任何实际的修改操作或写入操作,回滚操作本身不会产生实际的效果。空回滚的发生可能是由于某个事务没有进行任何数据修改操作,或者在回滚时发生了错误导致回滚操作实际上没有撤销任何事务的改变。空回滚会导致不必要的性能开销,因为回滚操作会占用系统资源,但没有对数据产生实质性的改变。

  2. 防悬挂(Hang Prevention):防悬挂是指在并发事务处理中,为了防止某个事务在等待资源的过程中被其他事务永久地阻塞,导致系统陷入死锁状态或长时间的等待状态。悬挂指的是事务被挂起而无法继续执行的情况。防悬挂的目标是保证系统中的所有事务都能够正常执行,并避免其中任何一个事务因为资源竞争而无法继续执行。

为了防止悬挂的发生,可以采取以下措施:

设置合理的事务超时时间:如果一个事务在一定的时间内没有完成,系统可以将其回滚,避免长时间的等待。
设置合理的锁超时时间:当一个事务请求获取锁时,如果在一定的时间内无法获取到锁,可以中断事务的执行或进行回滚操作,避免造成悬挂。
使用死锁检测和解决机制:通过死锁检测算法,及时发现并解决可能导致悬挂的死锁情况。
优化并发控制策略:合理设计事务的并发控制策略,避免不必要的资源竞争和互斥访问,从而减少悬挂的可能性。
空回滚和防悬挂是在并发事务处理中需要考虑和解决的问题,通过合理的事务设计和并发控制策略,可以减少空回滚的发生,并采取措施预防悬挂的发生,提高系统的并发性能和可靠性。

分布式锁

什么是分布式锁?为什么在分布式系统中需要使用分布式锁?

分布式锁是一种用于协调分布式系统中并发访问共享资源的机制。在分布式系统中,多个节点同时访问共享资源可能导致数据不一致或冲突的问题,因此需要使用分布式锁来确保在同一时间只有一个节点能够访问该资源,保证数据的一致性和正确性。

分布式锁的实现方式有哪些?

  1. 数据库
  2. 基于缓存(如Redis)
  3. 基于ZooKeeper等。

ZK如何实现分布式锁

ZooKeeper可以作为分布式锁的协调服务。使用ZooKeeper实现分布式锁的步骤包括创建一个临时有序节点、检查自己是否为最小节点、如果是最小节点则获取到锁,否则监听前一个节点的删除事件。当节点前一个节点被删除时,当前节点收到通知并重新尝试获取锁。这种方式可以确保只有一个节点能够获取到锁,实现了分布式环境下的互斥访问。

  1. 创建一个具有唯一性的锁路径,例如在ZooKeeper中创建一个顺序临时节点。

  2. 检查自己创建的节点是否是当前所有节点中最小的节点。可以通过获取锁路径下的所有子节点,并比较自己的节点编号与其他节点的编号来判断。

  3. 如果自己是最小节点,则表示成功获取到锁,执行业务逻辑。

  4. 如果自己不是最小节点,则监听前一个节点的删除事件。

  5. 当前一个节点被删除时,收到通知,重新尝试获取锁,回到步骤2。

  6. 使用ZooKeeper实现分布式锁的原理是基于ZooKeeper提供的有序临时节点和监听机制。有序临时节点保证了节点的顺序性,而监听机制则用于实现等待和唤醒的功能。通过这种方式,只有一个节点能够成为最小节点并成功获取到锁,其他节点则会等待并竞争锁的释放。

  7. 需要注意的是,使用ZooKeeper实现分布式锁时,应考虑异常情况和锁的超时处理,以避免死锁和长时间等待的问题。

redis分布式锁 解决死锁和锁续期问题

基于Redis的分布式锁通过以下步骤实现:

使用SETNX命令尝试在Redis中设置一个特定的键作为锁,如果设置成功,则获得锁;否则,表示锁已经被其他节点持有。
设置锁的过期时间,避免锁被长期占用,防止死锁。
执行业务逻辑,完成后释放锁,使用DEL命令删除锁的键。
在处理锁的竞争问题时,可以采用以下策略:

竞争锁失败的节点可以使用自旋(自旋等待)或者等待一段时间后重新尝试获取锁。
为了防止锁过期时间过短导致业务未完成而锁被释放,可以在锁过期时间前使用续期机制,使用EXPIRE命令延长锁的过期时间。
对此使用的中间件有 watchDog看门狗。

处理分布式锁的死锁和活锁问题

  1. 死锁问题可以通过引入超时机制和死锁检测来解决。当一个事务在一段时间内无法获取到锁时,可以选择放弃获取锁或者进行回滚操作,以避免长时间的阻塞。死锁检测可以通过周期性地扫描锁的占用情况,检测到死锁后进行解锁或回滚操作。
  2. 活锁问题可以通过引入随机性、退避策略和重试机制来解决。随机性可以避免多个事务同时重试导致的竞争问题,退避策略可以让事务在遇到竞争时进行暂时的等待,并随机选择重试的时间间隔。

分布式锁的超时问题及相关考虑因素

  1. 分布式锁的超时问题可以通过设置合适的锁超时时间来解决。当一个事务获取锁的时间超过设定的超时时间时,可以选择放弃获取锁或进行其他处理。
  2. 超时机制需要根据业务需求和系统性能进行合理设置。超时时间设置过短可能导致频繁的锁竞争和失败重试,影响性能;超时时间设置过长可能导致事务长时间等待锁,影响系统的响应性。

分布式锁的可重入性的意义及重要性:

可重入性是指同一个线程在持有锁的情况下可以再次获取锁而不会造成死锁。
在分布式锁中,可重入性非常重要,因为在分布式环境下,一个事务可能涉及多个子操作,这些子操作可能需要获取同一个锁。如果分布式锁不支持可重入性,就会导致死锁或逻辑错误的发生。

在高并发场景下,分布式锁可能面临以下性能和可伸缩性的挑战:

  1. 锁竞争:高并发情况下,多个线程同时争抢锁资源,导致锁的竞争激烈,可能造成性能瓶颈和延迟增加。

  2. 锁粒度:如果锁的粒度过大,即锁住了过多的资源,会导致并发性下降,限制系统的可伸缩性。如果锁的粒度过细,可能会导致锁的争用增加,造成锁竞争问题。

  3. 锁的管理:分布式锁需要管理锁的获取和释放过程,包括节点的注册、心跳检测、锁的状态维护等,这些管理操作会增加系统的负担和开销。

为了优化分布式锁的性能,在高并发场景下,可以考虑以下策略:

  1. 锁粒度优化:根据业务需求和数据访问模式,评估锁的粒度。尽量将锁的粒度设置为最小化,以减少锁的争用。

  2. 缓存优化:使用缓存来减少对分布式锁的频繁访问。可以通过缓存预取、缓存锁状态等方式,减少对分布式锁的请求次数。

  3. 异步处理:将不需要获取分布式锁的操作异步化,减少对锁资源的竞争。将可以并行执行的操作解耦,提高系统的并发性能。

  4. 优化锁算法:选择高性能的分布式锁算法,如基于ZooKeeper的分布式锁、基于Redis的分布式锁等,根据具体业务场景选择最合适的分布式锁实现。

  5. 分片锁:如果可能,将数据进行分片,每个分片上使用独立的锁,减少锁的竞争范围,提高并发性能。

  6. 锁超时设置:合理设置锁的超时时间,避免锁的长时间占用,以提高系统的响应性。

  7. 避免死锁和活锁:设计良好的锁协议,避免死锁和活锁的发生,保证系统的正常运行。

以上策略可以根据具体的业务场景和系统需求进行调整和优化,以提高分布式锁在高并发场景下的性能和可伸缩性。

算法

hash算法

以下是一些常见的哈希算法:

  1. MD5 (Message Digest Algorithm 5):产生128位哈希值,常用于校验数据完整性。由于其较低的安全性和易受碰撞攻击,已不推荐用于密码存储等安全场景。

  2. SHA-1 (Secure Hash Algorithm 1):产生160位哈希值,也常用于校验数据完整性。与MD5类似,也不再推荐在安全敏感场景中使用。

  3. SHA-256 (Secure Hash Algorithm 256-bit):SHA-2系列中的一种,产生256位哈希值,被广泛应用于加密、数字签名等领域。

  4. SHA-3 (Secure Hash Algorithm 3):SHA-3系列中的一种,是最新的哈希算法标准,提供了更高的安全性和抗碰撞能力。

  5. CRC32 (Cyclic Redundancy Check 32):产生32位哈希值,主要用于数据校验和错误检测,常见于网络传输和存储系统。

  6. MurmurHash:一系列高性能的非加密哈希算法,具有快速计算和较低的碰撞风险,常用于哈希表、分布式缓存等场景。

  7. CityHash:一种高速哈希算法,适用于处理大型数据集,广泛应用于分布式系统、搜索引擎等。

  8. FNV (Fowler-Noll-Vo) Hash:一种简单但高效的哈希算法,提供良好的分布性和低碰撞概率,适用于哈希表等应用。

这些是常见的哈希算法,每种算法都有其特定的应用场景和优缺点。在选择哈希算法时,需要根据具体需求考虑安全性、速度、分布性、抗碰撞性等因素,选择适合的算法。

一些常见的限流算法

  1. 固定窗口计数器算法(Fixed Window Counter):将请求计数在固定时间窗口内进行统计,当请求数达到限制时进行限流。例如,每秒只允许处理100个请求。

  2. 滑动窗口计数器算法(Sliding Window Counter):类似于固定窗口计数器算法,但是在每个时间窗口内,允许的请求数可以动态调整。例如,每秒内平均允许100个请求,但不能超过200个请求。

  3. 漏桶算法(Leaky Bucket):将请求以恒定的速率放入一个固定大小的桶中,超出桶容量的请求会被丢弃或延迟处理。可以控制请求的速率。

  4. 令牌桶算法(Token Bucket):类似于漏桶算法,但是在每个时间单位内,桶中会按固定速率生成令牌。请求需要获取令牌才能被处理,当桶中没有足够的令牌时,请求会被丢弃或延迟处理。

  5. 漏斗算法(Funnel):根据漏斗的容量和速率限制请求的处理。每个请求会占用一定的容量,当漏斗剩余容量不足时,请求会被丢弃或延迟处理。

这些算法可以根据具体的应用场景和需求选择使用。限流算法可以保护系统免受过多的请求压力,防止系统过载,并确保资源的合理分配和稳定运行。

蚂蚁金服 对分布式事务的实践TCC

TCC 协议,各个参与者需要实现3个操作:Try、Confirm 和 Cancel,3个操作对应2个阶段,Try 方法是一阶段的资源检测和预留阶段,Confirm 和 Cancel 对应二阶段的提交和回滚。

存在一个发起方和两个参与者,两个参与者分别实现了 Try、Confirm 和 Cancel 接口,第一阶段被包含在发起方的本地事务模版中(图中黄颜色的两条虚线就是发起方本地事务的范围),也就是说发起方负责调用各个参与者的一阶段方法,发起方的本地事务结束后,开始执行二阶段操作,二阶段结束则整个分布式事务结束。

二阶段是通过 Spring 提供的事务同步器实现的,发起方在发起一个分布式事务的时候,会注册一个事务同步器,当发起方本地事务结束的时候,会进入事务同步器的回调方法中。如果发起方的本地事务失败,则在回调中自动回滚所有参与者。如果发起方的本地事务成功,则二阶段自动提交所有参与者。二阶段结束后,删除所有事务记录。

总结一下:

事务发起方是分布式事务的协调者;
分布式事务必须在本地事务模板中进行,发起方本地事务的最终状态(提交或回滚)决定整个分布式事务的最终状态;
发起方主职责:开启一个分布式事务 + 调用参与者一阶段方法。发起方实现的时候,首先是开启一个本地事务,调用 Start 开启分布式事务,框架会自动注册一个 Spring 事务同步器,然后发起方发起对参与者 Try 方法的调用,当有一个 Try 方法失败,则阻断发起方本地事务,状态置为回滚;否则,所有的参与者 Try 成功,整个分布式事务的状态就是提交。框架会利用事务同步器自动去执行参与者的二阶段方法;
使用数据库持久化记录事务数据,也就是会跟踪发起方和各个参与者的状态,我们称为主事务状态和分支事务状态。这样我们就知道一个大事务整体是处于什么状态,每个参与者又是什么状态,当一笔事务失败时,我们就能捞起那些失败的参与者,进行补偿重试;

业务在实现参与者的时候,需要遵循以下规范

  • 业务模型分二阶段设计;
  • 幂等控制;
  • 并发控制;
  • 允许空回滚;
  • 防悬挂控制;

二阶段设计

二阶段设计和幂等控制比较容易明白。二阶段设计就是一阶段的资源预留和二阶段的提交回滚。

比如以扣钱场景为例,账户 A 有 100 元,要扣除其中的 30 元。

  • 一阶段要先检查资源是否足够,账户余额是否大于等于 30 块,资源不足则需要立马返回失败;资源足够则把这部分资源预留起来,预留就是锁资源,锁的粒度可大可小,尽量是按照最小粒度、尽快释放的原则来,比如这里引入一个“冻结部分”的字段,“可用余额”在一阶段后就能立马得到释放,锁的是冻结字段。
  • 二阶段,如果是提交则真正扣除冻结的 30 元;如果是回滚的话,则把冻结部分加回可用余额里。

幂等控制

幂等控制,就是 Try-Confirm-Cancel 三个方法均需要保持幂等性。无论是网络数据包重传,还是异常事务的补偿执行,都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执行;用户在实现 TCC 服务时,需要考虑幂等控制,即 Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的。

并发控制

并发控制即当两个并发执行的分布式事务操作同一个账号时,冻结的部分是相互隔离的,也就是 T1 冻结金额只能被事务 1 使用,T2 冻结金额只能被事务 2 使用。冻结资源与事务 ID 之间建立关联关系。

允许空回滚

首先对空回滚的定义就是 Try 未执行,Cancel 先执行了。正常是一阶段的请求先执行,然后才是二阶段的请求。出现空回滚的原因,是网络丢包导致的,调用 Try 方法时 RPC timeout 了,分布式事务回滚,触发 Cancel 调用;参与者未收到 Try 请求而收到了 Cancel 请求,出现空回滚。

我们在设计参与者时,要支持这种空回滚。

防悬挂
悬挂的定义是 Cancel 比 Try 先执行。不同于空回滚,空回滚是 Try 方法的请求没有收到。悬挂是 Try 请求到达了,只不过由于网络拥堵,Try 的请求晚于二阶段的 Cancel 方法。

整个流程是这样的:

调用 TCC 服务 Try 方法,网络拥堵(未丢包),RPC超时;
分布式事务回滚;
TCC 服务 Cancel 被调用,执行了空回滚;整个分布式事务结束;
被拥堵的 Try 请求到达 TCC 服务,并被执行;出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,TCC 参与者悬挂;
解决悬挂的问题,可以跟踪事务的执行,如果已经回滚过了,一阶段不应该正常执行,这时候要拒绝 Try 的执行。

posted @ 2023-05-31 12:43  刘三茶  阅读(1014)  评论(0编辑  收藏  举报