MQ收到无序的消息时如何进行业务处理

业务背景

跟第三方系统做对接,双方通过ActiveMQ进行通信,消息之间是有内在关联的,也就是消息本来应该是有业务顺序的,但由于一些原因,现在收到消息是乱序的,这种情况下做业务处理就有一点小问题了

方案一:自己重排序

收到消息后,自己在内存排序,然后按顺序丢到队列中,自己控制消息的发送和接收保证收到按发送的顺序来收到消息。

如果自己排序的话就要对每个消息标记一个顺序,同时还要指定预先定义好哪些消息属于一类并且相互之间有依赖顺序。

具体实现的话,可以这样做:

1、收到一条消息,封装一下加个序号,放到Redis中,用列表或者有序集合来存储,同时用字符串类型存一下这个业务单号的当前最小序号(默认是1)

2、如果是用有序集合存的话,用序号当做分数,这样消息丢进去就已经排好序了,每次用最小分数的那个元素和当前最小序号比较,如果小于或等于,则删除这个元素,将它发送到MQ,同时最小序号加1

3、如果用列表存的话,可以lpush添加,lrange查最先放进去的那个元素,和当前最小序号比较,小于或等于,则rpop删除,发MQ,最小序号加1

大概就是这么个意思,以上是我的思路,没有实现,感觉应该是可行的

方案二:定时任务扫描

1、收到消息后,先存到数据库中,这条记录的状态为“未消费”

2、进行业务处理

(1)如果处理这条消息不需要依赖其它的消息,或者它依赖的消息已经先于它被处理了,那么直接做业务处理,完成后更新消息记录表,将这条记录的状态置为“已处理”

(2)如果这条消息依赖的消息还没有收到(通常表现为可能是某个表的数据状态不对或者没有数据,等等),则不处理

3、定时任务扫描消息记录表,找到那些状态为“未处理”的数据,调用统一的消息处理接口,依次执行,逻辑同上

举个例子,

假设某个业务场景会收到5个消息,顺序假设为1、2、3、4、5

最极端的情况,假设先收到5,存表,暂不处理

再收到4,存表,暂不处理

3,存表,暂不处理

2,存表,暂不处理

1,存表,立即处理,更新状态“已处理”

定时任务第一次扫描,2会被处理,更新状态“已处理”

第二次扫描,3会被处理,更新状态“已处理”

第三次扫描,4会被处理,更新状态“已处理”

第四次扫描,5会被处理,更新状态“已处理”

至此,所有消息都按顺序被处理完了

方案三:根据消息的发出时间识别消息的顺序

发送消息的时候可以将消息的创建时间写进消息体中,这样消费者收到消息后可以根据创建时间来排序,至少同一种业务下的消息的顺序是可以保证的,事实上通常我们也只关心某个业务下的消息的顺序。如果消息体中本身带了消息的发送时间,就可以用创建时间来自己排序。(业务类型 + 消息创建时间)

方案四:“递归”+观察者

如果每条消息(或者报文)中带有原始消息ID的话,就可以根据当前消息体中的原始消息ID找到之前的那条消息,一直向上找,直到找到那条不带原始消息ID的记录,则开始依次处理。

这种方式适用于消息中带有某个业务中的上一条消息ID的情况,这也是一种传递消息内在顺序的方式。

假设,有一个业务,正常完成这个业务会收到3条消息,假设分别是“消息1”、“消息2”、“消息3”

收到“消息1”时,由于它是该业务的第一条消息,所以它不带原始消息ID(即,上一条消息ID),则先村表,然后立即处理

收到“消息2”时,根据原始消息ID找到上一条消息(假设是“消息1”),则首先判断消息1是否已被处理,如有没有被处理,则先处理消息1,再处理消息2

收到“消息3”时,根据原先消息ID,先找消息2,如果消息2已被处理,则直接处理消息3,如果消息2没被处理,则找消息1,消息1处理完后,再处理消息2,最后处理消息3

以上是示意图,理论上是这样处理,但实际上不会真的采用递归的方法取调用,因为还是没有解决先收到2,后收到1的情况。

要处理这种情况,需要在收到1的时候通知2,于是我们可以引入观察者模式,采用注册监听回调的方法。

这样的话,无论消息顺序是123、132、213、231、312、321都能处理

总结一下,每条消息的处理逻辑是这样的:

1、如果带原始消息ID,则查询原始消息

2、如果原始消息不存在,则逻辑终止(return)

3、如果原始消息存在,但未处理,则逻辑终止(return)

4、如果原始消息存在,并且已处理,则继续处理本消息的逻辑

5、通知观察本消息的监听者

总之一句话,收到一条消息,当且仅当消息不带原始消息ID,或原始消息存在且已处理完毕的情况下,才会执行本消息的处理逻辑,本消息处理完毕后通知所有观察者

posted @ 2023-02-22 18:18  废物大师兄  阅读(492)  评论(0编辑  收藏  举报