You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Django集成Celery爬虫任务中Worker内存泄漏问题:任务完成后内存持续增长未释放

Django集成Celery爬虫任务中Worker内存泄漏问题:任务完成后内存持续增长未释放

我之前在做Django+Celery驱动的爬虫任务时,也踩过这个内存只升不降的坑——任务跑完内存完全不回落,重启Worker就立刻恢复,折腾了好一阵才把问题捋清楚。结合你的配置和已经做的尝试,给你分享一些实用的排查方向和解决办法:

一、先从爬虫任务本身的内存管理入手

爬虫任务天生容易积累内存,很多时候不是Celery的锅,而是我们的代码没做好资源回收:

  • 及时释放临时资源
    requests发起请求时,尽量用with语句自动关闭响应,避免响应体长期占用内存:

    with requests.get(url, stream=True) as resp:
        # 分块处理响应内容
        for chunk in resp.iter_content(chunk_size=1024):
            pass
    

    用BeautifulSoup解析DOM后,手动删除对象并触发垃圾回收:

    soup = BeautifulSoup(resp.text, "html.parser")
    # 解析逻辑...
    del soup
    import gc
    gc.collect()
    
  • 避免全局变量缓存大数据
    不要在任务函数外定义全局的列表、字典存储爬取结果,每次任务都用局部变量——任务结束后,局部变量会被Python的GC自动回收,不会长期占用内存。

  • 数据库连接及时清理
    爬虫任务耗时可能较长,Django的数据库连接可能会长期占用且未释放,建议在任务的开头或结尾调用连接清理方法:

    from django.db import close_old_connections
    
    @app.task
    def crawl_task():
        close_old_connections()  # 任务开始前关闭失效的旧连接
        # 爬虫逻辑...
        close_old_connections()  # 任务结束后再次清理连接
    
  • 流式处理大内容
    如果爬取大文件、批量数据,不要一次性加载到内存,比如用requests.get(url, stream=True)分块读取,或者用分页解析的方式处理列表数据,避免内存瞬间暴涨。

二、强化Celery Worker的内存回收机制

你已经尝试了--max-tasks-per-child=200,这是非常有效的兜底手段,再补充几个可以配合的配置:

  • 调整--max-tasks-per-child的数值
    这个参数强制Worker进程处理N个任务后自动重启,彻底释放内存。你可以根据单个任务的内存涨幅调整数值——如果每个任务跑完内存涨100MB以上,就把值设小一点(比如50);如果涨幅不大,保持200也没问题。

  • 新增--max-memory-per-child限制
    这个参数可以让Worker进程在内存占用超过指定阈值(单位:MB)时自动重启,比按任务数重启更灵活。比如启动Worker时加上:

    celery -A your_project worker --loglevel=info --max-tasks-per-child=200 --max-memory-per-child=1024
    

    意思是:要么处理满200个任务,要么内存超过1024MB,满足任一条件就自动重启Worker。

  • 控制Worker并发数
    虽然你配置了worker_prefetch_multiplier=1(每次预取1个任务),但如果启动Worker时用了--concurrency=N(N>1),多个进程同时运行会导致内存叠加。如果服务器内存有限,建议把并发数设为1或者和CPU核心数匹配,避免内存过载。

三、排查依赖库与Celery本身的潜在泄漏

有时候第三方库的内存泄漏也会导致问题:

  • 检查爬虫依赖的内存行为
    比如requests的全局Session会保存所有Cookie和连接池,导致内存持续上涨,建议在每个任务内部创建局部Session:

    @app.task
    def crawl_task():
        session = requests.Session()
        try:
            resp = session.get(url)
            # 处理逻辑...
        finally:
            session.close()  # 手动关闭Session释放资源
    
  • 用工具定位内存泄漏点
    可以用Python自带的tracemalloc或者第三方库memory_profilerobjgraph来监控任务的内存变化:

    • tracemalloc记录任务前后的内存快照,定位内存占用最高的代码行:
      import tracemalloc
      
      @app.task
      def crawl_task():
          tracemalloc.start()
          # 爬虫逻辑...
          snapshot = tracemalloc.take_snapshot()
          top_stats = snapshot.statistics('lineno')
          print("[Top 10 memory usage]")
          for stat in top_stats[:10]:
              print(stat)
      
    • objgraph找出未被回收的大对象,定位引用链,看哪些对象被意外持有导致无法GC。

四、你的现有Celery配置的小补充

看了你的Celery配置,大部分都是合理的:

  • task_ignore_result=Trueresult_backend="rpc://"的搭配没问题,不会因为存储任务结果占用内存;
  • task_acks_late=Trueworker_prefetch_multiplier=1适合爬虫这种耗时任务,避免任务丢失;
  • task_time_limitsoft_time_limit能防止任务卡死,建议保持。

最后给个小建议:先跑几个测试任务,用top或者ps aux --sort=-rss盯着Worker进程的内存变化,看是每个任务都线性上涨,还是某个任务突然暴涨,定位清楚是普遍问题还是个别任务的问题,再针对性优化,效率会更高!

火山引擎 最新活动