引言
Hi 你好,我是有清
RocketMQ 作为一款消息中间件,它的信息的投递与消费,通常都会与数据库的更新进行挂钩,那么如何保证 消息和数据库的更新是一个原子性的操作呢?
比如在我数据库更新失败的时候,不进行消息的发送,或者在我发送消息失败的时候,数据库的更新进行回滚
这边总结三种最佳实践,提供给大家作为参考学习
1. 错误思路
正确方案分享之前,先分享一个错误做法,看下你是否踩坑
一旦发送 mq 超时,抛出异常,整体事务回滚,下游感知到订单更新成功,但是数据库依旧还是之前的状态,就出现了数据不一致的情况了
2. 基于 RocketMQ 的分布式事务
事务消息是 Apache RocketMQ 提供的一种高级消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性
2.1 事务消息交互流程
生产者将消息发送至Apache RocketMQ服务端。
Apache RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息被标记为"暂不能投递",这种状态下的消息即为半事务消息。
生产者开始执行本地事务逻辑。
生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
- 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
- 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。
生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
生产者根据检查到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。
2.2 代码示例
2.3 注意事项
避免大量未决事务导致超时
Apache RocketMQ支持在事务提交阶段异常的情况下发起事务回查,保证事务一致性。但生产者应该尽量避免本地事务返回未知结果。大量的事务检查会导致系统性能受损,容易导致事务处理延迟。
正确处理"进行中"的事务
消息回查时,对于正在进行中的事务不要返回Rollback或Commit结果,应继续保持Unknown的状态。 一般出现消息回查时事务正在处理的原因为:事务执行较慢,消息回查太快。解决方案如下:
- 将第一次事务回查时间设置较大一些,但可能导致依赖回查的事务提交延迟较大。
- 程序能正确识别正在进行中的事务。
3. 基于本地事务的一致性处理
在Spring框架中基于本地消息服务实现MQ最终一致性的过程大致如下:
- 业务数据更新:业务服务处理业务逻辑,在事务的保护下更新数据库中的业务数据。
- 创建本地消息记录:在同一个业务事务中,业务服务创建本地消息记录,并保存到数据库。这条记录包含了将来要发送到消息队列(MQ)的消息。
- 业务事务提交:业务事务成功提交,确保业务数据的更新和消息记录的创建在数据库中保持原子性和一致性。
- 异步发送消息:通过定时任务或者事件驱动机制,一个独立于业务逻辑的消息服务定期轮询数据库中的消息记录表,查找处于未发送状态的消息。
- 发送MQ消息:消息服务从本地消息表中取出消息记录,向MQ发送消息。成功发送后,更新本地消息记录的状态以表示消息已成功发出。
- 处理发送失败:若消息发送失败,比如出现网络问题或者MQ不可用,消息服务记录失败事件,并可根据配置进行重试,直至消息发送成功或者达到最大重试次数。
- 消息消费者处理:MQ消费者接收并处理消息。若成功处理(例如,执行了一些跨服务的业务逻辑),则结束。若消费失败(比如业务逻辑异常或者临时错误),通常MQ会重新入队此消息,进行后续重试直至成功或达到重试上限。
- 消息状态回查:对于在发送过程中失败或者状态模糊的消息,可能还需要一个检查机制。消息服务可能会对这些消息进行回查,重新核实业务逻辑的完成情况,并决定是否重新发布消息到MQ。
整个过程用于确保即使发生系统故障,也能够通过后续的消息重发和消费重试来达成最终数据的一致性。
3.1 时序说明
3.2 伪代码
4. 基于 binlog 变动实现消息一致性
基于监听数据库的binlog(二进制日志)来实现消息一致性是一种有效的技术手段,尤其是在需要跨多个数据库和服务进行数据同步时。这种方法通常称为Change Data Capture(CDC),它能够监听数据库的变更事件,并将这些事件转换为消息,然后传递到消息队列系统中,以便其他服务进行消费。
以下是基于监听binlog实现消息一致性的大致步骤:
- 监听数据库的binlog:需要一种机制或工具来监听数据库的binlog。这些工具可以是开源的如Debezium,Maxwell或者商业产品。
- 变更事件捕获:当数据库发生写入、更新或删除操作时,这些变更会被记录在binlog中。监听工具捕获这些事件并生成相应的变更事件。
- 变更事件发布:捕获的变更事件被发布到消息队列系统中。
- 处理消息队列中的事件:其他服务作为消息消费者,从消息队列中读取并处理这些变更事件,实现数据的最终一致性。
- 幂等性和错误处理:为了确保系统的健壮性,消费者服务应该具备正确处理重复消息的能力(幂等性)以及恰当的错误处理机制。