如前文所述,Arbiter是gunicorn master进程的核心。Arbiter主要负责管理worker进程,包括启动、监控、杀掉Worker进程;同时,Arbiter在某些信号发生的时候还可以热更新(reload)App应用,或者在线升级gunicorn。Arbiter的核心代码在一个文件里面,代码量也不大,源码在此:https://github.com/benoitc/gunicorn
  
Arbiter主要有以下方法:
setup:
    处理配置项,最重要的是worker数量和worker工作模型
 
init_signal
    注册信号处理函数
 
handle_xxx:
    各个信号具体的处理函数
 
kill_worker,kill_workers:
    向worker进程发信号
 
spawn_worker, spawn_workers:
    fork出新的worker进程
 
murder_workers:
    杀掉一段时间内未响应的worker进程
 
manage_workers:
    根据配置文件的worker数量,以及当前active的worker数量,决定是要fork还是kill worker进程
 
reexec
    接收到信号SIGUSR2调用,在线升级gunicorn
 
reload:
    接收到信号SIGHUP调用,会根据新的配置新启动worker进程,并杀掉之前的worker进程
 
sleep
    在没有信号处理的时候,利用select的timeout进行sleep,可被唤醒
 
wakeup
    通过向管道写消息,唤醒进程
 
run
    主循环
 
  Arbiter真正被其他代码(Application)调用的函数只有__init__和run方法,在一句代码里:
    Arbiter(self).run()
  上面代码中的self即为Application实例,其中__init__调用setup进行配置项设置。下面是run方法伪代码
def run()
self.init_signal()
self.LISTENERS = create_sockets(self.cfg, self.log)
self.manage_workers()
while True:
if no signal in SIG_QUEUE
self.sleep()
else:
handle_signal()
 关于fork子进程
  fork子进程的代码在 spawn_worker, 源码如下:
  

     def spawn_worker(self):
self.worker_age += 1
worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
self.app, self.timeout / 2.0,
self.cfg, self.log)
self.cfg.pre_fork(self, worker)
pid = os.fork()
if pid != 0:
self.WORKERS[pid] = worker
return pid # Process Child
worker_pid = os.getpid()
try:
util._setproctitle("worker [%s]" % self.proc_name)
self.log.info("Booting worker with pid: %s", worker_pid)
self.cfg.post_fork(self, worker)
worker.init_process()
sys.exit(0)
except SystemExit:
raise
except AppImportError as e:
self.log.debug("Exception while loading the application",
exc_info=True)
print("%s" % e, file=sys.stderr)
sys.stderr.flush()
sys.exit(self.APP_LOAD_ERROR)
except:
self.log.exception("Exception in worker process"),
if not worker.booted:
sys.exit(self.WORKER_BOOT_ERROR)
sys.exit(-1)
finally:
self.log.info("Worker exiting (pid: %s)", worker_pid)
try:
worker.tmp.close()
self.cfg.worker_exit(self, worker)
except:
self.log.warning("Exception during worker exit:\n%s",
traceback.format_exc())

Arbiter.spawn_worker

  主要流程:
    (1)加载worker_class并实例化(默认为同步模型 SyncWorker)
    (2)父进程(master进程)fork之后return,之后的逻辑都在子进程中运行
    (3)调用worker.init_process 进入循环,worker的所有工作都在这个循环中
    (4)循环结束之后,调用sys.exit(0)
    (5)最后,在finally中,记录worker进程的退出
    
    下面是我自己写的一点代码,把主要的fork流程简化了一下
 # prefork.py
import sys
import socket
import select
import os
import time def do_sub_process():
pid = os.fork()
if pid < 0:
print 'fork error'
sys.exit(-1)
elif pid > 0:
print 'fork sub process %d' % pid
return # must be child process
time.sleep(1)
print 'sub process will exit', os.getpid(), os.getppid()
sys.exit(0) def main():
sub_num = 2
for i in range(sub_num):
do_sub_process()
time.sleep(10)
print 'main process will exit', os.getpid() if __name__ == '__main__':
main()
在测试环境下输出:
  fork sub process 9601
  fork sub process 9602
  sub process will exit 9601 9600
  sub process will exit 9602 9600
  main process will exit 9600
 
  需要注意的是第20行调用了sys.exit, 保证子进程的结束,否则会继续main函数中for循环,以及之后的逻辑。注释掉第19行重新运行,看输出就明白了。
 
关于kill子进程
  master进程要kill worker进程就很简单了,直接发信号,源码如下:
  

     def kill_worker(self, pid, sig):
"""\
Kill a worker :attr pid: int, worker pid
:attr sig: `signal.SIG*` value
"""
try:
os.kill(pid, sig)
except OSError as e:
if e.errno == errno.ESRCH:
try:
worker = self.WORKERS.pop(pid)
worker.tmp.close()
self.cfg.worker_exit(self, worker)
return
except (KeyError, OSError):
return
raise
 
关于sleep与wakeup
  我们再来看看Arbiter的sleep和wakeup。Arbiter在没有信号需要处理的时候会"sleep",当然,不是真正调用time.sleep,否则信号来了也不能第一时间处理。这里得实现比较巧妙,利用了管道和select的timeout。看代码就知道了
        def sleep(self):
"""\
Sleep until PIPE is readable or we timeout.
A readable PIPE means a signal occurred.
"""
ready = select.select([self.PIPE[0]], [], [], 1.0) # self.PIPE = os.pipe()
if not ready[0]:
return
while os.read(self.PIPE[0], 1):
pass

  代码里面的注释写得非常清楚,要么PIPE可读立即返回,要么等待超时。管道可读是因为有信号发生。这里看看pipe函数

  os.pipe()

Create a pipe. Return a pair of file descriptors (r,w) usable for reading and writing, respectively.

 
  那我们看一下什么时候管道可读:肯定是往管道写入的东西,这就是wakeup函数的功能
        def wakeup(self):
"""
Wake up the arbiter by writing to the PIPE
"""
os.write(self.PIPE[1], b'.')

最后附上Arbiter的信号处理

  • QUITINT: Quick shutdown
  • TERM: Graceful shutdown. Waits for workers to finish their current requests up to the graceful timeout.
  • HUP: Reload the configuration, start the new worker processes with a new configuration and gracefully shutdown older workers. If the application is not preloaded (using the --preloadoption), Gunicorn will also load the new version.
  • TTIN: Increment the number of processes by one
  • TTOU: Decrement the number of processes by one
  • USR1: Reopen the log files
  • USR2: Upgrade the Gunicorn on the fly. A separate TERM signal should be used to kill the old process. This signal can also be used to use the new versions of pre-loaded applications.
  • WINCH: Gracefully shutdown the worker processes when Gunicorn is daemonized.
 
 
reference:

gunicorn Arbiter 源码解析的更多相关文章

  1. gunicorn syncworker 源码解析

    gunicorn支持不同的worker类型,同步或者异步,异步的话包括基于gevent.基于eventlet.基于Aiohttp(python版本需要大于3.3),也有多线程的版本.下面是gunico ...

  2. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  3. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  4. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  5. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  6. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  7. Spring IoC源码解析——Bean的创建和初始化

    Spring介绍 Spring(http://spring.io/)是一个轻量级的Java 开发框架,同时也是轻量级的IoC和AOP的容器框架,主要是针对JavaBean的生命周期进行管理的轻量级容器 ...

  8. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  9. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

随机推荐

  1. MyBatis学习笔记1--初识MyBatis

    我也是初学者,写博客只是想把自己的整个思路整理一下,有不对或者不好的地方,请大家多多指正. 1.MyBatis简介 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射. ...

  2. MySQL 2006 超时

    http://ronaldbradford.com/blog/sqlstatehy000-general-error-2006-mysql-server-has-gone-away-2013-01-0 ...

  3. cgg之数据类型

    所有例子都在64为操作系统 Linux 2.6.30 x86_64 x86_64 x86_64 GNU/Linux 1.1整数 在stdint.h中定义一些看上去更明确的整数类型 #ifndef __ ...

  4. IDEA热部署(二)---jetty插件启动maven项目

    jetty插件的配置 我们使用jetty插件来进行启动我们的maven项目,在pom.xml中进行配置: <plugins> <plugin> <groupId>o ...

  5. Python机器学习库和深度学习库总结

    我们在Github上的贡献者和提交者之中检查了用Python语言进行机器学习的开源项目,并挑选出最受欢迎和最活跃的项目. 1. Scikit-learn(重点推荐) www.github.com/sc ...

  6. vuejs配置less

     本次我们来聊聊,vuejs里如何使用less.都是根据自己的开发经验来写,不对的地方请各位拍砖. 在webstorm上配置Less 首先,全局安装less npm install less -g 然 ...

  7. 安卓Html标签,创意工具类

    之前开发项目中,遇到了在Textview中使用Html标签的情形,由于在代码中使用字符串,Android Studio上一堆的黄色警告,而且对于过时的Html.fromHtml,拿它一点办法也没有. ...

  8. python并发编程之多线程一

    一,什么是线程 线程也被称为轻量进程计算机科学术语,指运行中的程序的调度单位. 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程.线程不拥有系统资源,只有运行必须的一些数据结构: ...

  9. shell,bash,zsh,console,terminal到底是什么意思,它们之间又是什么关系?

    原文链接 终端(terminal,或者叫物理终端):是一种设备,不是一个程序,一般说的就是能提供命令行用户界面的设备,典型的是屏幕和键盘,或其他的一些物理终端.虚拟终端:屏幕和键盘只是一个终端,可能不 ...

  10. web管理kvm ,安装webvirtmgr

    原创博文安装配置KVM http://www.cnblogs.com/elvi/p/7718574.htmlweb管理kvm http://www.cnblogs.com/elvi/p/7718582 ...