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

应用场景

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、验证异常论断的方式是、第一个大胆猜想、第二个必须保证有日志可追踪查询论证。

posted @ 2024-06-30 12:30  [傾盡伊人]  阅读(75)  评论(0编辑  收藏  举报