====================
使用twistd托管应用
====================
:作者: gashero
:日期: 2009-08-26
.. contents:: 目录
.. sectnum::
简介
------
系统开发中经常遇到要把应用放到daemon中运行的情况,话说这个倒是不难,有很多现成的模块可用,就算是自己写也没几行代码。但是daemon托管的同时再捕捉所有屏幕输出日志,自动切换到其他uid/gid,切换运行目录chroot等等高级功能就不那么容易自己搞定了。
而使用twisted框架的daemon托管程序twistd来执行这些任务则是轻车熟路,功能全面而且稳定可靠。
使用twistd托管应用
--------------------
使用twistd托管应用需要实现以下几个要求:
#. 启动函数非阻塞
#. 定义启动模块全局变量 `application`
第二个条件倒是相对容易实现,但是对于绝大多数框架来说,第一个条件几乎是不可能的任务。下面给出一个符合上面要求的启动脚本结构::
from twisted.internet import reactor,protocol,defer
from twisted.application import service,internet
from twisted.python import log
def main():
print 'started!'
return
if __name__=='__main__':
main()
elif __name__=='__builtin__':
main()
application=service.Application('hello')
假设如上模块叫做 ``sample.py`` ,那么可以通过如下脚本启动使其进入daemon模式运行::
twistd -y sample.py
如上启动后会自动托管屏幕输出日志。需要关闭该应用时可以用::
kill `cat twistd.pid`
对于主函数中需要阻塞的情况,也就是几乎绝大多数情况下,可以使用 `reactor.callLater()` 函数使得在启动函数中不阻塞,而在其后的主循环中阻塞的方式运行。如下(只显示启动脚本部分)::
if __name__=='__main__':
main()
elif __name__=='__builtin__':
reactor.callLater(1,main)
application=service.Application('hello')
当然,你也许想到了为什么不把 `main()` 放入线程中运行。这是因为很多框架的启动函数中同时会注册信号处理器,而这些注册行为必须要在主线程中执行。当然如果你足够幸运的话,也可以用如下的形式碰碰运气::
if __name__=='__main__':
main()
elif __name__=='__builtin__':
reactor.callInThread(main)
application=service.Application('hello')
这里建议使用twisted自带的线程管理函数,而不是自己用threading和thread模块的线程,因为后者的错误日志可能会消失的无影无踪。
好了,按照如上改造后,就可以使用twisted托管了。日志是按照不超过1MB来轮询的,在 ``twistd.log.<N>`` 中的数字N会持续增加,新日志的N比较小。建议用一个crontab来定期清理多余的日志。
控制脚本
----------
为了更方便的使用托管,还可以编写一个控制脚本实现进程的启动、退出、阻塞方式调试、日志查看等工作。内容如下,我一般叫做 ``ctlapp.sh`` ::
#! /usr/bin/env sh MAIN_MODULE=sample.py case $1 in start) PYTHONPATH=.:$PYTHONPATH twistd -y $MAIN_MODULE ;; stop) kill `cat twistd.pid` ;; restart) kill `cat twistd.pid` sleep 1 PYTHONPATH=.:$PYTHONPATH twistd -y $MAIN_MODULE ;; log) tail -f twistd.log ;; *) echo "Usage: ./ctlapp.sh start | stop | restart | log" ;; esac
使用该控制脚本的4个参数可以方便的控制应用。注意其中设置PYTHONPATH部分,是因为某些编译版本的twistd无法找到当前路径下的模块。