MQ消费消息顺序的思考
1、背景
- 为什么会乱序:在三高的情况下,很多系统都是集群模式。有可以能消息A先发,消息B先被处理;对于一些没有强制性要求的没有问题,但是对于状态流转的就有大问题,所以需要解决这种消费的顺序问题。
思考:建议不要在MQ当中使用消息的投递顺序来保证消息的顺序一致性,因为消息中间件是公用的,保证一致性需要确认,我们只需要保证消息投递的准确性,确定投递完成即可,由业务来做消息的顺序处理,对于kafka如果要保证消息的一致性,必须每一个topic只有一个partition,那么就是端到端的消费,比较慢而且比较脆弱,容易堆积,如果使用多线程去消费一个topic,那么必须在多线程当中去处理消息的顺序,这需要保证多线程的顺序性,这种也不好。
2、栗子
-
只消费最终状态
账单系统位于各个系统的最下游,监听各个订单的消息来创建账单合修改账单的状态,对于账单内容来说,我只关注这条订单的最终状态,给用户的展示也是最终状态,就没有状态机之前的约束,但是可能存在消息的乱序。
那怎么保障呢,个人思路:
public @interface Column { /** 数据库字段名称 */ String name(); /** 是否支持版本控制 */ boolean supportVersion() default true; /** 用于版本控制时,数据标识 */ int versionOrder() default -1; /** 版本控制的时间戳,消息GmtOccur值*/ boolean timestamp() default false; }
column注解用于DAO模型的字段:
/** * This property corresponds to db column <tt>rebate_fee_type</tt>. */ @Column(name = "rebate_fee_type", type = DataType.STRING, versionOrder = 16) private String rebateFeeType; /** * This property corresponds to db column <tt>status</tt>. */ @Column(name = "status", type = DataType.STRING, versionOrder = 17) private String status; /** * This property corresponds to db column <tt>column_version</tt>. */ @Column(name = "column_version", type = DataType.STRING, supportVersion = false) private String columnVersion;
columnVersion数据数据库的一个字段。
步骤:
如果收到第一条消息,首先根据消息得到DO,那么column_version 的值:
{ "baseTime": 1594952377305, -----① "versions": [{ "fields": [65, 2, 66, 5, 72, 9, 16, 18, 19, 21, 23, 26, 28, 30, 34, 36, 37, 48, 49, 53, 54], ---② "version": 0 ---③ }] }
说明:
① :baseTime 是 insert 操作时,对应MQ的发送的时间(每条MQ 会有一个MQ 产生时间uniformEvent.GmtOccur),数据库中对应字段 GmtTimestamp
②:对应DO模型字段上的注解@Column的versionOrder,如果字段有值都会记录在fields里面
③:第一次新增版本为零
如果收到第二条消息,根据消息得到 DO记做 MA(消息中也会有GmtOccur值)
第一步:将DO数据中的GmtTimestamp值与第一条数据的baseTime值比较,得到差值 记做A
第二步:循环versions,比较version(记做B)和A,如果A小,则说明是过期数据不处理,如果大,上个版本的数据记做MB ,则
{ "baseTime": 1594952377305, "versions": [{ "fields": [2, 1, 33, 42, 43, 31], ---MA "version": 47 A }, { "fields": [65, ①, 66, 5, 72, 9, 16, 18, 19, 21, 23, 26, 28, 30, 34, 36, 37, 48, 49, 53, 54], ---MB "version": 0 B }] } 1 被删除
循环比较MA的fields中的versionOrder是不是在MB中存在,如果存在则删除B中对应的字段versionOrder,保留MA中的,表明此字段在MA中有更新,version 为 差值A
如果收到第三条消息 根据消息得到DO 记做MC (消息中也会有GmtOccur值)
第一步:同上
第二步:同上
如果此次比较得到的version值在MA的version和MB 的version之间,记做MAB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "baseTime" : 1594952377305 , "versions" : [{ "fields" : [ 2 , 1 , 33 , 42 , 43 , 31 ], ---MA "version" : 47 }, { "fields" : [②, 79 , 80 ], ---MAB "version" : 40 }, { "fields" : [ 65 ,①, 66 , 5 , 72 , 9 , 16 , 18 , 19 , 21 , 23 , 26 , 28 , 30 , 34 , 36 , 37 , 48 , 49 , 53 , 54 ], ---MB "version" : 0 }] } 1 在第二条消息的时候已经被删除 2 在MA中存在了,但是MAB 的版本小,则此值应该取MA中的值,MAB中被删除 |
循环比较MAB的fields中的versionOrder是不是在MB中存在,如果存在则删除B中对应的字段versionOrder,保留MA中的,表明此字段在MA中有更新,version 为 差值A
此种情况还需要与MA比较,因为MAB的版本比MA的小,MAB中的部分字段虽然相对MB来说是新的需要更新,但是相对MA来说是过期数据,但是MAB相较于MA中不存在的数据,在MA看来是最新的。
-
状态之间有约束的
例如一个订单系统,一个订单的流程:下单->待发货->已发货->待收货->已收货->待评价。 这几个状态机都是有约束关系的,必须完成上一下状态才能进行流转。
那么这种情况要怎么办呢,毕竟这种流程有一定的操作顺序延迟,出现顺序不一致的情况概率比较少,那么我们可以采取等一等的方法。
比如:订单状态已经在【待发货】的状态,这【已收货】的状态先到,我们可以校验状态,如果不对重新入队列,直到状态机正确才进行处理。
思考:这种方式如果出现大量的状态不一致,可能会出现消息堆积的情况,这个时候我们需要一个蓄洪机制,对于消息处理失败的,我们先不入队列,先放入一个数据库存储起来,等高峰期过了在泄洪。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix