《深入理解分布式事务》第九章 可靠消息最终一致性分布式事务原理

《深入理解分布式事务》第九章 可靠消息最终一致性分布式事务原理

一、基本原理

可靠消息最终一致性的基本原理是事务发起方(消息发送者)执行本地事务成功后发出一条消息,事务参与方(消息消费者)接收到事务发起方发送过来的消息,并成功执行本地事务。事务发起方和事务参与方最终的数据能够达到一致的状态

这里主要强调如下两点:

  1. 事务发起方一定能够将消息成功发送出去
  2. 事务参与方一定能够成功接收到消息

可以利用消息中间件实现可靠消息最终一致性分布式事务方案,如下图所示:

在这里插入图片描述

事务发起方将消息发送给消息中间件,事务参与方从消息中间件中订阅(接收)消息。事务发起方会通过网络将消息发送给消息中间件,而事务参与方也需要哦通过网络接收消息中间件的消息。网络的不确定性可能会造成事务发起方发送消息失败,也可能会造成事务参与方接收消息失败,即造成分布式事务的问题

在使用可靠消息最终一致性方案解决分布式事务的问题时,需要确保消息发送和消息消费的一致性,从而确保消息的可靠性

二、本地消息表

为了防止在使用消息一致性方案处理分布式事务的过程中出现消息丢失的情况,使用本地事务保证数据业务操作和消息的一致性,也就是通过本地事务,将业务数据和消息数据分别保存到本地数据库的业务数据表和本地消息表中,然后通过定时任务读取本地消息表中的消息数据,将消息发送到消息中间件,等到消息消费者成功接收到消息后,再将本地消息表中的消息删除。这种方式实现的分布式事务就是基于本地消息表的可靠消息最终一致性分布式事务

1.实现原理

基于本地消息表实现的可靠消息最终一致性方案的核心思想是将需要通过分布式系统处理的任务,比如同步数据等操作,通过消息或者日志的形式异步执行,这些消息或者日志可以存储到本地文件中,也可以存储到本地数据库的数据表中,还可以存储到消息中间件中,然后通过一定的业务规则进行重试。这种方案要求各个服务的接口具有幂等性,原理如下图所示:

在这里插入图片描述

存放消息的本地消息表和存放数据的业务数据表位于同一个数据库中,这种设计能够保证使用本地事务达到消息和业务数据的一致性,并且引入消息中间件实现多个分支事务之间的最终一致性,整体流程如下:

  1. 事务发起方向业务数据表成功写入数据后,会向本地消息表发送一条消息数据,因为写业务数据和写消息数据在同一个本地事务中,所以本地事务会保证这条消息数据一定能够正确地写入本地消息表
  2. 使用专门的定时任务将本地消息表中的消息写入消息中间件,如果写入成功,会将消息从本地消息表中删除。否则,继续根据一定的规则进行重试操作
  3. 如果消息根据一定的规则写入消息中间件仍然失败,可以将失败的消息数据转储到 “死信” 队列数据表中,后续进行人工干预,以达到事务最终一致性的目的
  4. 事务参与方,也就是消息消费者会订阅消息中间件的消息,当接收到消息中间件的消息时,完成本地的业务逻辑
  5. 事务参与方的本地事务执行成功,则整个分布式事务执行成功。否则,会根据一定的规则进行重试。如果仍然不能成功执行本地事务,则会给事务发起方发送一条事务执行失败的消息,以此来通知事务发起方进行事务回滚

2.优缺点

可靠消息最终一致性分布式事务解决方案是处理分布式事务的典型方案,也是业界使用比较多的一种方案。基于本地消息表实现的可靠消息方案是其中一种具体实现方式,这种实现方式有如下明显的优点:

  1. 使用消息中间件在多个服务之前传递消息数据,在一定程度上避免了分布式事务的问题
  2. 作为业界使用比较多的一种方案,相对比较成熟

也有比较明显的缺点,如下所示:

  1. 无法保证各个服务节点之间数据的强一致性
  2. 某个时刻可能会查不到提交的最新数据
  3. 消息表会耦合到业务库中,需要额外手动处理很多发送消息的逻辑,不利于消息数据的扩展。如果消息表中存储了大量的消息数据,会对操作业务数据的性能造成一定的影响
  4. 消息发送失败时需要重试,事务参与方需要保证消息的幂等
  5. 如果消息重试后仍然失败,则需要引入人工干预机制
  6. 消息服务与业务服务耦合,不利于消息服务的扩展和维护
  7. 消息服务不能共用,每次需要实现分布式事务时,都需要单独开发消息服务逻辑,增加了开发和维护的成本

三、独立消息服务

顾名思义,独立消息服务就是将消息处理部分独立部署成单独的服务,以便消息服务能够单独开发和维护,这样就实现了消息服务和业务服务的解耦、消息数据和业务数据的解耦,方便对消息服务进行扩展

1.实现原理

独立消息服务是在本地消息表的基础上进一步优化,将消息服务独立出来,并将消息数据从本地消息表独立成单独消息数据库,引入消息确认服务和消息恢复服务,如下图所示:

在这里插入图片描述

独立消息服务实现的分布式事务中有几个核心服务,分别为可靠消息服务、消息确认服务、消息恢复服务和消息中间件,具体流程如下:

  1. 事务发起方向可靠消息服务成功发送消息后,执行本地事务
  2. 可靠消息服务接收到事务发起方发送的消息后,将消息存储到消息库中,并将消息记录的状态标记为 “待发送”,并不会马上向消息中间件发送消息。同时,向事务发起方响应消息发送已就绪的状态
  3. 当事务发起方的事务执行成功时,事务发起方会向可靠消息服务发送确认消息,否则,发送取消消息
  4. 当可靠消息服务接收到事务发起方发送过来的确认消息时,会直接将消息库中保存的当前消息删除或标记为 “已删除”
  5. 消息中间件接收到可靠消息服务发送过来的消息时,会将消息投递给业务参与方,业务参与方接收到消息后,执行本地事务,并将执行结果作为确认消息发送到消息中间件
  6. 消息中间件将确认结果投递到可靠消息服务,可靠消息服务接收到确认消息后,根据结果状态将消息库中的当前消息记录标记为 “已完成”
  7. 如果事务发起方向可靠消息服务发送消息失败,会触发消息重试机制。如果重试后仍然失败,则会由消息确认服务定时校对事务发起方的事务状态和消息数据库中当前消息的状态,发现状态不一致时,采用一定的校对规则进行校对
  8. 如果可靠消息服务向消息中间件发送消息失败,会触发消息重试机制。如果重试后仍然失败,则会由消息恢复服务根据一定的规则定时恢复消息库中的消息数据

2.优缺点

使用独立消息服务实现分布式事务的优点如下:

  1. 消息服务能偶独立部署、独立开发和维护
  2. 消息服务与业务服务解耦,具有更好的扩展性和伸缩性
  3. 消息表从本地数据库解耦出来,使用独立的数据库存储,具有更好的扩展性和伸缩性
  4. 消息服务可以被多个服务共用,降低了重复开发消息服务的成本
  5. 消息数据的可靠性不依赖于消息中间件,弱化了对于消息中间件的依赖性

缺点如下:

  1. 发送一次消息需要请求两次接口
  2. 事务发起方需要开发比较多的事务查询接口,在一定程度上增加了开发成本

四、RocketMQ 事务消息

RocketMQ 是阿里巴巴开源的一款支持事务消息的消息中间件,于 2012 年正式开源,2017 年成为 Apache 基金会的顶级项目。RocketMQ 的高可用机制以及可靠消息设计能够在系统发生异常时保证事务达到最终一致性

1.实现原理

RocketMQ 主要由 Producer 端和 Broker 端组成。RocketMQ 的事务消息主要是为了让 Producer 端的本地事务与消息发送逻辑形成一个完整的原子操作,即 Producer 端的本地事务和消息发送逻辑要么全部执行成功,要么全部不执行。在 RocketMQ 内部,Producer 端和 Broker 端具有双向通信能力,使得 Broker 端具备事务协调者的功能。RocketMQ 提供的消息存储机制本身就能够对消息进行持久化操作,这些可靠的设计能够保证在系统出现异常时,事务依然能够达到一致性

RocketMQ 4.3 版本之后引入了完整的事务消息机制,其内部实现了完整的本地消息表逻辑,使用 RocketMQ 实现可靠消息分布式事务就不用用户再实现本地消息表的逻辑了,极大地减轻开发工作量

使用 RocketMQ 实现可靠消息分布式事务解决方案的基本原理如下图所示:

在这里插入图片描述

整体流程如下:

  1. 事务发起方向 RocketMQ 发送 Half 消息
  2. RocketMQ 向事务发起方响应 Half 消息发送成功
  3. 事务发起方执行本地事务,向本地数据库中插入、更新、删除数据
  4. 事务发起方向 RocketMQ 发送提交事务或者回滚事务的消息
  5. 如果事务参与方未收到消息或者执行事务失败,且 RocketMQ 未删除保存的消息数据,则 RocketMQ 会回查事务发起方的接口,查询事务状态,以此确认是再次提交事务还是回滚事务
  6. 事务发起方查询本地数据库,确认事务是否执行成功的状态
  7. 事务发起方根据查询到的事务状态,向 RocketMQ 发送提交事务或者回滚事务的消息
  8. 如果第七步中,事务发起方向 RocketMQ 发送的是提交事务的消息,则 RocketMQ 会向事务参与方投递消息;
    如果第七步中,事务发起方向 RocketMQ 发送的是回滚事务的消息,则 RocketMQ 不会向事务参与方投递消息,并且会删除内部存储的消息数据
  9. 如果 RocketMQ 向事务参与方投递的是执行本地事务的消息,则事务参与方会执行本地事务,向本地数据库中插入、更新、删除数据
  10. 如果 RocketMQ 向事务参与方投递的是查询本地事务状态的消息,则事务参与方会查询本地数据库中事务的执行状态

在使用 RocketMQ 实现分布式事务时,上述流程中的主要部分都由 RocketMQ 自动实现了,开发人员只需要实现本地事务的执行逻辑和本地事务的回查方法,重点关注事务的执行状态即可

2.RocketMQ 本地事务监听接口

RocketMQ 内部提供了本地事务的监听接口 RocketMQLocalTransactionListener。Rocket-MQLocalTransactionListener 接口中主要有 executeLocalTransaction(Message, Object) 和 check-LocalTransaction(Message) 两个方法,源码如下:

public interface RocketMQLocalTransactionListener{
	RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg);
	RocketMQLocalTransactionState checkLocalTransaction(Message msg);
}

当事务发起方成功向 RocketMQ 发送准备执行事务的消息后,RocketMQ 会回调 RocketMQLocalTransactionListener 接口中的 executeLocalTransaction(Message, Object) 方法。executeLocalTransaction(Message, Object) 方法中主要接收两个参数:一个是 Message 类型参数,表示回传的消息;另一个是 Object 类型参数,是事务发起方调用 RocketMQ 的 send() 方法时传递的参数。此方法会返回事务的状态,当返回 COMMIT 时,表示事务提交,当返回 ROLLBACK 时,表示事务回滚,当返回 UNKNOW 时,表示事务回调

当需要回查本地事务状态时,调用 checkLocalTransaction(Message) 方法。checkLocal-Transaction(Message) 方法中接收一个 Message 类型参数,表示要回查的事务消息。此方法返回事务的状态,同 executeLocalTransaction(Message, Object) 方法返回的事务状态,这里不再赘述

使用 RocketMQ 实现分布式事务时,事务发起方向 RocketMQ 发送事务消息比较简单,代码片段如下所示:

//创建一个事务消息生产者
TransactionMQProducer producer = new TransactionMQProducer("ProducerGroup");
//设置 Producer 端的地址
producer.setNamesrvAddr("127.0.0.1:9876");
//启动 Producer 端
producer.start();
//设置 TransactionListtener 实现
//transactionListener 表示发送准备消息成功后执行的回调接口
producer.setTransactionListener(transactionListener);
//发送事务消息
SendResult sendResult = producer.sendMessageInTransaction(msg, null);

五、消息发送的一致性

消息发送一致性指的是事务发起方执行本地事务与产生消息数据和发送消息的整体一致性。换句话说,就是事务发起方执行事务操作成功,则一定能够将其产生的消息成功发送出去。这里一般会将消息发送到消息中间件中,例如 Kafka、RocketMQ、RabbitMQ 等。消息发送的一致性包括消息发送与确认机制、消息发送的不可靠性、保证发送消息的一致性

1.消息发送与确认机制

消息发送的一致性设计消息的发送与确认机制。常规消息中间件的消息发送与确认机制如下所示:

  1. 消息生产者生成消息并将消息发送给消息中间件。这里可以通过同步和异步的方式发送
  2. 消息中间件接收到消息后,将消息数据持久化存储到磁盘。这里可以根据配置调整存储策略
  3. 消息中间件向消息生产者返回消息的发送结果。这里返回的可以是消息发送的状态,也可以是异常信息
  4. 消息消费者监听消息中间件并消费指定主题中的数据
  5. 消息消费者获取消息中间件中的数据后,执行本地的业务逻辑
  6. 消息消费者对已经成功消费的消息向消息中间件进行确认,消息中间件收到收费者反馈的确认消息后,将确认后的消息从消息中间件中删除

一般情况下,常规的消息中间件对消息的处理流程无法实现消息发送的一致性,因此,直接使用现成的消息中间件无法完成实现消息发送的一致性。在实现分布式事务时,需要手动开发消息发送与确认机制以满足消息发送的一致性

2.消息发送的不一致性

如果不做处理,消息的发送是不可靠的,无法满足消息发送的一致性。这里通过几个具体的案例来说明因消息发送的不可靠性导致的不一致问题

先操作数据库,再发送消息,代码片段如下所示

public void saveDataAndSendMessage(){
	//保存交易流水信息
	payService.save(payInfo);
	//发送消息
	messageService.sendMessage(message);
}

这种情况无法保证消息发送的一致性,可能虽然数据保存成功了,但是消息发送失败了。事务参与方未能收到消息,无法执行事务参与方的业务逻辑,最终导致事务的不一致

先发送消息,再操作数据库,代码片段如下所示

public void saveDataAndSendMessage(){
	//发送消息
	messageService.sendMessage(message);
	//保存交易流水信息
	payService.save(payInfo);
}

这种情况无法保证消息发送的一致性,可能虽然消息发送成功了,但是保存数据失败了。事务参与方收到消息并成功地执行了本地事务操作,而事务发起方保存数据失败了,最终导致事务的不一致

在同一事务中,先发送消息,后操作数据库,代码片段如下所示

@Transaction
public void saveDataAndSendMessage(){
	//发送消息
	messageService.sendMessage(message);
	//保存交易流水信息
	payService.save(payInfo);
}

这种情况下,虽然使用了 Spring 的 @Transactional 注解,使发送消息和保存数据的操作在同一事务中,但是仍然无法保证消息发送的一致性。可能消息发送成功,数据库操作失败,消息发送成功后无法进行回滚操作。事务发起方执行事务失败,事务参与方接收到消息后执行事务成功,最终导致事务的不一致

在同一事务中,先操作数据库,后发送消息,代码片段如下所示

@Transactional
public void saveDataAndSendMessage(){
	//保存交易流水信息
	payService.save(payInfo);
	//发送消息
	messageService.sendMessage(message);
}

这种情况下,如果保存数据成功,而发送消息失败,则抛出异常,对保存数据的操作进行回滚,最终,保存数据的操作和发送消息的操作都执行失败,看上去发送消息满足一致性了,实际上,这种情况仍然无法满足消息发送的一致性

如果数据保存成功,消息发送成功,由于网络出现故障等异常情况,导致发送消息的响应超时,则抛出异常,回滚保存数据的操作。但是事务参与者可能已经成功接收到消息,并成功执行了事务操作,最终导致事务的不一致

3.如何保证消息发送的一致性

要保证消息发送的一致性,就要实现消息的发送与确认机制。事务发起方向消息中间件成功发送消息后,消息中间件向事务发起方返回消息发送成功的状态。当事务参与方接收到消息并处理完事务操作后,需要向消息中间件发送确认消息,整体流程如下图所示:

在这里插入图片描述

主体流程如下所示:

  1. 事务发起方向消息中间件发送待确认消息
  2. 消息中间件接收到事务发起方发送过来的消息,将消息存储到本地数据库,此时并不会向事务参与方投递消息
  3. 消息中间件向事务发起方返回消息存储结果,事务发起方根据返回的结果确定执行的业务逻辑,必要时,还会向上层抛出异常信息
  4. 事务发起方完成业务处理后,把业务处理的结果发送给消息中间件
  5. 消息中间件收到事务发起方发送过来的结果数据后,根据结果确定后续的处理逻辑。如果事务发起方发送过来的结果为 “成功”,消息中间件会更新本地数据库中的消息状态为 “待发送”。否则,将本地数据库中的消息状态标记为 “已删除”,或者直接删除数据库中相应的消息记录
  6. 事务参与方会监听消息中间件,并接收状态为 “待发送” 的消息,当收到消息中间件的消息后,会执行对应的业务逻辑,消息中间件对应的记录变更为 “已发送”
  7. 事务参与方的业务操作完成后,会向消息中间件发送确认消息,表示事务参与方已经收到消息并且执行完对应的业务逻辑,消息中间件会将消息从本地数据库中删除
  8. 为了保证事务发起方一定能能够将消息发送出去,在事务发起方的应用服务中需要暴露一个回调查询接口。消息服务在后台开启一个线程,定时扫描消息服务中状态为 “待发送” 的消息,回调事务发起方提供的回调查询接口,根据消息服务中的业务参数回查事务发起方本地事务的执行状态。如果消息服务查询到事务发起方的事务状态为 “执行成功”。同时,当前消息中间件中对应的消息状态为 “待发送”,则将对应的消息投递出去,并且将对应的消息记录更新为 “已发送”。如果消息服务查询到事务发起方的执行状态为 “执行失败”,则消息服务会删除消息中间件中对应的消息,不在投递
  9. 消息中间件也会根据状态向事务发起方投递事务参与方的执行状态,事务发起方会根据状态执行对应的操作,比如事务回滚等

经过上述的流程,事务发起方就能够保证消息发送的一致性了

六、消息接收的一致性

消息发送的一致性主要由事务发起方保证,消息服务进行辅助。消息接收的一致性需要由事务参与方保证,消息服务进行辅助

1.消息接收与确认机制

消息接收的一致性在一定程度上需要满足消息的接收与确认机制,具体过程如下所示:

  1. 消息中间件向消息消费方投递信息
  2. 消息消费方接收到消息中间件投递过来的消息,执行本地业务逻辑,执行完成后,将执行结果发送到消息中间件
  3. 消息中间件接收到消费者发送过来的结果状态,如果状态为 “执行成功”,则删除对应的消息记录,或者将其状态设置为 “已删除”
  4. 如果消息中间件向消息消费方投递消息失败,会根据一定的规则进行重试。如果重试多次后,仍然无法投递到消息消费方,则会将对应的消息存储到死信队列中,后续进行人工干预
  5. 如果消息消费方执行完业务逻辑后,无法成功将结果返回给消息中间件,则同样需要引入重试机制,在消息消费方单独开启一个线程,定时扫描本地数据库中状态为执行完成但向消息中间件发送消息失败的记录,并定时向消息中间件发送状态结果

这里,有两点需要注意:

  1. 消息消费方接收消息中间件消息的接口需要满足幂等性
  2. 消息消费方向消息中间件发送结果状态时,如果需要重试,则应限制最大的重试次数,否则,消息发送操作可能会变成死循环

常规消息中间件无法做到上述流程,在实现分布式事务时,需要手动实现

2.消息接收的不一致性

如果对消息的接收逻辑不做任何限制和处理,消息的接收是不可靠的,会导致每次接收到消息后,对事务的处理得出不同的结果,最终导致消息接收的不一致性,具体表现在如下几个方面:

  1. 事务参与方接收消息的方法没有实现幂等,消息中间件向事务参与方多次重试投递消息时,事务参与方得出不同的业务处理结果,导致事务参与方与事务发起方的事务结果不一致
  2. 事务参与方可能无法收到消息中间件投递的消息,但是消息中间件未实现消息重试投递机制,事务参与方无法执行分支事务,导致事务参与方与事务发起方的事务结果不一致
  3. 事务参与方执行完本地业务逻辑后,无法正确地将执行结果反馈给消息中间件,消息中间件无法正确删除已处理过的消息,会再次向事务发起方重试投递消息,可能会导致事务参与方与事务发起方的事务结果不一致
  4. 事务参与方无法保证完整收到消息中间件投递过来的消息,导致事务参与方与事务发起方的事务结果不一致

事务参与方如果需要保证消息接受的一致性,需要对消息的接收逻辑进行相应的限制和处理,并且消息中间件需要支持重试消息投递的逻辑

3.如何保证消息接收的一致性

如果需要实现消息接收的一致性,则需要解决如下几个问题:

  1. 限制消息中间件重复投递消息的最大次数
  2. 事务参与方接收消息的接口满足幂等性
  3. 实现事务参与方与消息中间件之间的确认机制
  4. 消息中间件中的消息多次重试投递失败后,放入死信队列,后续引入人工干预机制

具体处理流程如下图所示:

在这里插入图片描述

整体流程说明如下所示:

  1. 消息中间件向事务参与方投递消息时,如果投递失败,则会按照一定的重试规则重新投递未确认的消息,也就是会按照一定的规则,扫描发送失败并且状态为 “待发送” 的消息,将其投递给事务参与方
  2. 如果重试次数达到最大重试次数,仍然无法成功将消息投递出去,则将对应的消息存入死信队列,后续通过人工干预投递
  3. 如果消息正确投递出去,则会将数据中存储的对应的消息记录状态更新为 “已发送”
  4. 事务参与方接收到消息中间件投递的消息,执行业务逻辑后,将执行的结果发送给消息中间件
  5. 消息中间件接收到事务参与方发送的确认消息后,根据确认消息更新数据库中对应消息的记录状态。还会根据确认消息执行是否向事务发起方投递消息的逻辑,事务发起方根据接收的消息执行相应的逻辑处理,比如事务回滚等

总之,上述流程需要满足消息中间件重试消息投递时,有最大重试次数。事务接收方的接口需要满足幂等性。事务接收方与消息中间件之间需要实现消息确认机制。消息中间件向事务参与方多次投递消息失败后,达到最大重试次数,需要将消息放入死信队列,并引入人工干预机制

经过上述流程,消息的接收就能够保证一致性了

七、消息的可靠性

在实现可靠消息最终一致性分布式事务时,需要满足消息的可靠性,这里的可靠性包括消息发送的可靠性、消息存储的可靠性和消息消费的可靠性

1.消息发送的可靠性

消息发送的可靠性除了要满足消息发送的一致性,还需要保证事务发起方的可靠性,最简单的实现方式就是多副本机制。也就是说,将事务发起方部署多份,形成集群模式

另外,还需要保证消息生产和发送的可靠性。引入回调确认机制,在事务发起方提供回调接口,在消息发送异常时,消息服务也能通过一定的机制回调事务发起方提供的回调接口,获取事务发起方的事务执行状态和消息数据,确保消息一定被消息服务成功接收。消息服务收到消息后,会返回一个确认信息,表示消息服务已经成功收到事务发起方发送的消息。如果事务发起方在一定时间内未收到消息服务返回的确认消息,就会触发消息重试机制,按照一定的规则重新发送消息

例如,事务发起方消息发送成功,但是由于网络异常,未能收到消息服务返回的确认消息,此时,事务发起方就会按照一定的规则重新发送消息,消息服务就有可能收到多条相同的消息数据。因此消息服务也需要实现幂等

消息的重试机制需要实现响应时长判断逻辑(例如,超出 1 分钟,未收到返回的确认消息,就认为本次消息需要重新发送),也需要对重试的次数进行限制

2.消息存储的可靠性

消息存储的可靠性就是确保消息能够进行持久化,不会因为消息堆积、服务崩溃、服务器宕机、网络故障等因素,造成消息丢失

实现存储的可靠性最简单的方式就是消息存储的多副本机制,将原本只存储一份的消息,冗余存储成多份。目前,大多数消息中间件都实现了消息的冗余副本机制,这里不再赘述

3.消息消费的可靠性

消息消费的可靠性除了要满足消息接收的一致性外,还要确保消息被成功消费,避免由于消息丢失,事务参与方崩溃或者服务器宕机造成消息消费不成功。此时,就需要将事务参与方冗余成多个副本,部署成集群模式

除了事务参与方的多副本机制,还要实现事务参与方的重试机制与幂等机制,按照一定的规则获取消息中间件中的数据,以确保事务发起方成功收到消息并成功消费

例如,使用 RocketMQ 消息中间件实现的分布式事务中,事务参与方从 RocketMQ 消息中间件中拉取消息,如果成功获取消息并执行完本地事务操作,则会向 RocketMQ 返回确认消息。如果事务参与方从 RocketMQ 拉取消息失败,或者消费消息失败,就需要重新获取消息进行消费,如果达到一定的重试次数仍然失败,则会将该消息发送到 RocketMQ 的重试队列中

如果事务参与者崩溃或者服务器宕机,RocketMQ 会认为该消息没有被事务参与方成功消费,会被其他的事务参与方重新消费。此时,就有可能造成事务参与方重复消费的情况,因此需要事务参与方实现消息的幂等操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
第1章 课程介绍 介绍该课程的内容、学习成果、实例,还有学习所需的前提知识。 1-1 导学-分布式事务实践 第2章 事务原则与实现 介绍了事务的四大原则,并通过实例介绍数据库实现事务的方法,以及使用JDBC实现事务的方法。 2-1 事务原则与实现:事务 2-2 事务原则与实现:SQL事务 2-3 事务原则与实现:JDBC事务(上) 2-4 事务原则与实现:JDBC事务(下) 第3章 使用Docker搭建环境 介绍了Docker的使用,通过Docker将课程环境搭建起来,方便那些不了解这些技术的同学之后的学习。 3-1 docker简介与mysql安装-1 3-2 docker简介与mysql安装-2 3-3 SpringBoot基础 第4章 Spring事务机制 介绍了Spring的事务机制、事物抽象、内部事务和外部事物,以及常用的几种事务管理的实现,包括DataSource、JPA、JMS、JTA都通过实例进行说明。还有XA以及两阶段提交,并通过实例演示了使用JTA,通过两阶段提交,实现多数据源的事务实现。... 4-1 Spring事务机制_基本接口 4-2 Spring事务机制_实现 4-3 Jpa事务实例 4-4 Jms事务原理 4-5 Jms-session事务实例 4-6 Jms-spring事务实例 4-7 外部事务与JTA 4-8 JTA单数据源事务实例 4-9 JTA多数据源事务实例 第5章 分布式系统 介绍了分布式系统的定义、实现原则和几种形式,详细介绍了微服务架构的分布式系统,并使用Spring Cloud框架演示了一个完整的微服务系统的实现过程。 5-1 CAP原则和BASE理论简介 5-2 分布式系统综述 5-3 SpringCloud微服务架构 5-4 实现registry 5-5 实现proxy 5-6 user服务 5-7 order服务 5-8 添加hystrix 5-9 使用feign 5-10 优化服务间调用 第6章 分布式事务实现,模式和技术 介绍分布式事务的定义、原则和实现原则,介绍使用Spring框架实现分布式事务的几种方式,包括使用JTA、Spring事务同步、链式事务等,并通过实战介绍其实现。除此以外还介绍了一些分布式事务相关的技术,如幂等性、全局一致性ID、分布式对象等。... 6-1 分布式事务介绍 6-2 spring分布式事务实现_使用JTA 6-3 spring分布式事务实现_不使用JTA 6-4 实例1-DB-DB 6-5 实例1-DB-DB.链式事务管理器 6-6 实例2-JPA-DB.链式事务管理器 6-7 实例3-JMS-DB.最大努力一次提交 6-8 分布式事务实现模式与技术 6-9 全局一致性ID和分布式对象_ 第7章 分布式事务实现:消息驱动模式 详细介绍3种分布式事务实现的模式中的消息驱动模式并通过完整实例演示了消息驱动模式下,实现微服务系统的分布式事务的完整过程。 7-1 分布式事务实现:消息驱动模式 7-2 消息驱动模式实例:设计 7-3 消息驱动模式实例:创建ticket服务 7-4 消息驱动模式实例:实现基本ticket功能 7-5 消息驱动模式实例:锁票1 7-6 消息驱动模式实例:锁票2 7-7 按消息流程实现业务 7-8 支付过程 7-9 票转移 7-10 错误处理:锁票失败 7-11 错误处理:扣费失败 7-12 并发时的错误处理 第8章 分布式事务实现:Event Sourcing模式 详细介绍了分布式事务实现的模式中的Event Sourcing模式,并通过完整实例演示了Event Sourcing模式下,实现微服务系统的分布式事务的完整过程。 8-1 事件溯源模式介绍 8-2 事件溯源模式与Axon框架-1 8-3 事件溯源模式与Axon框架-2 8-4 使用Axon框架的设计过程介绍 8-5 Axon框架-实例(上) 8-6 Axon框架-实例(下) 8-7 Saga模式和Axon Saga 8-8 聚合命令事件(上) 8-9 聚合命令事件(下) 8-10 实现saga 8-11 实现query 8-12 处理超时 8-13 并发测试 8-14 cloud-axon实例:分布式处理介绍 8-15 事件设计 8-16 事件与队列设计 8-17 实现User服务 8-18 实现Ticket服务 8-19 实现Order服务 8-20 实现读写分离 8-21 测试与并发 8-22 事件溯源模式与Axon框架总结 第9章 TCC模式和微服务架构的设计模式 本章介绍TCC模式,也对微服务系统的几种设计模式,以及这些模式下分布式事务的实现模式进行了介绍。 9-1 TCC模式介绍 9-2 微服务架构的设计模式 第10章 课程总
目录回到顶部↑第1章 开发成功的Oracle应用 1 1.1 我的方法 2 1.2 黑盒方法 4 1.3 开发数据库应用的正确(和不正确)方法 8 1.3.1 了解Oracle体系结构 8 1.3.2 理解并发控制 14 1.3.3 多版本 19 1.3.4 数据库独立性? 25 1.3.5 “怎么能让应用运行得更快?” 41 1.3.6 DBA与开发人员的关系 45 1.4 小结 46 第2章 体系结构概述 47 2.1 定义数据库和实例 48 2.2 SGA和后台进程 53 2.3 连接Oracle 56 2.3.1 专用服务器 56 2.3.2 共享服务器 57 2.3.3 TCP/IP连接的基本原理 58 2.4 小结 61 第3章 文件 63 .3.1 参数文件 64 3.1.1 什么是参数? 65 3.1.2 遗留的init.ora参数文件 67 3.1.3 服务器参数文件 69 3.1.4 参数文件小结 75 3.2 跟踪文件 76 3.2.1 请求的跟踪文件 77 3.2.2 针对内部错误生成的跟踪文件 80 3.2.3 跟踪文件小结 83 3.3 警告文件 83 3.4 数据文件 86 3.4.1 简要回顾文件系统机制 86 3.4.2 Oracle数据库中的存储层次体系 87 3.4.3 字典管理和本地管理的表空间 91 3.5 临时文件 93 3.6 控制文件 95 3.7 重做日志文件 95 3.7.1 在线重做日志 96 3.7.2 归档重做日志 98 3.8 密码文件 100 3.9 修改跟踪文件 103 3.10 闪回日志文件 104 3.10.1 闪回数据库 104 3.10.2 闪回恢复区 105 3.11 DMP文件(EXP/IMP文件) 106 3.12 数据泵文件 107 3.13 平面文件 110 3.14 小结 111 第4章 内存结构 113 4.1 进程全局区和用户全局区 113 4.1.1 手动PGA内存管理 114 4.1.2 自动PGA内存管理 121 4.1.3 手动和自动内存管理的选择 131 4.1.4 PGA和UGA小结 132 4.2 系统全局区 133 4.2.1 固定SGA 137 4.2.2 重做缓冲区 137 4.2.3 块缓冲区缓存 138 4.2.4 共享池 145 4.2.5 大池 148 4.2.6 Java池 149 4.2.7 流池 150 4.2.8 自动SGA内存管理 150 4.3 小结 151 第5章 Oracle进程 153 5.1 服务器进程 153 5.1.1 专用服务器连接 154 5.1.2 共享服务器连接 156 5.1.3 连接与会话 157 5.1.4 专用服务器与共享服务器 163 5.1.5 专用/共享服务器小结 166 5.2 后台进程 167 5.2.1 中心后台进程 168 5.2.2 工具后台进程 175 5.3 从属进程 178 5.3.1 I/O从属进程 178 5.3.2 并行查询从属进程 179 5.4 小结 179 第6章 锁 181 6.1 什么是锁? 181 6.2 锁定问题 184 6.2.1 丢失更新 184 6.2.2 悲观锁定 185 6.2.3 乐观锁定 187 6.2.4 乐观锁定还是悲观锁定? 197 6.2.5 阻塞 198 6.2.6 死锁 201 6.2.7 锁升级 206 6.3 锁类型 206 6.3.1 DML锁 207 6.3.2 DDL锁 215 6.3.3 闩 218 6.3.4 手动锁定和用户定义锁 226 6.4 小结 227 第7章 并发与多版本 229 7.1 什么是并发控制? 229 7.2 事务隔离级别 230 7.2.1 READ UNCOMMITTED 232 7.2.2 READ COMMITTED 233 7.2.3 REPEATABLE READ 235 7.2.4 SERIALIZABLE 237 7.2.5 READ ONLY 239 7.3 多版本读一致性的含义 240 7.3.1 一种会失败的常用数据仓库技术 240 7.3.2 解释热表上超出期望的I/O 241 7.4 写一致性 244 7.4.1 一致读和当前读 244 7.4.2 查看重启动 247 7.4.3 为什么重启动对我们很重要? 250 7.5 小结 251 第8章 事务 253 8.1 事务控制语句 254 8.2 原子性 255 8.2.1 语句级原子性 255 8.2.2 过程级原子性 257 8.2.3 事务级原子性 260 8.3 完整性约束和事务 260 8.3.1 IMMEDIATE约束 260 8.3.2 DEFERRABLE约束和级联更新 261 8.4 不好的事务习惯 263 8.4.1 在循环中提交? 264 8.4.2 使用自动提交? 270 8.5 分布式事务 271 8.6 自治事务 273 8.6.1 自治事务如何工作? 273 8.6.2 何时使用自治事务? 276 8.7 小结 279 第9章 redo与undo 281 9.1 什么是redo? 281 9.2 什么是undo? 282 9.3 redo和undo如何协作? 285 9.4 提交和回滚处理 289 9.4.1 COMMIT做什么? 289 9.4.2 ROLLBACK做什么? 296 9.5 分析redo 297 9.5.1 测量redo 298 9.5.2 redo生成和BEFORE/AFTER触发器 300 9.5.3 我能关掉重做日志生成吗? 306 9.5.4 为什么不能分配一个新日志? 310 9.5.5 块清除 312 9.5.6 日志竞争 315 9.5.7 临时表和redo/undo 317 9.6 分析undo 321 9.6.1 什么操作会生成最多和最少的undo? 321 9.6.2 ORA-01555: snapshot too old错误 323 9.7 小结 334 第10章 数据库表 335 10.1 表类型 335 10.2 术语 337 10.2.1 段 337 10.2.2 段空间管理 339 10.2.3 高水位线 340 10.2.4 freelists 342 10.2.5 PCTFREE和PCTUSED 345 10.2.6 LOGGING和NOLOGGING 348 10.2.7 INITRANS和MAXTRANS 349 10.3 堆组织表 349 10.4 索引组织表 352 10.5 索引聚簇表 368 10.6 散列聚簇表 376 10.7 有序散列聚簇表 386 10.8 嵌套表 390 10.8.1 嵌套表语法 390 10.8.2 嵌套表存储 399 10.8.3 嵌套表小结 402 10.9 临时表 402 10.10 对象表 410 10.11 小结 418 第11章 索引 421 11.1 Oracle索引概述 422 11.2 B*树索引 423 11.2.1 索引键压缩 426 11.2.2 反向键索引 429 11.2.3 降序索引 435 11.2.4 什么情况下应该使用B*树索引? 437 11.2.5 B*树小结 448 11.3 位图索引 448 11.3.1 什么情况下应该使用位图索引? 449 11.3.2 位图联结索引 453 11.3.3 位图索引小结 455 11.4 基于函数的索引 456 11.4.1 重要的实现细节 456 11.4.2 一个简单的基于函数的索引例子 457 11.4.3 只对部分行建立索引 465 11.4.4 实现有选择的惟一性 467 11.4.5 关于CASE的警告 467 11.4.6 关于ORA-01743的警告 469 11.4.7 基于函数的索引小结 470 11.5 应用域索引 470 11.6 关于索引的常见问题和神话 472 11.6.1 视图能使用索引吗? 472 11.6.2 Null和索引能协作吗? 472 11.6.3 外键是否应该加索引? 475 11.6.4 为什么没有使用我的索引? 476 11.6.5 神话:索引中从不重用空间 483 11.6.6 神话:最有差别的元素应该在最前面 486 11.7 小结 490 第12章 数据类型 491 12.1 Oracle数据类型概述 491 12.2 字符和二进制串类型 494 12.2.1 NLS概述 494 12.2.2 字符串 497 12.3 二进制串:RAW类型 504 12.4 数值类型 506 12.4.1 NUMBER类型的语法和用法 509 12.4.2 BINARY_FLOAT/BINARY_DOUBLE类型的语法和用法 513 12.4.3 非固有数值类型 513 12.4.4 性能考虑 514 12.5 LONG类型 515 12.5.1 LONG和LONG RAW类型的限制 516 12.5.2 处理遗留的LONG类型 517 12.6 DATE、TIMESTAMP和INTERVAL类型 523 12.6.1 格式 523 12.6.2 DATE类型 525 12.6.3 TIMESTAMP类型 533 12.6.4 INTERVAL类型 541 12.7 LOB 类型 544 12.7.1 内部LOB 545 12.7.2 BFILE 557 12.8 ROWID/UROWID类型 559 12.9 小结 560 第13章 分区 561 13.1 分区概述 561 13.1.1 提高可用性 562 13.1.2 减少管理负担 564 13.1.3 改善语句性能 569 13.2 表分区机制 571 13.2.1 区间分区 571 13.2.2 散列分区 574 13.2.3 列表分区 579 13.2.4 组合分区 581 13.2.5 行移动 583 13.2.6 表分区机制小结 585 13.3 索引分区 586 13.3.1 局部索引与全局索引 587 13.3.2 局部索引 587 13.3.3 全局索引 594 13.4 再论分区和性能 610 13.5 审计和段空间压缩 617 13.6 小结 618 第14章 并行执行 619 14.1 何时使用并行执行 620 14.2 并行查询 622 14.3 并行DML 628 14.4 并行DDL 631 14.4.1 并行DDL和使用外部表的数据加载 632 14.4.2 并行DDL和区段截断 634 14.5 并行恢复 643 14.6 过程并行化 643 14.6.1 并行管道函数 644 14.6.2 DIY并行化 648 14.7 小结 652 第15章 数据加载和卸载 655 15.1 SQL*Loader 655 15.1.1 用SQLLDR加载数据的FAQ 660 15.1.2 SQLLDR警告 686 15.1.3 SQLLDR小结 686 15.2 外部表 687 15.2.1 建立外部表 688 15.2.2 处理错误 693 15.2.3 使用外部表加载不同的文件 697 15.2.4 多用户问题 697 15.2.5 外部表小结 698 15.3 平面文件卸载 698 15.4 数据泵卸载 708 15.5 小结 710 索引 711
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 C#开发基于FreeSql多库分布式事务、跨库查询、跨库分页查询、跨库增删改等功能实现源码+项目说明+sln.zip **前言** 话说2021年开始了一个基于ASP.NET Core 微服务的项目,谈到微服务 多库环境下 分布式事务、分库分表这些问题都是逃不开的,于是首先从ORM开始调研,需要考虑到一些重要的因素 **功能强大、支持多种数据库(并且行为一致,防止出现换库的情况)、支持分库分表** 等等,这时候第一时间就想到了 [FreeSql](https://github.com/dotnetcore/FreeSql) ,FreeSql的架构设计非常好,每一种支持的数据库都有对应的Provider实现 做到行为一致,而且支持CodeFirst和DbFirst,分库分表FreeSql也有比较简单切有效的方案,本人也经常向FreeSql的作者叶老板请教学习,非常佩服他的技术与人品,也非常感谢他能做出这么好的ORM框架。 **分布式事务** 既然分库了 分布式事务怎么处理,说到分布式事务 常见的解决方案有TCC/SAGA/消息队列最终一致性,在.NET生态中有基于消息队列实现的分布式事务 [CAP](https://github.com/dotnetcore/CAP) ,TCC和SAGA调研了很久没有发现有比较成熟的实现,那么就决定使用`CAP(最终一致性事务)` 由于项目持续的改版,业务的实时性变得越来越高,基于消息队列的这种最终一致性或者说异步事务的方案 越来越不适合我们的项目,这时候就需要同步的事务方案,TCC/SAGE又没有太好的解决方案(我真的没有找到。。),于是想着自己设计一个,基于FreeSql实现事务管理器。 想要的效果:和单库事务一样,出现错误回滚 但是问题来了 多库呢?不同的数据库呢? * 在多库事务的开启时,每个库管理开启自己的事务 * 如果某一个库事务开启后的操作出现异常,则回滚全部数据库事务 * 在多库事务提交时,每个库的事务统一提交 * 记录日志,第一个执行Common的数据库称之为主库,会自动创建一个日志表,用于记录多库事务的信息、执行的SQL、业务模块 用于人工介入或者事务补偿 * 如果主库(第一个库)Common成功后,其他某一个库可能由于网络原因、数据库宕机 无法Common事务,导致数据不一致,这时候要根据日志进行事务补偿或者人工介入,例如 存在三个库(订单库、物流库、商品库) 订单库就是主库(会记录日志) 在Common事务时,如果订单库(主库)Common失败,则(订单库、物流库、商品库)事务全部回滚,如果`订单库`(主库)Common成功,但是`物流库`由于其他原因无法Common成功 则会被日志记录并跳过,然后再去Common `商品库` 以及其他库.. **跨库查询/跨库分页查询** 通过时间分片定位、事件委托、分页算法实现跨库分页查询 1.appsettings.json配置 2.初始化数据库 3.获取IFreeSql操作对象 5.跨库分页查询 6. 跨库增删改 7.跨库并行查询(不分页) 8.跨库ToOne查询 9.跨库Any查询 10.分布式事务、多库事务

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

313YPHU3

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值