celery rabbitmq内存异常排查过程及内存组成解析
现象
celery 在用rabbitmq集群当broker的时候,经常会出现rabbitmq某个节点内存爆满的问题。要知道,一旦rabbitmq节点内存爆满,触发内存报警,rabbitmq会自动堵塞所有连接,直到内存降下去。
按理说这样的策略也说的过去,毕竟不能内存满了还接收消息,但我的rabbitmq满了之后很难降下去(我的内存报警阈值是3G, 3G…),导致celery明明连接上了,就因为被堵塞,导致celery以为自己连接上了broker,其实并没有,所以一直报IOError,连接异常关闭。
问题追踪
修改celery?
一开始我以为是celery设置的问题,毕竟从rabbitmq管理界面来看,绝大部分都是celeryev开头的,celeryev是什么东西呢?
官方解释是这样的:celeryev is a simple curses monitor displaying task and worker history. You can inspect the result and traceback of tasks, and it also supports some management commands like rate limiting and shutting down workers.
意思是celeryev可以监控任务状态和执行结果,还支持其他的一些管理操作,类似flower.
celeryev开头的队列是celery自己创建的,为了发送事件方便监控捕捉。OK, 创建队列没有问题,毕竟我们也需要监控,但问题是这些队列怎么不会自行删除呢,应该有存活时间的设置才对。于是在celery的配置文件中加上EVENT_QUEUE_TTL=10(后来查询才知道,celery默认就是5,我竟然还延长了),规定消息存活时间为10s。然而并没有什么卵用,内存还是涨的飞快,重启只能暂时缓解状况。
刨根问底
没办法了,一步一步来,先看看这些内存到底是rabbitmq哪些操作占用了,从哪里来,又到哪里去?
rabbitmqctl status分析内存占用情况,得到下面输出:
{memory,
[{total,1974176432},
{connection_readers,61888},
{connection_writers,48528},
{connection_channels,379904},
{connection_other,151952},
{queue_procs,5243832},
{queue_slave_procs,1872218632},
{plugins,7103632},
{other_proc,25382552},
{mnesia,7720984},
{metrics,289360},
{mgmt_db,10010000},
{msg_index,516944},
{other_ets,3452744},
{binary,2671640},
{code,25009466},
{atom,1033401},
{other_system,13167501}]},
这样可以看出rabbitmq各个进程或操作占用的内存及分配情况,一个一个来看一下:
connection_readers: 负责连接解析器和大多数连接状态的进程。它们的大部分内存都属于TCP缓冲区。节点拥有的客户端连接越多,此类别将使用的内存越多。
connection_writers:负责序列化传出协议帧和写入客户端连接套接字的进程。节点拥有的客户端连接越多,此类别将使用的内存越多。
connection_channels:连接的通道占用的内存。
queue_procs:主队列,索引和消息保存在内存中。排队的消息数量越多,通常会将此内容归因于此部分。但是,这在很大程度上取决于队列属性以及消息是否作为瞬态发布。
queue_slave_procs:队列镜像,索引和消息保存在内存中。减少镜像(副本)的数量或不使用固有的瞬态数据镜像队列可以减少镜像使用的RAM量。排队的消息数量越多,通常会将此内容归因于此部分。但是,这在很大程度上取决于队列属性以及消息是否作为瞬态发布。(很明显,就是它了)
metrics:节点本地指标。连接,通道,队列越多,节点主机,收集和保留的统计数据就越多。
stats_db:聚合和预先计算的度量标准,节点间HTTP API请求缓存以及与统计数据库相关的所有其他内容。
binaries:运行时二进制堆。本节的大部分内容通常是消息体和属性(元数据)。
plugins:插件或插件产生的数据。
allocated_unused:分配但未使用。
reserved_unallocated:由内核保留但不是运行时保留。
mnesia:虚拟主机,用户,权限,队列元数据和状态,交换,绑定,运行时参数等。
other_ets: 一些插件可以使用ETS表来存储它们的状态.
code: 字节码和模块元数据。这应该只消耗空白/空节点上的两位数内存。
柳暗花明
经过上面一分析,很明显就是镜像队列的数据占用了大量的内存。然后进去05节点管理界面看了一眼(节点有三台,04, 05,06,04是硬盘节点,05,06是内存节点,内存经常报警的就是05节点),发现确实是大量的镜像队列数据占了绝大数内存,而且神奇的是,这些个镜像队列的主队列都是06节点上的,也就意味这06节点上面的队列太多导致了05节点做镜像时候用了太多内存(不太明白为什么镜像队列竟然比原队列占用的空间还多,不太合理,后续研究),既然知道了是因为这戏队列造成的,而且这些队列没有数据,删掉了不就行了?
又起波澜
装上rabbitmqadmin,运行rabbitmqadmin -f tsv -q list queues name | while read queue; do rabbitmqadmin -q delete queue name=${queue}; done
然而:
*** Not found: /api/queues/%2F/celeryev.002d702c-e30b-4647-8155-5a71dc2a19f9
What? 手动执行删除命令,还是not found。行吧,我在管理界面删,这下总不能找不到了吧。
然而在管理界面点击队列下面的delete按钮,管理界面直接挂掉了,一直没响应。事情开始变得有些不太对了,队列竟然删不掉?
尾声
既然删不掉,我就来点狠的,统统梭哈(清除)!由于主要事情发生在05和06节点上,那么两台都执行:
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmq start_app
数据都没了,我看你们还怎么复制队列数据。
然而一顿操作之后,管理界面看到的队列数并没有减少,难道必须要对04节点下手?
04节点终于reset了,然而reset之后起不来了,排查了错误,kill掉了错误的进程,04节点终于工作了,之后05,06分别添加进集群:
rabbitmqctl join_cluster --ram rabbit@04
添加用户,添加用户的角色,添加用户的权限(默认guest用户只能在本机访问,所以需要添加额外的用户)。
之后开启必要的插件,增加必要的镜像队列策略。
到这里,rabbitmq那些垃圾队列都不存在了,内存也没有飙到过g级别了,一直是100m左右,工作状况很良好,Good!
总结
当遇到这种问题的时候,正确的做法还是要循序渐进,不要想当然认为是celery的问题(这里celery有点冤…),分析这些内存分配情况如何?是哪些进程占用了?又是谁创建了这些队列?
我们可以:
- 规定这些队列的生存时间,防止一直占用空间,这方面主要从源头celery入手。
- 如果是垃圾队列,没有消费者使用,那么删掉也是可以的,暂时解了燃眉之急。
引以为鉴。

浙公网安备 33010602011771号