RocketMQ 一行代码造成大量消息发送失败
yuyutoo 2024-10-22 18:37 1 浏览 0 评论
作者 | 丁威
来源 | 中间件兴趣圈
问题现象
首先接到项目反馈使用 RocketMQ 会出现如下错误:
错误信息关键点:MQBrokerException:CODE:2DESC:[TIMEOUT_CLEAN_QUEUE]broker busy,start flow control for a while,period in queue:205ms,size of queue:880。
由于项目组并没有对消息发送失败做任何补偿,导致丢失消息发送失败,故需要对这个问题进行深层次的探讨,并加以解决。
问题分析
首先我们根据关键字:TIMEOUT_CLEAN_QUEUE 去 RocketMQ 中查询,去探究在什么时候会抛出如上错误。根据全文搜索如下图所示:
该方法是在BrokerFastFailure中定义的,通过名称即可以看成其设计目的:Broker端快速失败机制。
Broker端快速失败其原理图如下:
消息发送者向 Broker 发送消息写入请求,Broker 端在接收到请求后会首先放入一个队列中(SendThreadPoolQueue),默认容量为 10000。
Broker会专门使用一个线程池(SendMessageExecutor)去从队列中获取任务并执行消息写入请求,为了保证消息的顺序处理,该线程池默认线程个数为1。
如果Broker端受到垃圾回收等等因素造成单条写入数据发生抖动,单个 Broker 端积压的请求太多从而得不到及时处理,会极大的造成客户端消息发送的时间延长。
设想一下,如果由于Broker压力增大,写入一条消息需要500ms甚至超过1s,并且队列中积压了5000条消息,消息发送端的默认超时时间为3s,如果按照这样的速度,这些请求在轮到 Broker 执行写入请求时,客户端已经将这个请求超时了,这样不仅会造成大量的无效处理,还会导致客户端发送超时。
故 RocketMQ 为了解决该问题,引入 Broker 端快速失败机制,即开启一个定时调度线程,每隔10毫秒去检查队列中的第一个排队节点,如果该节点的排队时间已经超过了 200ms,就会取消该队列中所有已超过 200ms 的请求,立即向客户端返回失败,这样客户端能尽快进行重试,因为 Broker都是集群部署,下次重试可以发送到其他 Broker 上,这样能最大程度保证消息发送在默认 3s 的时间内经过重试机制,能有效避免某一台 Broker 由于瞬时压力大而造成的消息发送不可用,从而实现消息发送的高可用。
从 Broker 端快速失败机制引入的初衷来看,快速失败后会发起重试,除非同一时刻集群内所有的 Broker 都繁忙,不然消息会发送成功,用户是不会感知这个错误的,那为什么用户感知了呢?难道 TIMEOUT_ CLEAN _ QUEUE 错误,Broker 不重试?
为了解开这个谜团,接下来会采用源码分析的手段去探究真相。接下来将以消息同步发送为例揭示其消息发送处理流程中的核心关键点。
MQClient 消息发送端首先会利用网络通道将请求发送到 Broker,然后接收到请求结果后并调用 ProcessSendResponse 方法对响应结果进行解析,如下图所示:
在这里返回的Code 为 RemotingSysResponseCode .SYSTEM_BUSY。
我们从proccessSendResponse方法中可以得知如果Code为SYSTEM_BUSY,该方法会抛出MQBrokerException,响应Code为 SYSTEM_BUSY,其错误描述为开头部分的错误信息。
那我们沿着该方法的调用链路,可以找到其直接调用方:DefaultMQProducerImpl 的 SendKernelImpl,我们重点考虑如果底层方法抛出 MQBrokerException 该方法会如何处理。
其关键代码如下图所示:
可以看出在SendKernelImpl方法中首先会捕捉异常,先执行注册的钩子函数,即就算执行失败,对应的消息发送后置钩子函数也会执行,然后再原封不动的将该异常向上抛出。
SendKernelImpl方法被DefaultMQProducerImpl 的 SendDefaultImpl 方法调用,下面是其核心实现截图:
从这里可以看出 RocketMQ 消息发送高可用设计一个非常关键的点,重试机制,其实现是在 For 循环中使用 Try Catch 将SendKernelImpl 方法包裹,就可以保证该方法抛出异常后能继续重试。从上文可知,如果 SYSTEM_BUSY 会抛出 MQBrokerException,但发现只有上述几个错误码才会重试,因为如果不是上述错误码,会继续向外抛出异常,此时For循环会被中断,即不会重试。
这里非常令人意外的是连SYSTEM_ERROR都会重试,却没有包含 SYSTEM_BUSY,显然违背了快速失败的设计初衷,故笔者断定,这是RocketMQ 的一个BUG,将SYSTEM_BUSY 遗漏了,后续会提一个PR,增加一行代码,将 SYSTEM_BUSY 加上即可。
问题分析到这里,该问题应该就非常明了。
解决方案
如果大家在网上搜索 TIMEOUT_CLEAN_QUEUE 的解决方法,大家不约而同提出的解决方案是增加WaitTimeMillsInSendQueue的值,该值默认为200ms,例如将其设置为1000s等等,以前我是反对的,因为我的认知里 Broker 会重试,但现在发现 Broker不会重试,所以我现在认为该 BUG未解决的情况下适当提高该值能有效的缓解。
但这是并不是好的解决方案,我会在近期向官方提交一个PR,将这个问题修复,建议大家在公司尽量对自己使用的版本进行修改,重新打一个包即可,因为这已经违背了 Broker 端快速失败的设计初衷。
但在消息发送的业务方,尽量自己实现消息的重试机制,即不依赖RocketMQ 本身提供的重试机制,因为受制于网络等因素,消息发送不可能百分之百成功,建议大家在消息发送时捕获一下异常,如果发送失败,可以将消息存入数据库,再结合定时任务对消息进行重试,尽最大程度保证消息不丢失。
相关推荐
- mysql数据库如何快速获得库中无主键的表
-
概述总结一下MySQL数据库查看无主键表的一些sql,一起来看看吧~1、查看表主键信息--查看表主键信息SELECTt.TABLE_NAME,t.CONSTRAINT_TYPE,c.C...
- 一文读懂MySQL的架构设计
-
MySQL是一种流行的开源关系型数据库管理系统,它由四个主要组件构成:协议接入层...
- MySQL中的存储过程和函数
-
原文地址:https://dwz.cn/6Ysx1KXs作者:best.lei存储过程和函数简单的说,存储过程就是一条或者多条SQL语句的集合。可以视为批文件,但是其作用不仅仅局限于批处理。本文主要介...
- 创建数据表:MySQL 中的 CREATE 命令深入探讨
-
数据库是企业日常运营和业务发展的不可缺少的基石。MySQL是一款优秀的关系型数据库管理系统,它支持数据的插入、修改、查询和删除操作。在数据库中,表是一个关系数据库中用于保存数据的容器,它由表定义、表...
- SQL优化——IN和EXISTS谁的效率更高
-
IN和EXISTS被频繁使用在SQL中,虽然作用是一样的,但是在使用效率谁更高这点上众说纷纭。下面我们就通过一组测试来看,在不同场景下,使用哪个效率更高。...
- 在MySQL中创建新的数据库,可以使用命令,也可以通过MySQL工作台
-
摘要:在本教程中,你将学习如何使用MySQLCREATEDATABASE语句在MySQL数据库服务器上创建新数据库。MySQLCREATEDATABASE语句简介...
- SQL查找是否"存在",别再用count了
-
根据某一条件从数据库表中查询『有』与『没有』,只有两种状态,那为什么在写SQL的时候,还要SELECTCOUNT(*)呢?无论是刚入道的程序员新星,还是精湛沙场多年的程序员老白,都是一如既往...
- 解决Mysql数据库提示innodb表不存在的问题
-
发现mysql的error.log里面有报错:>InnoDB:Error:Table"mysql"."innodb_table_stats"notfo...
- Mysql实战总结&面试20问
-
1、MySQL索引使用注意事项1.1、索引哪些情况会失效查询条件包含or,可能导致索引失效如果字段类型是字符串,where时一定用引号括起来,否则索引失效...
- MySQL创建数据表
-
数据库有了后,就可以在库里面建各种数据表了。创建数据表的过程是规定数据列的属性的过程,同时也是实施数据完整性(包括实体完整性、引用完整性和域完整性)约束的过程。后面也是通过SQL语句和Navicat...
- MySQL数据库之死锁与解决方案
-
一、表的死锁产生原因:...
- MySQL创建数据库
-
我的重点还是放在数据表的操作,但第一篇还是先介绍一下数据表的容器数据库的一些操作。主要涉及数据库的创建、修改、删除和查看,下面演示一下用SQL语句创建和用图形工具创建。后面主要使用的工具是Navica...
- MySQL中创建触发器需要执行哪些操作?
-
什么是触发器触发器,就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上的SQL片段,但是触发器无需调用,当对数据库表中的数据执行DML操作时自动触发这个SQL片段...
- 《MySQL 入门教程》第 17 篇 MySQL 变量
-
原文地址:https://blog.csdn.net/horses/article/details/107736801原文作者:不剪发的Tony老师来源平台:CSDN变量是一个拥有名字的对象,可以用于...
- 关于如何在MySQL中创建表,看这篇文章就差不多了
-
数据库技术是现代科技领域中至关重要的一部分,而MySQL作为最流行的关系型数据库管理系统之一,在数据存储和管理方面扮演着重要角色。本文将深入探讨MySQL中CREATETABLE语句的应用,以及如何...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)