Celery+Redis爬虫任务并发调优后Redis连接及缓冲区问题排查
Celery爬虫集群调优后Redis问题排查与解决
环境与架构概述
- 运行环境:Python 3.6、Celery 4.2.1,自建Redis(同时承担消息代理、缓存、分布式锁职责)
- 核心任务类型:
- job_executor:单VM仅运行1个并发为1的worker,监听队列
crawl_job_{job_id}执行爬虫任务;单任务包含1-20000个URL,对应GCP托管实例组(MIG)1-100台VM,任务耗时15秒至120分钟 - crawl_job_initiator:部署在独立VM上,监听队列
crawl_job_initiator_queue,通过Terraform为单个任务ID创建MIG与VM,并向对应队列添加job_executor任务,单任务耗时约70秒
- job_executor:单VM仅运行1个并发为1的worker,监听队列
调优操作
为提升批量任务启动速度,将crawl_job_initiator的worker并发数从1调整为20,并新增job_id级非阻塞锁(锁获取失败时执行指数退避重试)
调优后出现的两类错误
错误1:Redis连接失败(RDB快照fork内存分配失败)
- 现象:crawl_job_initiator及其他任务日志报Redis连接失败;Redis日志提示后台保存RDB快照时,fork进程无法分配内存
- 临时解决/优化方案:
- 提升Redis内存配置
- 设置
vm.overcommit_memory=1(解除系统内存过度分配限制,允许fork子进程使用更多内存)
错误2:Redis客户端因输出缓冲区超限被关闭
- 现象:Redis日志显示MIG中的job_executor VM及crawl_job_initiator客户端因输出缓冲区超限被强制关闭;多次调大
client-output-buffer-limit(从pubsub 16mb 8mb 60调整至4000mb 2000mb 60)仍报错;测试添加10万任务至队列未触发该问题 - 排查方向:
- 锁的退避重试逻辑:指数退避重试可能导致短时间内大量Redis请求堆积,再加上worker并发20的配置,会让同一时间向Redis发送的订阅/请求量剧增,输出缓冲区来不及处理
- Celery的
-Ofair调度策略:该策略会让worker优先处理等待最久的任务,可能导致部分任务的Redis响应被积压,输出缓冲区持续占用无法释放 - Redis pubsub缓冲区特性:Celery用Redis pubsub传递任务通知,当worker消费速度跟不上消息生产速度时,缓冲区会持续膨胀;单纯调大缓冲区只是治标,需找到消费瓶颈
- 调试方法:
- 开启Redis慢查询日志,记录耗时超过阈值的命令,定位是否存在批量锁请求或阻塞操作
- 执行
INFO clients命令查看Redis客户端的缓冲区使用情况,重点关注client_output_buffer_pubsub相关指标,确认哪些客户端的缓冲区持续增长 - 监控Celery worker的任务处理状态,查看是否有任务长时间阻塞在锁获取阶段,导致Redis连接长时间持有且持续接收消息
- 解决方案:
- 优化锁的重试逻辑:设置重试间隔上限,避免短时间内大量重试请求;或在锁获取失败时直接将任务重新放回队列,而非持续重试
- 调整Celery调度策略:移除
-Ofair参数,改用默认的-O(或-Odefault)策略,让worker按顺序处理任务,减少任务积压导致的缓冲区占用 - 拆分锁的粒度:将job_id级锁拆分为更细粒度(如按任务批次),减少单锁的竞争次数,降低Redis请求频率
- 优化Redis pubsub配置:结合业务场景调整
client-output-buffer-limit的软限制与超时时间,比如将超时从60秒延长至300秒,给worker足够时间消费缓冲区消息;同时确保Redis有足够内存承载缓冲区数据 - 监控告警:配置Redis客户端缓冲区使用量的监控告警,当缓冲区接近阈值时及时排查任务堆积情况
附:Celery Worker Supervisord配置
job_executor配置
# job_executor supervisord配置 [crawl-job-executor] command=/home/ubuntu/Env/bin/celery worker -A crawler.taskapp --loglevel=info --concurrency=1 --max-tasks-per-child=1 --max-memory-per-child=350000 -Ofair -Q crawl_job_{job_id} -n crawl_job_{job_id} autostart=true autorestart=true startsecs=10 stopwaitsecs=10
crawl_job_initiator配置
# crawl_job_initiator supervisord配置 [crawl-job-initiator] command=/home/ubuntu/Env/bin/celery worker -A crawler.taskapp --loglevel=info --concurrency=20 --max-tasks-per-child=1 --max-memory-per-child=350000 -Ofair -Q crawl_job_initiator -n crawl_job_initiator@%%h autostart=true autorestart=true startsecs=10 stopwaitsecs=10
内容的提问来源于stack exchange,提问作者Prashant Sengar
相关产品推荐
相关产品推荐

