1、gunicorn + flask 处理高并发请求介绍

一,独角兽
    Gunicorn 服务器作为wsgi app的容器, 采用 pre-fork 模型中有一个管理进程以及几个的工作进程。master 管理多个 slave 进程
   创建slave进程,监听事件:
     1, 根据定义的 work数量 创建多个 work 进程
     2, 在worker.init_process()函数中,每个woker子进程都会单独去实例化我们的wsgi app对象。针对多进程,一个进程实例化一个 app 对象,多线程,一个线程处理实例化app对象,协程根据server对象
     3, 然后,worker和master各自进入自己的消息循环。 master的事件循环就是收收信号,管理管理worker进程,
       而worker进程的事件循环就是监听网络事件并处理(如新建连接,断开连接,处理请求发送响应等等),真正的连接最终是连到了worker进程上的


# 创建 固定数量的 worker(由 manager 进行维护)
    def manage_workers(self):
        if len(self.WORKERS.keys()) < self.num_workers:
            self.spawn_workers()
        while len(workers) > self.num_workers:
            (pid, _) = workers.pop(0)
            self.kill_worker(pid, signal.SIGQUIT)

    # 创建 work 进程
    def spawn_worker(self):
        self.worker_age += 1
        #创建worker。请注意这里的app 对象并不是真正的wsgi app对象,而是gunicorn的app
        #对象。gunicorn的app对象负责import我们自己写的wsgi app对象。
        worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,
                                    self.app, self.timeout / 2.0,
                                    self.cfg, self.log)
        # pid = 0 表示子进程
        pid = os.fork()
        #父进程,返回后继续创建其他worker,没worker后进入到自己的消息循环
        if pid != 0:
            # 父进程记录子进程
            self.WORKERS[pid] = worker
            return pid

        # 子进程继续运行
        # Process Child
        worker_pid = os.getpid()
        try:
            ..........
            worker.init_process() #子进程,初始化woker,进入worker的消息循环,
            sys.exit(0)
        except SystemExit:
            raise
        ............
二,  Gunicorn 几种启动方式 sync (默认值) eventlet gevent tornado
        IO 受限 -建议使用gevent或者asyncio
        CPU受限 -建议增加workers数量
        不确定内存占用? -建议使用gthread
       # gunicorn 启动4个进程(默认启动方式),每个 work 单线程 并发量 4*1
        gunicorn -w 4 -b 0.0.0.0:8000 demo:app
         # gunicorn 允许每个worker拥有多个线程  并发量 = 4*2
        gunicorn -w 4 --thread=2 --worker-class=gthread main:app
         # gunicorn  伪线程 gevent (协程) 并发量 3*1000
        gunicorn --worker-class=gevent --worker-connections=1000 -w 3 main:app
三, web 异步任务实现方式:
    1, web 的多并发 并不是有 flask 来实现的, flask 只是负责根据Request产生Response(一个python程序),
    多并发是由 WSGI server是通过进程(pre-fork)来并发的。这样并发就取决与进程数,如果WSGI server用了gevent,
      eventlet等 green thread技术,就可以支持更多并发
    2, 针对 work 启动方式  gevent
      每个ggevent worker启动的时候会启动多个server对象,worker首先为每个listener创建一个server对象,
      每个server对象都有运行在一个单独的gevent pool对象中。真正等待链接和处理链接的操作是在server对象中进行的
    3,WSGIServer 实际上是创建一个协程去处理该套接字,也就是说在WSGIServer 中,一个协程单独负责一个HTTP链接。
    协程中运行的self._handle函数实际上是调用了WSGIHandler的handle函数来不断处理http 请求
    4,gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。
      每当有HTTP链接到来时,wsgi server创建一个协程来处理该链接,协程处理该链接的时候,先初始化WSGI环境,
      然后调用用户提供的app对象去处理HTTP请求(处理http请求可以理解为一个程序)。
#为每个listener创建server对象。
    for s in self.sockets:
        pool = Pool(self.worker_connections) #创建gevent pool
        if self.server_class is not None:
           #创建server对象
            server = self.server_class(
                s, application=self.wsgi, spawn=pool, log=self.log,
                handler_class=self.wsgi_handler, **ssl_args)
        .............
        server.start() #启动server,开始等待链接,服务链接
        servers.append(server)
        .........
  在handle函数的循环内部,handle_one_request函数首先读取HTTP 请求,初始化WSGI环境,然后最终调用run_application函数来处理请求
    def run_application(self):
        self.result = self.application(self.environ, self.start_response)
        self.process_result()

.

2、gunicorn flask的请求流程

当运行gunicorn这个命令启动flask的时候

首先回去调用gunicorn/app/wsgiapp中的run方法

 回去调用run()方法,这个run方式本质是gunicorn/arbiter中的run()方法

 主要就关注其中manage_workers()方法

很明显调用了spawn_workers()方法,这个方式只是遍历了spawn_worker()。因此我们直接看spawn_worker()方法


可以发现 初始化了一个workclass,这个worker是gunicorn/workers/sync。为什么是使用这个因为在gunicorn/config中的worker_class默认使用的SyncWorker

 

 好了接下来我们说一下worker,这些worker本质上都是为了处理一个一个request的

 看到respiter = self.wsgi(environ, resp.start_response)这个了吗,这就是gunicorn符合wsgi的内涵。这个吧当前的环境和一个response传进去,当然传出了的也是response。

我们看看这个方法是那里呢?

寻找了一下是在生成worker的时候传入的app中获取的。

 那么我们来看一下这个app到底是什么东西,在gunicorn中的Arbiter初始化函数中有一句

 

这个app本质就是从外部初始化传进来的,寻找这个类常初始化的地方gunicorn/app/base中

 感情把自己的实例传进去了,不过在这里也找到了wsgi()方法

这个load()方法是子类gunicorn/app/wsgiapp中实现的

最后看一下load_wsgiapp()

 他的意思就是把你在命令中的app实例导入。这里最后提一下在flask中的实现,这个是FLASK类在flask/app.py中


因为这个类实现了__call__方法所以使用实例传入参数以后就来到了flask框架进行request处理。

————————————————
参考:https://blog.csdn.net/yj1499945/article/details/79040099

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐