记一次Scrapy进程卡死的Debug过程

本文分享了一次解决Python爬虫程序中死锁问题的经历,通过使用py-spy工具定位到网络请求未设置超时参数导致的问题,并最终修复。
Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

发现问题

日常巡查数据入库情况时,发现最新数据的入库时间停在了凌晨。立刻登录远程服务器,尝试定位问题。

  1. 定时任务是否正常工作,是否有报错信息

    crontab -l
    

    经检查发现,定时任务工作正常,也没有运行报错的记录。

  2. 查看系统进程,采集程序是否运行

    ps -ef | grep xxxappspider
    

    输出信息如下

    可以看到进程在凌晨 01:40 成功启动了,但是一直没有执行完成,推测是代码出现了死锁等问题?查看日志也没有记录到有用的信息。

  3. 检查代码,尝试复现该bug

    在服务器手动执行程序,均能正常运行。简单复查代码,也没有发现哪里会导致死锁。

解决问题

由于手头还有比较急的任务,只是给程序加上了更详细的日志记录,然后 kill 掉卡住的进程,让定时任务重新运行起来。

第二天问题再次出现,同样是凌晨的定时任务出现了卡死的情况。首先排除服务器原因,相同服务器其他任务均正常运行。其次排除存储原因,我们的采集结果是统一入到 Kafka 队列,经过一系列的操作后存储到数据库的。这个 Kafka 队列所有应用都在使用,如果出现问题不会只这一个任务。然后大致可以确定,是这个任务在凌晨运行时,会因为某些原因导致卡死。

好了,是时候祭出我们的大杀器: py-spy

这是一个 Python 的性能分析工具,我是在听《捕蛇者说》的时候了解到的这个库,现在正好拿来用用。

先简单看下怎么用:

[test@localhost ~]# py-spy --help
py-spy 0.1.11
A sampling profiler for Python programs

USAGE:
    py-spy [FLAGS] [OPTIONS] --pid <pid> [python_program]...

FLAGS:
        --dump           Dump the current stack traces to stdout
    -F, --function       Aggregate samples by function name instead of by line number
    -h, --help           Prints help information
        --nonblocking    Don't pause the python process when collecting samples. Setting this option will reduce the
                         perfomance impact of sampling, but may lead to inaccurate results
    -V, --version        Prints version information

OPTIONS:
    -d, --duration <duration>    The number of seconds to sample for when generating a flame graph [default: 2]
    -f, --flame <flamefile>      Generate a flame graph and write to a file
    -p, --pid <pid>              PID of a running python program to spy on
    -r, --rate <rate>            The number of samples to collect per second [default: 100]

ARGS:
    <python_program>...    commandline of a python program to run

只需要输入 Python 进程的 pid 就能直观的显示该进程中各项任务的耗时情况。更重要的是,它不需要重启代码就能运行,非常适合我们现在遇到的情况。

安装很简单:

pip install py-spy

使用很简单:

# 先找到这个卡住的Python进程的pid
ps -ef |grep python |grep ***
# 启动 py-spy 观察这进程
py-spy --pid 32179

输出信息如下:

可以看到,程序是卡在了建立网络连接的部分。hand_request是一个为某个App请求签名的函数,被单独放在了utils这个目录下。接下来就简单了,找到这个函数,在第43行,发现了一个 post 请求。嗯,其实不管是 post 还是 get 都不要紧,重要的是这个请求没有加 timeout 参数!!!

Requests 文档里写的很清楚了,如果没有超时参数,程序有可能永远失去响应。

超时

你可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

注意

timeout 仅对连接过程有效,与响应体的下载无关。 timeout 并不是整个下载响应的时间限制,而是如果服务器在 timeout 秒内没有应答,将会引发一个异常(更精确地说,是在timeout 秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do not time out.

至此,Debug完成。

总结

这么低级的 bug,确实是我自己写的。

当初写的时候忽视了这个问题,测试的时候没有发现问题也就过去了。第一次发现问题的时候,查问题并不仔细,只简单看了spiders目录下的几个爬虫代码,没有去检查utils目录下的工具类的代码,故而并没有找到具体问题。第二次通过 py-spy 的帮助,成功找到并解决了问题。

解决问题后,反思下原因:很可能是这个 App 会在凌晨进行维护,导致请求没有得到响应,同时没有设置超时函数,程序就会一直卡在哪里。

最后,推荐一下《捕蛇者说》,这是一个关于“编程、程序员、Python”的中文博客。没事听听大佬们唠嗑,真的很涨知识。

参考链接

  1. py-spy 的官方地址
  2. Ep 02. 开发中的碎碎念 ——《捕蛇者说》
  3. 超时 ——Requests 官方文档

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>嗯,用户的问题是关于Scrapy-Redis中部分进程卡死但仍然在运行的情况,需要找出原因和解决方案。首先,我得回忆一下Scrapy-Redis的基本工作原理。Scrapy-Redis是基于Scrapy的分布式爬虫框架,利用Redis进行任务队列的管理和调度,所以问题可能出在Redis的连接、任务分配或者网络通信上。 用户提到进程卡死但仍然运行,这可能意味着进程没有完全崩溃,而是处于某种阻塞状态。常见的原因可能包括网络问题导致Redis连接超时,或者任务队列出现死锁,无法获取新任务。另外,资源竞争也可能导致进程挂起,比如多个进程同时竞争同一个资源而没有正确的锁机制。 接下来需要分析可能的原因。首先,检查Redis服务器是否稳定,连接是否正常。如果Redis服务器出现响应延迟或者连接中断,爬虫进程可能会在等待响应时卡住。其次,任务队列是否有任务积压或者任务无法被正确处理,导致进程一直在等待任务。另外,分布式锁的问题,比如使用Redis的锁机制时,如果锁没有正确释放,其他进程可能会一直等待。 然后考虑资源限制。比如,爬虫进程可能占用了过多的内存或CPU,导致系统资源不足,进程被挂起。此外,爬虫中间件或管道中的某些自定义代码可能存在阻塞操作,比如同步的数据库操作,而没有使用异步处理,导致整个进程阻塞。 还需要检查日志信息,看是否有异常或错误提示。比如,连接超时的错误、任务处理中的异常,或者中间件中的问题。另外,Scrapy的telnet控制台是否被启用,有时候telnet端口被占用或未正确关闭,可能导致进程卡在监听状态,如引用[2]中提到的情况。 关于解决方案,首先建议用户检查Redis的连接状态和服务器性能,确保网络稳定,适当调整超时设置。其次,检查任务队列的管理,确认任务的分发和去重机制是否正常。优化资源使用,比如限制并发数,调整下载延迟,减少资源竞争。对于可能的死锁问题,检查分布式锁的实现,确保锁的获取和释放是原子操作,并且有超时机制。 此外,建议用户更新Scrapy-Redis和相关依赖库到最新版本,修复已知的bug。启用详细的日志录,帮助定位卡死的具体位置。如果问题依旧,可以尝试逐步禁用中间件和管道,排除第三方组件的影响。最后,确保代码中没有长时间阻塞的操作,尤其是涉及I/O的部分,考虑使用异步处理或增加超时机制。 用户可能需要进一步的帮助,比如具体的配置调整或代码示例,因此生成相关问题时应涵盖这些方面,比如如何监控Redis连接,如何优化任务队列,或者如何处理资源竞争问题。</think>### Scrapy-Redis进程卡死原因分析及解决方案 #### 一、可能原因分析 1. **Redis连接阻塞** - 网络波动或Redis服务器响应延迟导致连接超时,进程卡在等待Redis响应状态 - 未设置合理的连接超时参数(如`SOCKET_TIMEOUT=30`, `REDIS_TIMEOUT=10`) 2. **任务队列死锁** - 多个爬虫进程同时竞争同一个任务队列时,未正确处理分布式锁机制 - 队列去重机制异常导致任务无法被消费(如`dupefilter`模块故障) 3. **资源竞争与限制** - 内存泄漏或CPU占用过高导致进程假死但仍保持运行状态 - 数据库连接池耗尽或文件句柄数达到系统上限 4. **中间件/管道阻塞** - 同步代码阻塞事件循环(如未使用异步存储库直接操作MySQL) - 图片下载管道`ImagesPipeline`未正确配置(参考引用[2]中提到的Pillow依赖问题) #### 二、诊断步骤 ```python # 启用Scrapy详细日志观察卡死位置 custom_settings = { 'LOG_LEVEL': 'DEBUG', 'TELNETCONSOLE_ENABLED': True # 通过telnet检查引擎状态 } ``` 1. 通过`redis-cli monitor`监控Redis命令执行情况 2. 使用`telnet localhost 6023`连接爬虫进程检查堆栈信息 3. 通过`lsof -p <PID>`查看进程打开的文件描述符数量 #### 三、解决方案 1. **优化Redis连接配置** ```python # settings.py SCHEDULER_IDLE_BEFORE_CLOSE = 10 # 空闲等待时间(秒) REDIS_PARAMS = { 'socket_timeout': 30, 'socket_connect_timeout': 15, 'retry_on_timeout': True } ``` 2. **调整并发参数** ```python CONCURRENT_REQUESTS = 50 # 总并发数 CONCURRENT_ITEMS = 100 # 管道并发数 DOWNLOAD_DELAY = 0.25 # 控制请求频率 ``` 3. **修复任务队列异常** - 定期清理Redis残留数据: ```bash redis-cli DEL "爬虫名:dupefilter" redis-cli DEL "爬虫名:requests" ``` - 使用`priority`字段确保任务有序性 4. **升级依赖版本** ```bash pip install --upgrade scrapy-redis redis pycurl ``` 5. **代码层改进** ```python # 使用异步存储组件(如aiomysql) class AsyncMySQLPipeline: async def process_item(self, item, spider): await self.conn.execute('INSERT...') ``` #### 四、监控建议 1. 添加`scrapy-redis`心跳检测机制 2. 使用`prometheus`监控: ```python # 暴露指标端点 from prometheus_client import start_http_server start_http_server(8000) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值