简介:从利用场景登程,给出解决方案与实现原理,并提供整套工业级实现源码。
作者:丁威
活动中心场景介绍
在电商零碎上线初期,往往会进行一些“拉新”流动,例如流动部门提出新用户注册送积分、送优惠券流动。
基于分布式、微服务的设计理念,通常的架构设计(子系统交互)如下图所示:
其外围零碎介绍如下:
- 账户核心
提供用户登录、用户注册等服务,一个新用户注册时,向 MQ 服务器中的 USER\_REGISTER 主题发送一条音讯,主流程完结,与送积分,送优惠券等过程解耦。 - 优惠券(券零碎)
提供发放优惠券、应用优惠券等与券相干的根底服务。 - 积分核心
提供积分相干的服务,例如积分赠送、积分生产、积分查问等根底服务。 - 送积分服务(消费者)
订阅 MQ,依照规定决定是否须要赠送积分,如果须要则调用积分相干的根底接口,实现积分的发放。 - 送优惠券(消费者)
订阅 MQ,依照规定决定是否须要赠送优惠券,如果须要则调用券零碎相干的根底接口,实现优惠券的发放。
下面的架构设计十分优雅,但并不是无懈可击,如果新用户注册胜利,但音讯发送到 MQ 失败,或者音讯胜利发送到 MQ,但发送完 MQ 后零碎出现异常导致用户注册失败又该如何呢?
下面的问题其实就是典型的分布式事务问题:即如何保障用户注册 (数据库操作) 与 MQ 音讯发送这两个分布式操作的一致性。
RocketMQ 事务音讯闪亮退场。
事务音讯实现原理
一言以蔽之:RocketMQ 事务音讯要解决的问题是音讯发送与业务的一致性,其解决思路:二阶段提交与事务状态回查,其具体实现流程如下图所示:
其外围设计理念:
- 应用程序开启一个数据库事务,进行数据库操作,并且在事务中发送一条 PREPARE 音讯,PREPARE 音讯发送胜利后告诉应用程序记录本地事务状态,而后提交本地事务。
- RocketMQ 在收到类型为 PREPARE 的音讯时,首先 备份音讯的原主题与原音讯生产队列,而后将音讯存储在主题为 RMQ\_SYS\_TRANS\_HALF\_TOPIC 的音讯队列中,故 PREPARE 的音讯是不会被客户端生产的。
- Broker 音讯服务器开启一个定时工作解决 RMQ\_SYS\_TRANS\_HALF\_TOPIC 中的音讯,会每隔指定工夫向音讯发送者发动 事务状态查问申请 , 询问音讯发送者客户端本地事务是否胜利,而后依据回查状态决定是提交还是回滚,即对处于 PREPARE 状态进行提交或回滚操作。
- 发送者如果明确得悉事务胜利,则能够返回 COMMIT,服务端会提交该条音讯,具体操作是恢复原音讯的主题与队列,从新发送到 Broker,生产端感知后生产。
- 发送者如果无奈明确得悉事务状态,则返回 UNOWN,此时服务端会期待肯定工夫后再次向发送者询问,默认询问 15 次。
- 发送者如果十分明确得悉事务失败,则能够返回 ROLLBACK。
在具体实际中,音讯发送者在无奈获取事务状态时不要果断的返回 ROLLBACK,而是要返回 UNOWN,让服务端定时重试回查,阐明如下:
在将 PREPARE 音讯发送到 Broker 后,服务端发动事务查问时本地事务可能还未提交,为了防止有效的事务回查机制,RocketMQ 通常至多在收到 PREPARE 音讯 6s 后才会发动第一次事务回查,可通过 transactionTimeOut 配置。故客户端在实现事务回查时无奈证实事务状态时不应该返回 ROLLBACK,而是返回 UNOWN。
事务音讯实战
光说不练假把式,接下来以一个新用户注册送优惠券的场景来具体介绍如何应用事务音讯。
我的项目模块职责阐明如下:
事务音讯的外围代码组装在 transaction-service,其外围类图如下:
其中外围要点如下:
- UserServiceImpl
Dubbo 接口业务实现类,相似 MVC 的管制层,在这里做一些参数验证,但不执行具体的业务逻辑,只是发送一条事务音讯到 MQ。 - UserRegTransactionListener
事务监听器,在 executeLocalTransaction 办法中执行业务逻辑,数据库本地事务加在该办法。
舒适提醒:之所以不在 UserServicveImpl 中执行本地事务,是因为 executeLocalTransaction 中抛出的异样会被 RocketMQ 框架捕获,及异样无奈被 UserServiceImpl 感知,即无奈实现其事务的一致性。
接下来展现其外围代码,全副源码已上传到 github 仓库。
仓库地址:https://github.com/dingwpmz/rocketmq-learning。
UserServiceImpl 外围实现
UserServiceImpl 的 外围要点如下:
- 首先应该对参数进行校验、业务逻辑进行校验,如果不满足业务条件,会发送一些有效音讯到 MQ,尽管不会造成业务异样,但会耗费性能。
- 发送事务音讯,倡议对音讯设置 Key,Key 的值能够用业务解决流水号 (可惟一示意该业务操作) 或者外围业务字段(例如订单编号)。
- 业务入口类可通过事务音讯发送状态来判断业务是否失败。
UserRegTransactionListener 外围实现
事务监听器须要实现执行本地事务与事务回查两个接口。
1、实现 executeLocalTransaction
首先须要实现 executeLocalTransaction 办法,执行本地事务,其代码如下图所示:
其中几个关键点阐明如下:
- 在该办法上增加数据库事务标签。
- 执行业务逻辑,示例 Demo 只是将用户数据存储到数据库。
- 如果业务执行失败,可明确告知须要回滚,下层调用方也可依据 ROLLBACK\_MESSAGE 进行相应的解决。
- 如果业务胜利,不倡议间接返回 COMMIT,而是倡议返回 UNKNOW, 因为该办法只管在办法最初一行,但可能产生断电等异常情况,数据库并没有胜利。
2、实现 checkLocalTransaction
其次须要实现事务状态回查,用来 RocketMQ 服务端感知事务是否胜利,其实现原理如下图所示:
其实现关键点如下:
- 如果能明确得悉本地事务胜利,则返回 COMMIT\_MESSAGE
- 如该不能明确得悉本地事务胜利,不能返回 ROLLBACK\_MESSAGE, 而是返回 UNKNOW,期待服务端下一次事务回查 (不会立刻触发), 服务端默认回查 15 次,如果 15 次都失去 UNKNOW,则会回滚该音讯。
代码获取
上文只是将事务音讯的外围代码加以解读,并重点论述每个步骤的实现关键点,笔者基于 SpringBoot,尝试联合场景学习 RocketMQ 的应用技巧,其代码上传到了 github 仓库:https://github.com/dingwpmz/rocketmq-learning。
点击跳转到代码仓库。
版权申明:本文内容由阿里云实名注册用户自发奉献,版权归原作者所有,阿里云开发者社区不领有其著作权,亦不承当相应法律责任。具体规定请查看《阿里云开发者社区用户服务协定》和《阿里云开发者社区知识产权爱护指引》。如果您发现本社区中有涉嫌剽窃的内容,填写侵权投诉表单进行举报,一经查实,本社区将立即删除涉嫌侵权内容。