如何运行长期(无限)Python进程?
我最近开始尝试用Python进行网页开发。到目前为止,我用Apache和mod_wsgi,以及Python 2.7的Django框架,取得了一些成功。不过,我遇到了一些问题,就是有些进程需要一直运行,不断更新信息等等。
我写了一个脚本,叫做“daemonManager.py”,它可以启动和停止所有或单个的Python更新循环(我应该叫它们守护进程吗?)。这个脚本通过分叉进程,然后加载特定功能的模块,最后启动一个无限循环来实现。它会在/var/run
中保存一个PID文件,以便跟踪进程。目前为止一切都还不错。但我遇到了一些问题:
有时候其中一个进程会突然退出。我早上检查
ps
时,发现这个进程不见了。没有记录任何错误(我在用logging
模块),而且我已经考虑了所有可能的异常并进行了记录。我也不认为这些退出的进程和我的代码有关系,因为我的所有进程运行的代码完全不同,而且退出的时间间隔也差不多。当然,我可能错了。Python进程在运行几天或几周后就死掉是正常的吗?我该如何解决这个问题?我应该写一个守护进程,定期检查其他守护进程是否还在运行吗?如果那个守护进程也停止了呢?我真不知道该怎么处理这个问题。我怎么才能编程判断一个进程是否还在运行呢?我把PID文件保存在
/var/run
中,通过检查PID文件是否存在来判断进程是否在运行。但是如果进程因为意外原因死掉了,PID文件会保留。因此每次进程崩溃(每周几次),我都得删除这些文件,这样就有点失去意义了。我想我可以检查文件中的PID对应的进程是否在运行,但如果另一个进程启动并被分配了死掉的进程的PID呢?我的守护进程会认为这个进程还在正常运行,即使它早就死了。再次,我不知道该如何处理这个问题。
如果有人能给出关于如何最好地运行无限的Python进程的有用建议,并且能帮助我解决上述问题,我会很感激。
我在一台Ubuntu机器上使用Apache 2.2.14。
我的Python版本是2.7.2。
3 个回答
我猜你是在使用Unix/Linux系统,但你并没有明确说明。我对你的问题没有直接的建议,所以我不指望自己能给出“正确”的答案。不过这里有一些可以探讨的地方。
首先,如果你的守护进程(daemon)崩溃了,你应该先解决这个问题。只有有bug的程序才会崩溃。也许你可以在调试器下启动它们,看看崩溃时发生了什么(如果可以的话)。这些进程有没有记录日志?如果没有,建议加上日志记录。这可能有助于你找出崩溃的原因。
其次,你的守护进程是在提供服务(比如打开管道等待请求)还是在定期清理?如果是定期清理的进程,建议使用cron来定期启动它们,而不是让它们在无限循环中运行。使用cron进程比守护进程更好。同样,如果它们是打开端口并处理请求的服务,你有没有考虑让它们与INETD一起工作?总之,使用一个守护进程(inetd)比一堆守护进程要好。
第三,把进程ID(PID)保存在文件里并不是很有效,正如你所发现的。也许使用共享的进程间通信(IPC),比如信号量,会更好。不过我这里没有具体的细节。
第四,有时候我需要在网站的上下文中运行一些东西。我使用一个cron进程,调用wget并访问一个维护的URL。你设置一个特殊的cookie,并在wget命令行中包含这个cookie的信息。如果这个特殊的cookie不存在,就返回403,而不是执行维护过程。这样做的另一个好处是避免了登录数据库和其他环境问题,因为处理普通网页的代码也在处理维护过程。
希望这些能给你一些启发。我觉得如果可以的话,尽量避免使用守护进程是个不错的开始。如果你能在mod_wsgi中运行你的Python代码,那就省去了支持多个“环境”的麻烦。调试一个运行了几天后崩溃的进程真是太痛苦了。
你可以把Python进程想象成可以“永远”运行的,只要你的程序、Python解释器或者你使用的Python库没有内存泄漏的问题。(即使有内存泄漏,如果你的64位机器有足够的交换空间,进程也可能会一直运行下去。几十年,甚至几百年都是有可能的。我有过Python进程在有限的硬件上运行了将近两年,直到硬件需要搬迁。)
确保程序在崩溃后能自动重启,这在以前的Linux系统中非常简单。当时使用的是SysV风格的 init
,你只需要在/etc/inittab
中添加一行,init(8)
就会在启动时启动你的程序,并在它崩溃时重新启动它。(我不知道现在很多发行版使用的新upstart
init
替代品是否有类似的功能。我不是说这不可能,只是我不知道怎么做。)
不过,即使是以前的init(8)
机制也没有那么灵活,很多人对此并不满意。比如,DJB的daemontools包就是一种用于进程控制和监控的工具,旨在让守护进程永远存活。还有Linux-HA套件提供了类似的工具,尽管它可能提供了过多的“额外”功能,不一定适合这个任务。monit
也是一个不错的选择。
我先说一下,这只是一种管理长时间运行过程(LRP)的方法,并不是唯一的方法。
根据我的经验,最好的产品是专注于你正在处理的具体问题,同时把一些辅助技术交给其他库来处理。在这里,我指的是将进程放到后台运行(也就是双重分叉的技巧)、监控和日志重定向。
我最喜欢的解决方案是 http://supervisord.org/
使用像 supervisord 这样的系统,你基本上是写一个普通的 Python 脚本,这个脚本在一个“无限”循环中执行某个任务。
#!/usr/bin/python
import sys
import time
def main_loop():
while 1:
# do your stuff...
time.sleep(0.1)
if __name__ == '__main__':
try:
main_loop()
except KeyboardInterrupt:
print >> sys.stderr, '\nExiting by user request.\n'
sys.exit(0)
这样写脚本使得开发和调试变得简单方便(你可以在终端轻松地启动/停止它,并在事件发生时查看日志输出)。当你准备把它投入生产时,只需定义一个 supervisor 配置来调用你的脚本(这里有一个完整的“程序”定义示例,大部分内容是可选的: http://supervisord.org/configuration.html#program-x-section-example)。
Supervisor 有很多配置选项,我就不一一列举了,但我可以说它特别解决了你提到的问题:
- 后台运行/守护进程
- 进程ID跟踪(可以配置为在进程意外终止时自动重启)
- 在你的脚本中正常记录日志(如果使用日志模块而不是打印,可以使用流处理器),但让 supervisor 为你重定向到文件。