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