实现不重启服务的情况下改变tornado的工作进程数

要实现不重启服务的情况下改变tornado 的进程数,那得先理解tornado是怎么管理多进程的。
其实很简单,主要看process.py的fork_processes函数,了解子进程的创建过程。

首先启动子进程。根据传入的进程数依次启动调用start_child函数fork一个进程,如果在子进程的上下文中,则改变全局变量task_id,并且返回task_id,退出fork_processes函数,启动IOLoop。如果是在父进程中,则继续创建并记录进程号对应的task_id。

 def start_child(i):
        pid = os.fork()
        if pid == 0:
            # child process
            _reseed_random()
            global _task_id
            _task_id = i
            return i
        else:
            children[pid] = i
            return None
for i in range(num_processes):
        id = start_child(i)
        if id is not None:
            return id

创建完成所有的子进程后,父进程进入了守护子进程的流程。简单来说就是一个大循环,然后阻塞在os.wait()上,等待子进程退出。如果有子进程异常退出时,父进程会重新创建一个新的。

num_restarts = 0
while children:
    try:
        pid, status = os.wait()
    except OSError as e:
        if errno_from_exception(e) == errno.EINTR:
            continue
        raise
    if pid not in children:
        continue
    id = children.pop(pid)
    if os.WIFSIGNALED(status):
        gen_log.warning("child %d (pid %d) killed by signal %d, restarting",
                        id, pid, os.WTERMSIG(status))
    elif os.WEXITSTATUS(status) != 0:
        gen_log.warning("child %d (pid %d) exited with status %d, restarting",
                        id, pid, os.WEXITSTATUS(status))
    else:
        gen_log.info("child %d (pid %d) exited normally", id, pid)
        continue
    num_restarts += 1
    if num_restarts > max_restarts:
        raise RuntimeError("Too many child restarts, giving up")
    new_id = start_child(id)
    if new_id is not None:
        return new_id
sys.exit(0)

要实现我们的目标,那么就需要改造这个process.py。首先得确定以什么的方式去触发创建进程和减少进程,我这里为了方便,使用了signal来控制。SIGUSR1增加进程,SIGUSR2减少进程。
定义增加进程和减少进程的处理函数。原来的代码没有记录进程数,这里设置一个全局的变量来记录进程数。

def handler_add_process(signum, frame):
    global _total_process
    _total_process += 1
    start_child(_total_process - 1)

def handler_reduce_process(signum, frame):
    global _total_process
    if len(children) <= 1:
        return
    kill_pid = None
    for k, v in children.items():
        if v == _total_process - 1:
            kill_pid = k
    if kill_pid != None:
        _total_process -= 1
        del children[kill_pid]
        #直接kill -9杀掉
        os.kill(kill_pid, 9)
    else:
        gen_log.warning("not find subprocess")
signal.signal(signal.SIGUSR1, handler_add_process)
signal.signal(signal.SIGUSR2, handler_reduce_process)

同时修改一下异常处理,因为此时子进程的上下文也进入大循环中了。如果没有子进程的情况下调用wait()函数会抛出error.ECHILD,所以捕获到该错误码,就可以退出循环,进程创建IOLoop的流程,这就成功创建出一个工作子进程。

try:
     pid, status = os.wait()
 except OSError as e:
     if errno_from_exception(e) == errno.EINTR:
         continue
     elif errno_from_exception(e) == errno.ECHILD:
         return task_id()
     raise

当然,我们不能够直接修改库中的代码。可以先把库的process.py文件拷备出来,放在自己的项目代码中,然后进行修改,最后再mock替换掉原来的process模块。

import process_mock
setattr(tornado,  "process", process_mock)
fork_id = tornado.process.fork_processes(4)
tornado.ioloop.IOLoop.current().start()

你可能感兴趣的:(python)