美妙的多进程管理 造 个类 gunicorn 的轮 blog: xiaorui.cc github: github.com/rfyiamcool
内容 supervisor vs gunicorn vs uwsgi linux 异步信号 孤 进程 vs 僵 进程 daemon 的实现 prefork 是怎么 回事 打造 个较完善的多进程管理轮 怎么写代码
Master Worker elegance ctrl c signal reduce add Master pname zombie/child dup fork fork fork daemon monitor Worker Worker Worker signal reload oom/crash dynamic
supervisor vs gunicorn vs uwsgi supervisor 是基于执 件的, 可以控制多个应用进程. 但单纯的多进程 管理. gunicorn uwsgi 是基于 application 的, 且可以实现 wsgi 关接
file: process.py supervisor 实现 file: options.py class Subprocess(): def spawn(): filename, argv = self.get_execv_args() pid = options.fork() if pid!= 0: return self._spawn_as_parent(pid) else: return self._spawn_as_child(filename, argv) def _spawn_as_child(): options.dup2(self.pipes['child_stdin'], 0) options.dup2(self.pipes['child_stdout'], 1) options.dup2(self.pipes['child_stdout'], 2) options.execve(filename, argv, env) def execve(self, filename, argv, env): return os.execve(filename, argv, env) #execve 把新进程替换老进程, 继承 [program:web] command=python /var/www/service.py 80%(process_num)02d process_name=%(program_name)s_%(process_num)02d autostart=true autorestart=true umask=022 startsecs=0 stopwaitsecs=0 redirect_stderr=true stdout_logfile=/tmp/codoon.log numprocs=4
why repeat? 基于 function 更加细腻 dynamic add/reduce log reload / module reload / config reload kill friendly timeout? force to kill queue, 共享变量, 锁... ( 封装好 ) more
Linux Signal for diy 信号 数字 描述 sigint 2 键盘 ctrl c sigquit 3 键盘 ctrl d or \ sigkill 9 暴 终 进程 sigalrm 14 定时器超时 sigterm 15 默认的 kill 信号 sigchld 17 进程退出发出的信号 sigttin 21 增加 个进程 sigttou 22 减少 个进程 sigwinch 34 清理 worker 进程
signal 不可靠信号 : 也称为非实时信号, 不支持排队, 信号可能会丢失, 比如 发送多次相同的信号, 进程只能收到 次. 信号值取值区间为 1~31; 可靠信号 : 也称为实时信号, 支持排队, 信号不会丢失, 发多少次, 就可 以收到多少次. 信号值取值区间为 32~64 信号不排队
signal. sigkill 法捕获 什么时候会被 sigkill. 自 贱,kill -9 pid. 内存占用厉害, 被 oom 了. ulimit 设置了 cpu timeout
在 python 下注册 sigkill error In [3]: def trycache(*args):...: print args...: In [4]: signal.signal(signal.sigkill,trycache) ----------------------------------------------------------------------- ---- RuntimeError Traceback (most recent call last) <ipython-input-4-7f0697d1acc4> in <module>() ----> 1 signal.signal(signal.sigkill,trycache) RuntimeError: (22, 'Invalid argument')
孤 进程 vs 僵 进程 孤 进程 : 个 进程退出, 它的 进程还在运, 那么那些 进程将成为 孤 进程 孤 进程将被 init 进程 ( 进程号为 1) 所收养, 并由 init 进程对它们完成 状态收集 作 僵 进程 : 个进程使用 fork 创建 进程, 如果 进程退出, 进程并没有调 用 wait 或 waitpid 获取 进程的状态信息, 那么 进程的进程描述符仍然保存在 系统中 这种进程称之为僵死进程
怎么解决僵 问题 通过 signal(sigchld, SIG_IGN) 通知内核对 进程的结束不关, 由内核回 收 进程调用 wait/waitpid 函数进 收, 如果尚 进程退出 wait 会导致 进 程阻塞 waitpid 可以通过传递 WNOHANG 使 进程不阻塞立即返回 通过 SIGCHLD 的注册函数来处理信号, 如 下 很多信号发出, 那么会有 丢失信号的问题, 因为内核发信号不排队... 孤 进程的 式, 通过 fork setsid 实现
import multiprocessing import time def daemon(): while 1: time.sleep(10) def non_daemon(): while 1: time.sleep(100) def non_daemon_break(): time.sleep(3) # 就是让他主动退出 if name == ' main ': t = [] t.append( multiprocessing.process(name= daemon', target=daemon)) t.append( multiprocessing.process(name= non-daemon', target=non_daemon)) t.append( multiprocessing.process(name= non-daemon', target=non_daemon_break)) for i in t: i.daemon = True i.start() while 1: time.sleep(1)
import time import os import multiprocessing def daemon(): while 1: time.sleep(10) def non_daemon(): while 1: time.sleep(100) def non_daemon_break(): time.sleep(3) # 因为是孤 进程, 进程由 init 收 了. if name == ' main ': t = [] t.append(multiprocessing.process(name= daemon', target=daemon)) t.append(multiprocessing.process(name= non-daemon', target=non_daemon)) t.append(multiprocessing.process(name= non-daemon', target=non_daemon_break)) for i in t: i.daemon = True i.start() os.kill(os.getpid(), 15)
Daemon 守候进程 fork 进程, 然后 进程退出, 这已经构成基本的 daemon! 但 进程还 在 进程的会话里面. 进程调用 setsid, 使 进程成为新的会话组长. 但新的会话组长可申请 控制终端. 再次 fork 个 孙进程, 掉 进程, 保留孙 进程. 切换 作目录, 关闭 stdin\stdout\stderr 的句柄,umask
如何摆脱终端? 进程 > 进程组 > 会话 = 登录终端 摆脱当前终端, new session 关闭终端会触发 SIGHUP 屏蔽 SIGHUP NOHUP = signal(sighup, SIG_IGN) NOHUP sleep 100 > dehub.log 2 > &1 == ignore sighup + os.dup(1,2)
import os import time def daemonize(): pid=os.fork() # fork1 if pid<0: # error print "fork1 error" return -1 elif pid>0: # parent. exit(0) os.chdir("/") os.setsid() pid=os.fork() # fork 2 if pid<0: print "fork2 error" return -1 elif pid>0: exit(0) os.umask(022) os.close(0) os.close(1) os.close(2) fd=os.open('/dev/null', 2) os.dup(fd) os.dup(fd) daemon 代码示例 if name == " main ": daemonize() time.sleep(30)
那么问题来了, 如何造 个健全的后端服务! 用配置 件控制 uid 权限规范进程名 daemon 守候进程调整进程, Add Reduce 调优配置 : 最 处理任务, 是否线程 协程捕获各种信号, 解决僵 进程 fcntl lock/check sock.pid 传参获取服务状态, 重启服务, 开启, 停
a program fd table fd0 flag 指针 fd1.. fd1.. fd table fd0 flag 指针 fd1.. file table 件状态标志 offset v node 指针 v 节点表 v 节点信息 i 节点信息当前 件长度 fd1..
end - xiaorui.cc