要实现不重启服务的情况下改变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()