其他
事务消息应用场景、实现原理与项目实战(附全部源码)
The following article is from 中间件兴趣圈 Author 丁威
活动中心场景介绍
其核心系统介绍如下:
账户中心
提供用户登录、用户注册等服务,一个新用户注册时,向 MQ 服务器中的 USER_REGISTER 主题发送一条消息,主流程结束,与送积分,送优惠券等过程解耦。优惠券(券系统)
提供发放优惠券、使用优惠券等与券相关的基础服务。积分中心
提供积分相关的服务,例如积分赠送、积分消费、积分查询等基础服务。送积分服务(消费者)
订阅 MQ,按照规则决定是否需要赠送积分,如果需要则调用积分相关的基础接口,完成积分的发放。送优惠券(消费者)
订阅 MQ,按照规则决定是否需要赠送优惠券,如果需要则调用券系统相关的基础接口,完成优惠券的发放。
事务消息实现原理
其核心设计理念:
应用程序开启一个数据库事务,进行数据库操作,并且在事务中发送一条 PREPARE 消息,PREPARE 消息发送成功后通知应用程序记录本地事务状态,然后提交本地事务。 RocketMQ 在收到类型为 PREPARE 的消息时,首先备份消息的原主题与原消息消费队列,然后将消息存储在主题为 RMQ_SYS_TRANS_HALF_TOPIC 的消息队列中,故 PREPARE 的消息是不会被客户端消费的。 Broker 消息服务器开启一个定时任务处理 RMQ_SYS_TRANS_HALF_TOPIC 中的消息,会每隔指定时间向消息发送者发起事务状态查询请求 ,询问消息发送者客户端本地事务是否成功,然后根据回查状态决定是提交还是回滚,即对处于 PREPARE 状态进行提交或回滚操作。 发送者如果明确得知事务成功,则可以返回 COMMIT,服务端会提交该条消息,具体操作是恢复原消息的主题与队列,重新发送到 Broker,消费端感知后消费。 发送者如果无法明确得知事务状态,则返回 UNOWN,此时服务端会等待一定时间后再次向发送者询问,默认询问 15 次。 发送者如果非常明确得知事务失败,则可以返回 ROLLBACK。
在将 PREPARE 消息发送到 Broker 后,服务端发起事务查询时本地事务可能还未提交,为了避免无效的事务回查机制,RocketMQ 通常至少在收到 PREPARE 消息 6s 后才会发起第一次事务回查,可通过 transactionTimeOut 配置。故客户端在实现事务回查时无法证明事务状态时不应该返回 ROLLBACK,而是返回 UNOWN。
事务消息实战
事务消息的核心代码组装在 transaction-service,其核心类图如下:
其中核心要点如下:
UserServiceImpl
Dubbo 接口业务实现类,类似 MVC 的控制层,在这里做一些参数验证,但不执行具体的业务逻辑,只是发送一条事务消息到 MQ。UserRegTransactionListener
事务监听器,在 executeLocalTransaction 方法中执行业务逻辑,数据库本地事务加在该方法。
温馨提示:之所以不在 UserServicveImpl 中执行本地事务,是因为 executeLocalTransaction 中抛出的异常会被 RocketMQ 框架捕捉,及异常无法被 UserServiceImpl 感知,即无法实现其事务的一致性。
UserServiceImpl 核心实现
UserServiceImpl 的核心要点如下:
首先应该对参数进行校验、业务逻辑进行校验,如果不满足业务条件,会发送一些无效消息到 MQ,虽然不会造成业务异常,但会消耗性能。 发送事务消息,建议对消息设置 Key,Key 的值可以用业务处理流水号(可唯一表示该业务操作)或者核心业务字段(例如订单编号)。 业务入口类可通过事务消息发送状态来判断业务是否失败。
UserRegTransactionListener 核心实现
1、实现 executeLocalTransaction
其中几个关键点说明如下:
在该方法上添加数据库事务标签。 执行业务逻辑,示例 Demo 只是将用户数据存储到数据库。 如果业务执行失败,可明确告知需要回滚,上层调用方也可根据 ROLLBACK_MESSAGE 进行相应的处理。 如果业务成功,不建议直接返回 COMMIT,而是建议返回 UNKNOW,因为该方法尽管在方法最后一行,但可能发生断电等异常情况,数据库并没有成功。
2、实现 checkLocalTransaction
其实现关键点如下:
如果能明确得知本地事务成功,则返回 COMMIT_MESSAGE 如该不能明确得知本地事务成功,不能返回 ROLLBACK_MESSAGE,而是返回 UNKNOW,等待服务端下一次事务回查(不会立即触发),服务端默认回查 15 次,如果 15 次都得到 UNKNOW,则会回滚该消息。
代码获取
﹀
﹀
﹀