gunicorn工作原理

Gunicorn“绿色独角兽”是一个被广泛使用的高性能的Python WSGI UNIX HTTP服务器,移植自Ruby的独角兽(Unicorn )项目,使用pre-fork worker模式,具有使用非常简单,轻量级的资源消耗,以及高性能等特点。

Gunicorn 服务器作为wsgi app的容器,能够与各种Web框架兼容(flask,django等),得益于gevent等技术,使用Gunicorn能够在基本不改变wsgi app代码的前提下,大幅度提高wsgi app的性能。

总体结构

gunicorn pre-fork worker模型中有一个管理进程以及几个的工作进程。管理进程:master,工作进程:worker。(以下代码中为了方面理解,均去除了一些干扰代码)

master通过pre-fork的方式创建多个worker:

  1.  
    def spawn_worker(self):
  2.  
    self.worker_age += 1
  3.  
    #创建worker。请注意这里的app 对象并不是真正的wsgi app对象,而是gunicorn的app
  4.  
    #对象。gunicorn的app对象负责import我们自己写的wsgi app对象。
  5.  
    worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
  6.  
    self.app, self.timeout / 2.0,
  7.  
    self.cfg, self.log)
  8.  
    pid = os.fork()
  9.  
    if pid != 0: #父进程,返回后继续创建其他worker,没worker后进入到自己的消息循环
  10.  
    self.WORKERS[pid] = worker
  11.  
    return pid
  12.  
     
  13.  
    # Process Child
  14.  
    worker_pid = os.getpid()
  15.  
    try:
  16.  
    ..........
  17.  
    worker.init_process() #子进程,初始化woker,进入worker的消息循环,
  18.  
    sys.exit(0)
  19.  
    except SystemExit:
  20.  
    raise
  21.  
    ............

在worker.init_process()函数中,worker中gunicorn的app对象会去import 我们的wsgi app。也就是说,每个woker子进程都会单独去实例化我们的wsgi app对象。每个worker中的swgi app对象是相互独立、互不干扰的。

manager维护数量固定的worker:

  1.  
    def manage_workers(self):
  2.  
    if len(self.WORKERS.keys()) < self.num_workers:
  3.  
    self.spawn_workers()
  4.  
    while len(workers) > self.num_workers:
  5.  
    (pid, _) = workers.pop(0)
  6.  
    self.kill_worker(pid, signal.SIGQUIT)

创建完所有的worker后,worker和master各自进入自己的消息循环。 
master的事件循环就是收收信号,管理管理worker进程,而worker进程的事件循环就是监听网络事件并处理(如新建连接,断开连接,处理请求发送响应等等),所以真正的连接最终是连到了worker进程上的。(注:有关这种多进程模型的详细介绍,可以参考http://blog.csdn.net/largetalk/article/details/7939080)

worker

woker有很多种,包括:ggevent、geventlet、gtornado等等。这里主要分析ggevent。

每个ggevent worker启动的时候会启动多个server对象:worker首先为每个listener创建一个server对象(注:为什么是一组listener,因为gunicorn可以绑定一组地址,每个地址对于一个listener),每个server对象都有运行在一个单独的gevent pool对象中。真正等待链接和处理链接的操作是在server对象中进行的。

  1.  
    #为每个listener创建server对象。
  2.  
    for s in self.sockets:
  3.  
    pool = Pool(self.worker_connections) #创建gevent pool
  4.  
    if self.server_class is not None:
  5.  
    #创建server对象
  6.  
    server = self.server_class(
  7.  
    s, application=self.wsgi, spawn=pool, log=self.log,
  8.  
    handler_class=self.wsgi_handler, **ssl_args)
  9.  
    .............
  10.  
    server.start() #启动server,开始等待链接,服务链接
  11.  
    servers.append(server)
  12.  
    .........

上面代码中的server_class实际上是一个gevent的WSGI SERVER的子类:

  1.  
    class PyWSGIServer(pywsgi.WSGIServer):
  2.  
    base_env = BASE_WSGI_ENV

需要注意的是构造PyWSGIServer的参数:

  1.  
    self.server_class(
  2.  
    s, application=self.wsgi, spawn=pool, log=self.log,
  3.  
    handler_class=self.wsgi_handler, **ssl_args)

这些参数中s是server用来监听链接的套接字。spawn是gevent的协程池。application即是我们的wsgi app(通俗点讲就是你用 flask 或者 django写成的app),我们的app就是通过这种方式交给gunicorn的woker去跑的。 handler_class是gevent的pywsgi.WSGIHandler子类。

当所有server对象创建完毕后,worker需要定时通知manager,否则会被认为是挂掉了。

  1.  
    while self.alive:
  2.  
    self.notify()
  3.  
    .......

这个地方的notify机制设计的比较有趣,每个worker有个与之对应的tmp file,每次notify的时候去操作一下这个tmp file(比如通过os.fchmod),这个tmp file的last update的时间戳就会更新。而manager则通过检查每个worker对应的temp file的last update的时间戳,来判断这个进程是否是挂掉的。

WSGI SERVER

真正等待链接和处理链接的操作是在gevent的WSGIServer 和 WSGIHandler中进行的。 
最后再来看一下gevent的WSGIServer 和 WSGIHandler的主要实现:

WSGIServer 的start函数里面调用start_accepting来处理到来的链接。在start_accepting里面得到接收到的套接字后调用do_handle来处理套接字:

  1.  
    def do_handle(self, *args):
  2.  
    spawn = self._spawn
  3.  
    spawn(self._handle, *args)

可以看出,WSGIServer 实际上是创建一个协程去处理该套接字,也就是说在WSGIServer 中,一个协程单独负责一个HTTP链接。协程中运行的self._handle函数实际上是调用了WSGIHandler的handle函数来不断处理http 请求:

  1.  
    def handle(self):
  2.  
    try:
  3.  
    while self.socket is not None:
  4.  
    result = self.handle_one_request()#处理HTTP请求
  5.  
    if result is None:
  6.  
    break
  7.  
    if result is True:
  8.  
    continue
  9.  
    self.status, response_body = result
  10.  
    self.socket.sendall(response_body)#发送回应报文
  11.  
    ..............

在handle函数的循环内部,handle_one_request函数首先读取HTTP 请求,初始化WSGI环境,然后最终调用run_application函数来处理请求:

  1.  
    def run_application(self):
  2.  
    self.result = self.application(self.environ, self.start_response)
  3.  
    self.process_result()

在这个地方才真正的调用了我们的 app。

总结:gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。每当有HTTP链接到来时,wsgi server创建一个协程来处理该链接,协程处理该链接的时候,先初始化WSGI环境,然后调用用户提供的app对象去处理HTTP请求。

gunicorn工作原理的更多相关文章

  1. 菜鸟学Struts2——Struts工作原理

    在完成Struts2的HelloWorld后,对Struts2的工作原理进行学习.Struts2框架可以按照模块来划分为Servlet Filters,Struts核心模块,拦截器和用户实现部分,其中 ...

  2. 【夯实Nginx基础】Nginx工作原理和优化、漏洞

    本文地址 原文地址 本文提纲: 1.  Nginx的模块与工作原理    2.  Nginx的进程模型    3 . NginxFastCGI运行原理        3.1 什么是 FastCGI   ...

  3. HashMap的工作原理

    HashMap的工作原理   HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...

  4. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)

    RAC 工作原理和相关组件(三) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...

  5. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  6. Servlet的生命周期及工作原理

    Servlet生命周期分为三个阶段: 1,初始化阶段  调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在 ...

  7. 代码管理工具 --- git的学习笔记二《git的工作原理》

    通过几个问题来学习代码管理工具之git 一.git是什么?为什么要用它?使用它的好处?它与svn的区别,在Mac上,比较好用的git图形界面客户端有 git 是分布式的代码管理工具,使用它是因为,它便 ...

  8. 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL

    周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...

  9. 浏览器内部工作原理--作者:Tali Garsiel

    本篇内容为转载,主要用于个人学习使用,作者:Tali Garsiel 一.介绍 浏览器可以被认为是使用最广泛的软件,本文将介绍浏览器的工作原理,我们将看到,从你在地址栏输入google.com到你看到 ...

随机推荐

  1. nginx+vue刷新404

    问题原因:刷新页面时访问的资源在服务端找不到,因为vue-router设置的路径不是真实存在的路径.如上的404现象,是因为在nginx配置的根目录/Data/app/xqsj_wx/dist下面压根 ...

  2. TensorFlow学习笔记——节点(constant、placeholder、Variable)

    一. constant(常量) constant是TensorFlow的常量节点,通过constant方法创建,其是计算图(Computational Graph)中的起始节点,是传入数据. 创建方式 ...

  3. java泛型讲解

    原文: https://blog.csdn.net/briblue/article/details/76736356 泛型,一个孤独的守门者. 大家可能会有疑问,我为什么叫做泛型是一个守门者.这其实是 ...

  4. SQL-27 给出每个员工每年薪水涨幅超过5000的员工编号emp_no、薪水变更开始日期from_date以及薪水涨幅值salary_growth,并按照salary_growth逆序排列。 提示:在sqlite中获取datetime时间对应的年份函数为strftime('%Y', to_date)

    题目描述 给出每个员工每年薪水涨幅超过5000的员工编号emp_no.薪水变更开始日期from_date以及薪水涨幅值salary_growth,并按照salary_growth逆序排列. 提示:在s ...

  5. 读写锁 SRWLOCK

    读写锁在对资源进行保护的同时,还能区分想要读取资源值的线程(读取者线程)和想要更新资源的线程(写入者线程). 对于读取者线程,读写锁会允许他们并发的执行.当有写入者线程在占有资源时,读写锁会让其它写入 ...

  6. MATLAB中mexFunction函数的接口规范(转)

    reference:https://www.cnblogs.com/haoyul/p/5606720.html https://www.cnblogs.com/zeakey/p/3984733.htm ...

  7. Problem A 还会用继承吗?

    定义一个Base类,包括1个int类型的属性,以及满足输出格式要求的构造函数.拷贝构造函数和析构函数. 定义Base类的子类Derived,包括1个int类型的属性, 以及满足输出格式要求的构造函数. ...

  8. 第四周四则运算3 PSP表格

    PSP2.1 Personal Software Process Stages time Plan 计划   -Estimate 整数四则运算 分数四则运算 括号 括号四则运算 在主函数中调用 30m ...

  9. <spark入门><Intellj环境配置><scala>rk入门><Intellj环境配置><scala>

    # 写在前面: 准备开始学spark,于是准备在IDE配一个spark的开发环境. 嫌这篇格式不好的看这里链接 用markdown写的,懒得调格式了,么么哒 # 相关配置: ## 关于系统 * mac ...

  10. L259

    Few things can feel as crushing as being rejected by someone who you're either dating or romanticall ...