Bootstrap

celery 线上问题

celery 线上问题

环境

项目中使用celery 去做异步化处理。针对不同的消息队列都会启动8个worker去消费。启动入口是supervisor,拉起django 的脚本。再由脚本去拉起所有的消费进程。

问题

线上celery 容器不停的挂死。通过监控可以看到内存过一段时间就会到达内存配置值。这时候项目跑不动。

分析

  • 既然是内存不足,首先查看了每个进程的内存使用情况。
htop

M 使用内存排序的方式去查看,这时候查看到的单个的进程使用的内存都在300以内并不是特别多。但是可以看到进程数量特别的多。同时发现了进程数量特别多,不符合我们通过配置拉起的数量。

  • 通过
ps -aux |grep celery| wc -l

以及监控可以看到进程的数量非常高。

  • 再次回到cat 监控上查看,发现对应的进程在某个时间点增长了一倍左右。怀疑这个增长导致了内存不足。通过查看日志发现在启动单个celery任务的时候会有个别进程异常退出的情况。而启动的叫脚本是使用了进程启动了所有的celery 任务,并且作为主进程去监控这些子进程。当其中一个子进程出现问题,主进程就会退出。然后supervisor进程又会把这个子进程拉起一次。这个操作就相当于 * 2.所以导致了进程数量暴涨,32G内存很快就会使用完。这时候启动的进程再去申请内存就会报错,无法申请内存。

解决

  1. 首先定位到报错是rabbitmq 配置有问题,提单解决。
  2. 第二步就是进行优化。优化监控处理方式。

优化

使用 python subprocess Popen.通过调用 subprocess.Popen 类。创建并且返回子进程。

先介绍一下相应属性

  1. pid
p.pid
  1. returncode
p.returncode

返回子进程的状态, 对应关系

  • None – 子进程尚未结束
  • == 0 – 正常退出
  • '> 0 – 异常退出,returncode 对应错误码
  • < 0 – 子进程被信号杀死。
  1. 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 都要报错。

那么怎么防止同一任务的子进程存在多份呢?两种做法:

  1. 在捕获False 的时候,主进程遍历所有子进程,如果存在则kill。
  2. 在主进程启动的时候根据相应规则查找,然后去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()
;