性能文章>这次操作差点把自己送走>

这次操作差点把自己送走原创

310099

这次操作差点把自己送走

起因

自从RabbitMQ的版本从3.9.8升级到3.11.15之后就一直没有消停过,这不监控又在呼唤了。集群中的一个节点磁盘的增速超过了阈值,查看服务器进七天的磁盘使用量一直在缓慢的增长,不是一个好的预兆,如果一直增长下去就会触发集群的磁盘空间阻塞,这是最致命的,一旦由于磁盘空间的阻塞会导致整个集群不可用,所有的连接都会处于Blocked或者Blocking状态。
通过如下步骤排查了告警的服务器:

  1. du -h /data检查服务器告警目录的目录使用量,发现某个队列的目录下产生了大量的段(segment)文件,并且存在一定规则的创建。
  2. 查看了RabbitMQ服务的日志文件,日志里重点关注的关键字acknowledgement和1800000记录,而且比较规则。
  3. 管理UI对比了队列间的差异,在段文件目录下的config配置文件中获取到队列的信息,对比发现在该队列上存在一条unacked状态的消息。
  4. 官方文档给出的描述段文件的处理说明(是否检索到的文章)

经过上述的操作,怀疑存在1条异常消息并且不能被消费者所处理,导致一段时间后会重新入队被消费,正好也能与段文件和日志的规则性能对上。于是准备了测试环境进行验证,复现了生产环境的现象,同时通过单队列维度设置了delivery-limit的限制,将异常消息路由到死信队列。确定问题出在了如下两个地方:

  1. 通过管理UI进行消息推送没有赋值消息ID属性,并且消息的格式并不符合JSON规范,导致消费者反序列化失败。
  2. 消费者组件的封装逻辑BUG,在消费者抛异常后有个重试机制,是基于消息ID实现,在没有消息ID的前提下会再次抛异常,并且不会给MQ集群回复任何ack或者nack的命令

接下来就是要将这些操作同步在生产环境执行一遍,消除生产环境的隐患。

经过

生产环境的操作,在一个非投产日的夜晚,吃过了晚饭后准备了线上的操作。改过了测试环境执行的脚本,然后在生产上执行了操作。就在执行完那一刹那,死信队里产生了告警提示,队列积压消息数量超过了阈值500。瞬间整个人都不好了,脑袋懵了于是傻傻的执行了一个操作将队列在交换器上解除了(unbind),队列积压的消息不在增长了,但解绑队列会导致消息的丢失,交换器在没有可以路由的队列时就对掉了这条消息。正确的操作是删除掉新增加的策略(反应过来就执行了),多么痛的的教训,操作的再晚些真的就把自己给送走了。

想多了也没用于是想着排查下堆业务的影响是啥,看还有没有可以弥补的方法,拉取了死信队列的消息,基本都属于同一个队列的。找到相关的负责同时,拉了会议讨论如何恢复这个业务,以及确定业务的影响范围,经过了解确定为数据的清洗逻辑,在RabbitMQ的上游逻辑中还有一个可以进行回放的程序,于是就又看到了希望。将负责系统同事拉回来进行业务的处理。本以为回放了就万事大吉了,但是在数据对比验证环节出现了对不平的情况,生产者发送的条数和消费者接收的条数差了近2/3的数据。

话说另外的排查就是消息为什么会进入死信队列,这是不正常的。在管理UI拉取了消息查看可以肯定的是消息过期了导致的进入死信队列。但通过MQ服务端都排查了,没有策略配置,没有队列参数设置,于是准备测试环境复现,在客户端参数的尝试下发现expiration:0参数的设置,会导致在消费者速率低于生产速率的时候,会将消息过期丢弃,在加上进入死信队列的策略后,过期的消息进入到了死信队列。
image.png

结果

调整客户端的配置,系统重启,然后重置回放的点位,将所有的消息重新回放,数据比对一切正常。至此一个悬着的心才有所踏实。不管去留的结果如何,在一个误操作下发现了一个留存已经的BUG并得到了有效的解决,是功是过到现在都不太好说了。

反思

  1. 操作要按部就班,由指定的人员按照指定的操作顺序进行操作,回退有回退的操作
  2. 要多观察系统给出的信息,比如已经明确了过期了,为什么没有考虑客户端设置的参数问题
点赞收藏
分类:标签:
heroyeah_293541
请先登录,查看9条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

Redis stream 用做消息队列完美吗?

Redis stream 用做消息队列完美吗?

Netty源码解析:writeAndFlush

Netty源码解析:writeAndFlush

9
9