celery 线上问题
环境
项目中使用celery 去做异步化处理。针对不同的消息队列都会启动8个worker去消费。启动入口是supervisor,拉起django 的脚本。再由脚本去拉起所有的消费进程。
问题
线上celery 容器不停的挂死。通过监控可以看到内存过一段时间就会到达内存配置值。这时候项目跑不动。
分析
- 既然是内存不足,首先查看了每个进程的内存使用情况。
htop
M 使用内存排序的方式去查看,这时候查看到的单个的进程使用的内存都在300以内并不是特别多。但是可以看到进程数量特别的多。同时发现了进程数量特别多,不符合我们通过配置拉起的数量。
- 通过
ps -aux |grep celery| wc -l
以及监控可以看到进程的数量非常高。
- 再次回到cat 监控上查看,发现对应的进程在某个时间点增长了一倍左右。怀疑这个增长导致了内存不足。通过查看日志发现在启动单个celery任务的时候会有个别进程异常退出的情况。而启动的叫脚本是使用了进程启动了所有的celery 任务,并且作为主进程去监控这些子进程。当其中一个子进程出现问题,主进程就会退出。然后supervisor进程又会把这个子进程拉起一次。这个操作就相当于 * 2.所以导致了进程数量暴涨,32G内存很快就会使用完。这时候启动的进程再去申请内存就会报错,无法申请内存。
解决
- 首先定位到报错是rabbitmq 配置有问题,提单解决。
- 第二步就是进行优化。优化监控处理方式。
优化
使用 python subprocess Popen.通过调用 subprocess.Popen 类。创建并且返回子进程。
先介绍一下相应属性
- pid
p.pid
- returncode
p.returncode
返回子进程的状态, 对应关系
- None – 子进程尚未结束
- == 0 – 正常退出
- '> 0 – 异常退出,returncode 对应错误码
- < 0 – 子进程被信号杀死。
- p.poll() 检查子进程,返回returncode 属性。
到这里,就可以实现子进程监控父进程了。
def check_subprocess(typ, process):
if process is None:
return False
ret = process.poll()
if ret is not None:
return False
return True
因为正常的逻辑子进程不应该退出,所以不为None 都要报错。
那么怎么防止同一任务的子进程存在多份呢?两种做法:
- 在捕获False 的时候,主进程遍历所有子进程,如果存在则kill。
- 在主进程启动的时候根据相应规则查找,然后去kill掉这些进程。
实力代码 实现第二种方法:
def kill_origin_worker():
command = "ps aux | grep 'celery worker' | grep -v grep | awk '{print $2}' | xargs kill"
# print command
process = subprocess.Popen(
command,
shell=True,
close_fds=True)
if process.poll() is not None:
exit(-1)
process.wait()