consistent-message 可靠消息组件

一.背景

一些业务场景需要在db操作成功后,告知其他服务,“告知”动作需满足:“异步”、“可靠”(此处指db操作成功则发送出去且不丢消息,db操作失败则不发送)、“解耦”

二.实现分析

“异步”首先想到MQ; “解耦”也会想到MQ; “可靠”,MQ能否满足? 消息传递有三个阶段:

1.producer—>mq;

2.mq内部保存;

3.mq—>consumer.

我们使用的_rocketMQ_能够保证2和3,但是1不是单单MQ能够保证的,需要producer配合解决。 怎样配合?我们一般有两种方案: ###1.rocketMQ提供事务性接口,保证"producer–>mq"与"db操作"两个操作的“原子性”。 rocketMQ确实存在这样的事务性api,只需要我们修改发送消息方式,可惜这个api接口并没有开源。暂时放弃。 ###2.将"producer–>mq"当做一个记录存入本地db中,与“db操作”在一个库中,由db来保证这两步的原子性。 然后由后台线程将"producer–>mq"的db记录从表中取出来发送到mq,若发送失败则再次发送。(成功、失败根据mq的反馈)

目前我们采用第二种方式,这也是所介绍的“可靠消息组件”所要实现的:

1.拦截方法,解析参数

2.事务中将所拦截方法及参数入库

3.异步捞取消息发送到rocketMQ;

三.特别说明

###1.消息不会丢失,但是在中间件(如MQ)或网络异常情况下可能会重复发送,我们尽可能的不重复发送。 需要使用者在消息中加入幂等单号,在consumer中根据幂等单号做幂等判断 ###2.消息发送是无序的,时间紧邻的消息可能后产生的先到达。 使用者如果要求消息的顺序性,只能在consumer端通过message.getUserProperty("_MESSAGE_CREATE_TIME")获取消息入库时间,或者在消息体中加入时间戳,然后自己处理。

3.消息发送有延时,秒级,消息量大时可能更长。

四.使用方法

使用步骤

1.针对每个业务名称及bizName建表

CREATE TABLE `consistent_message` (

`id` varchar(32) NOT NULL COMMENT '唯一ID',

`content` text NOT NULL COMMENT '业务名称',

`create_time` timestamp NOT NULL DEFAULT NULL COMMENT '创建时间',

`execute_time` timestamp NOT NULL DEFAULT NULL COMMENT '执行时间',

`business_code` varchar(255) DEFAULT NULL COMMENT '消息对应业务订单号',

`state` int(1) DEFAULT '0' COMMENT '消息状态(0 初始化 1 发送成功 2处理中)',

`repeat_count` int(11) DEFAULT '0' COMMENT '重复发送次数',

`content_class` varchar(255) DEFAULT NULL COMMENT '消息对应的类名',

PRIMARY KEY (`id`)

) COMMENT '消息表,用于将消息可靠的发送到mq';

2.在要拦截的方法上添加@ConsistentMessage_$_If_Change_This_Method_Definition_Please_Inform注解

3.spring配置文件中配置上每个要拦截的方法,要与添加注解的方法一致

4.实现AbstractMessageConverter类中convertMessage方法做_拦截事件_到_消息_的转换

###注意点

1.spring xml方法拦截配置、代码注解配置,通过此双重配置和启动时校验,减少配置与代码分离导致的:代码变动而xml配置没有相应变动的隐藏性问题。

2.如果重写的AbstractMessageConverter返回的结果为null,则本次拦截不当做消息持久化,即忽略,若想将此种情况记录下来,可设置AbstractMessageConverter的logIgnored属性为true.

3.拦截的方法一般为dao中方法,一般会有int型返回结果标识本次操作是否修改的数据,对于返回0的情况,即没有修改的实际数据,如果需要忽略,可对convertMessage方法中的result参数 做判断 :

if (!(result instanceof Integer) || (Integer) result == 0) {
//只对Integer类型做判断,因为mybatis dml操作返回的都是int类型
return null;
}

4.使用者可扩展该接口并配置在SenderConfig中,以取代默认的将消息发送到rocketmq。即不一定非要将消息发送到MQ

5.ConsistentMessageSessionContextHolder,可通过此类放置一些属性到上下文中,放入属性可在MessageConverter中获取,在拦截结束后被清除。

spring配置

class="com.lanyejingyu.component.consistentmsg.core.ApplicationContext">

com.test.dal.dataobject.CashOrder

com.test.dal.dataobject.CashOrder

consistentMessageInterceptor