记一次RocketMQ消费非顺序消息引起的线上事故

rocketmq · 浏览次数 : 4

小编点评

事故异常体现: 1. 工单状态从A->C->B,与理论状态只能从A->B->C不符。 2. 写入到搜索引擎的工单数据被本该更早写入引擎的数据覆盖了。 背景解释: 1. 工单创建完成后,会经历多个状态变更,如新建、状态新建、状态已审核、状态已分配、状态已更改等。 2. 所有工单的操作都是异步的,没有固定顺序。 3. 写到ES(搜索引擎)的MQ消息可以理解为并发出现,可能存在先后顺序的问题。 事故异常分析: 1. 由于MQ消息消费无序,可能导致先收到的消息A被处理,而后收到的消息B被覆盖。这可能是因为MQ没有加分布式锁,导致消息B先写入ES。 2. 解决方式包括:使用分布式锁确保同一时间只有一个处理器处理消息,或者在一定时间内收集写ES的消息并排序过滤,只处理最新版本的工单消息写入ES。 感想: 1. 有序的MQ消息资源可能会比较贵,因此需要在代码层保证数据稳定性。 2. 验证异常论断的方式包括:首先大胆猜想,然后必须保证有日志可追踪查询论证。

正文

应用场景

C端用户提交工单、工单创建完成之后、会发布一条工单创建完成的消息事件(异步消息)、MQ消费者收到消息之后、会通知各处理器处理该消息、各处理器处理完后都会发布一条将该工单写入搜索引擎的消息、最终该工单出现在搜索引擎、被工单处理人检索和处理。

事故异常体现

1、异常体现

从工单的流转记录发现、工单的状态从A->C->B、理论上 工单的状态只能从A->B->C。此处能得出两个结论、1、各处理器处理完工单之后、状态有误;2、写入到搜索引擎的工单数据被本该更早写入引擎的数据覆盖了。

从监控数据发现、结论1排除。

2、背景解释

一个工单创建完之后、会经历

  • 工单创建完城-->状态新建、将新工单信息更新至ES(搜索引擎)
  • 工单内容审核-->状态已审核、将新工单信息更新至ES
  • 工单分配给指定工作人员-->状态已分配、将最新工单信息更新至ES
  • 其他操作-->状态改变-->将最新工单信息更新至ES

 说明:
所有工单的操作都是异步的、没有固定顺序。
保证点:
写到ES之前从数据库所获取的工单信息都是最新的工单信息、无误。
案例中异常情况:
工单实际已经分配了工作人员(已分配状态)、可以查询到被分配的人、但是工单的状态显示是新建状态。

 3、事故异常分析

1、创单的顺序是优先于派单的
2、创单之后抛出一个写ES的MQ消息--消息A
3、派单之后也会抛出一个写ES的MQ消息--消息B
4、如果MQ是有顺序的、按照顺序消费消息、MQ消费者消费第一个消息(消息A)肯定是比第二份消息(消息B)要快的、正常情况、工单是绝对没有问题。
(非正常情况、可能是ES自身写消息到集群、有快慢之分、第二个消息会先写完、此种情况基本忽略不计)

5、此处异常是、MQ消息消费无序

  正常拿到消息A、查询数据库的状态正确、可以推送ES消息、再接着拿到消息B、查询数据库的状态正确、可以推送ES消息、因为推送ES没有加分布式锁、导致消息B那个时刻的工单数据被先写入ES、消息A那个时刻的工单数据后写入ES、导致ES的数据被覆盖、最终ES的最新版本的工单状态和数据库的工单状态不一致。

4、解决方式

背景说明(续):写ES的MQ消息可以理解为并发出现、在工单创建的那一刻、所有改动工单状态的消息基本会在ms级别时间内到达。
前提:在写ES的MQ消息中、携带更新完工单之后该工单的时间戳、作为工单的版本号。

方式一:

  暴力方式、直接在写ES的接口新增分布式锁、通过对比消息的版本号和工单数据库中的版本号、即可判定消息要舍弃还是写入ES(不采取、会严重降低写ES的效率)
方式二:
  仅对工单使用分布式锁、同时、在一定时间内(秒级)收集写ES的消息、并且对消息进行排序过滤、仅处理最新版本的工单消息写入ES。

5、感想

1、有序的MQ消息资源会比较贵,还是要代码层保证数据稳定性。
2、验证异常论断的方式是、第一个大胆猜想、第二个必须保证有日志可追踪查询论证。

与记一次RocketMQ消费非顺序消息引起的线上事故相似的内容: