源码
源代码: https://github.com/rudolflin/transaction-demo.git
依赖: consul(其实用不到, 只是为了做分布式事务未拆分前的demo使用) ,mysql ,rocketmq
以下部分全部摘自源码中的readme文件,图片懒得copy了,建议参考源代码.
分布式事务--本地消息表, rocketmq
- 采用消息中间件实现, 其实就是保证两方面, 生产者侧本地事务执行成功后一定会发消息,发消息一定会到brocker(代码实现). 消费者测一定会不断重试,直到成功(mq实现),极端情况人工处理(代码实现)
- 人工处理待开发
本地消息表方式
demo a,b,c功能
-
a->b->c三个服务依次调用, 每个服务的account表都减同样数额的钱
特点
- 最终一致性的前提是认为下游服务一定可以正确完成任务, 如果遇到突发事件, 通过重试最终也能完成.
- 发消息通过本地事务表加定时任务完成,这个是本地消息表完成分布式事务的保证. 保证了本地事务执行一定会发消息,发消息一定会成功.
- 服务消费要实现幂等, 但是要注意处理业务本地数据,写消息表(向下游发送消息),写去重表(幂等)要在一个本地事务里面.
- 判断幂等要在做业务之前,标记成功标志要在业务之后, 尽量和业务分离开.
- 目前想到的幂等方案只有去重表. 其他方案: 布隆过滤器(不是完全准确,有一定的错误率).关键在于,更新布隆过滤器和或者其他方案的去重标记, 要和业务事务,插入消息表等操作
放在一起.否则可能出现, 业务做完了,做成功的标记没有打上. - 需要两张表: task表(记录要发送mq的任务-生产者使用), 去重表(记录那些消息处理过-生产者使用)
rocketmq事务方式
-
需要两张张表: 1、本地事务记录表, 本地业务执行成功后, 保存一条记录, 供brocker反查,. 本地业务执行和记录事务成功状态要在一个本地事务里. 2、去重表(消费者使用,这种方式消息重复的概率较小)
-
生产者是依靠第5步查询,保证本地事务执行后,消息一定会发送成功的。 其实就是定时任务部分的工作 ,由mq来做了。
-
事务成功状态的保存, 要生成一个业务侧的key, 用于反查, 这个key可以放在(keys)字段里面 , 这样在messageExt中就能取到了.
-
事务状态表, 里边其实只有一个事务id就可以了, 事务状态不是必须的 , 因为只要存进来的, 都是成功的, 否则就是失败了, 也存不进来.
-
service层将参数信息处理后封装成model . 然后发mq, 这里是没有事务控制的. 真正的业务和存消息表另起一个service执行. 由mqlistener自动调用执行. 时机是在发送半消息成功之后.
-
transactionid, 直接使用sendmessageintransaction之后,excutelocaltransaction中传入的message中的id即可.
-
unknow其实只作为调试使用
-
事务回查
-
-
消费者只是普通消费着, 做好幂等即可
-
rocketmq 回查超过一定次数, 就会rollback消息
-
监控报警部分: 需要监控死信队列 ,或者在消息消费时做记录, 失败时存一下记录, 当失败多次后出发邮件报警. 因为死信触发的时间比较长,或者直接改这个死信时间间隔和次数也可以, 正常情况下 , 两三次失败后, 下次也会失败. (两三次失败时为了兼容网络抖动等的影响), 然后做一个消费者组消费死信队列, 触发人工报警, 将异常信息放入另一个topic或者数据表里, 等待人工修复.
报警部分待补充
-
rocketmq的回查次数 ,频率,
-
死信队列 报警(人工介入), (下游执行失败)
- 重试队列是以%RETRY%+consumerGroup作为维度的生成consumeQueue。
- 死信队列是以%DLQ%+consumerGroup作为维度的生成consumeQueue。
- 进入死信队列的条件是重试次数超过了最大重试次数。
- 死信队列的topic是在消息发送过程中判断对应的topic是否存在,不存在就动态进行创建。