深入理解uwsgi和gunicorn网络模型

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解uwsgi和gunicorn网络模型相关的知识,希望对你有一定的参考价值。

参考技术A 前言:

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=4264

上面有点偏远这次的话题,这次主要讲解 uwsgi 、gunicorn的网络方面的设计。我会围绕下面几个问题讲解uwsgi、gunicorn的设计。

uwsgi 、gunicorn 有啥区别?

uwsgi、gunicorn的Master Worker进程模型?

有这么多worker模型,我们应该怎么选择?

uwsgi、gunicorn作为网关角色的意义?

这类框架怎么组合性能最高 ?

在架构上是这样的,nginx负责动态的转发和静态文件的直接访问,gunicorn和uwsgi作为网关服务用来解析http请求,后面的flask只是个application而已,没有server的服务特征。

先说简单干练的gunicorn讲起吧,下面是gunicorn的启动方式:

Python

<textarea class="crayon-plain print-no" readonly="readonly" cols="22" rows="3" style="box-sizing: border-box; outline: none; border: 0px; resize: vertical; overflow: hidden; padding: 0px; margin: 0px; overflow-wrap: break-word; vertical-align: top; opacity: 0; color: rgb(0, 0, 0); z-index: 0; font-family: Monaco, MonacoRegular, "Courier New", monospace !important; font-size: 12px !important; line-height: 15px !important;"></textarea>

|

1

2

|

gunicorn -w 3 -b 127.0.0.1:5000 app:app -k gevent

|

通过strace得知 gunicorn 默认的网络模型是 select , 当我们worker 替换成 gevent 后, 改为 epoll 监听模型 . select 和 epoll之间的区别我们就不再啰嗦了。

下面是gunicorn 、uwsgi 的 Master Worker的模型,大体实现是这样的。

如果我们的app是flask写得,那么用gevent做worker的意义在于什么?

Gevent worker 它提供了一种机制,让你可以监听到多个事件,epoll wait调用是阻塞的,但是可以设置超时事件,在超时事件内,如果有事件准备好就返回。比如采用epoll事件处理模型,当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,当所有fd没有发生读写时,epoll才会阻塞等待。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。工作流之间会产生切换的,但这里的切换消耗远没有多线程上下文切换大。

gunicorn根据Master Worker来fork出子进程,Master在这里不用做处理对外的http请求,而用来管理这些子进程,比如 升级、重载配置、kill进程避免oom 等。这些worker(子进程)继承了主进程的listening fd,这时候从accept、parse http protocol、response 都是在一个gevent协程里面的,也就说 在协程池的数目允许下,每个连接就是一个gevent协程。 如果你的app的业务逻辑是阻塞模式的,又没兼容gevent的patch,那么可想而知,结果是同步阻塞了。

gunicorn框架对外服务的模式下有http、tcp socket和unix domain socket,这跟uwsgi的模式一样一样的。

对于高并发的场景下,如果支持unix domain socket 模式,最少可以省略tcp的计算校验,这样性能有不少的提升。gunicorn wsgi相比uwsgi的协议相比,可以使传输的协议层更加的紧凑。

下面是uwsgi的启动方式:

Python

<textarea class="crayon-plain print-no" readonly="readonly" cols="22" rows="3" style="box-sizing: border-box; outline: none; border: 0px; resize: vertical; overflow: hidden; padding: 0px; margin: 0px; overflow-wrap: break-word; vertical-align: top; opacity: 0; color: rgb(0, 0, 0); z-index: 0; font-family: Monaco, MonacoRegular, "Courier New", monospace !important; font-size: 12px !important; line-height: 15px !important;"></textarea>

|

1

2

|

/usr/local/bin/uwsgi --gevent 500 --gevent-monkey-patch --http 127.0.0.1:5000 --callable app --wsgi-file app.py --http-keepalive --master

|

上面是 uwsgi http 服务模式,但是uwsgi会启动两组端口port, 一个是5000 ,一个是5300x , 端口5000是我们已知的,这个端口用来直接对外接收请求的,他在构建完一个请求协议包之后,会connect 到 5300x 端口, 平白的多消耗了一些网络io。这种模式是 rep req模型,我能想到的优点是,他避免了因为listen fd事件的到来把其他进程唤醒的问题。 也就是说,只有5000对外,5300x是真正的worker。 端口5000根据一定的算法来选择worker。 5000 和 5300x的数据交互方式是 可压缩可序列化的tcp报文,有兴趣的可以抓包看看。

在内核2.6就早没有accept惊群这个说法了,但是当我们多个进程各自把listen fd放到epoll监听池里面时,其实会造成事件的唤醒,虽然最终只会被一次accept,但平白无故唤醒了多个进程也不是值得骄傲的。

题外话,nginx是通过多个进程轮流持锁的方式来避免epoll accept唤醒问题。

下面是 pull req 模型.

改成 –socket :5000 , 只会监听5000 port , 因为uwsgi协议比较特殊,测试起来很是麻烦。 我这里开源了一个uwsgi客户端。uwsgi client http://xiaorui.cc/?p=4205

改成 –socket /path/to/xiaorui.cc.sock ,线上经验表明 unix domain socket 模式要比tcp socket性能有提升的。

uwsgi 和 gunicorn 是长连接么? 怎么测试uwsgi的长连接 ? uwsgi 长连接实现方法?

gunicorn是长连接的,uwsgi要启用 –http-keepalive 模式才是长连接请求。 不要用curl测试,因为当你curl关闭的时候,已经出发了tcp四次挥手。 你可以根据strace和tcpdump来分析,在curl获取打印数据后,会发起close请求。 正确的测试方法是,你写个python requests请求,当请求完毕后,不要急着退出脚本,加一个sleep等待后再次去请求。 我们会发现连接始终是一个,tcpdump没有抓到建立新连接的报文。 uwsgi、gunicorn如何实现的长连接 ? 不只是在 返回的http加入 Connection:keep-alive 字段就标明是长连接,还需要借助select、epoll这样的io多路复用模型,用来监听各个fd读写事件。 简单说只要server不主动去close(),客户端client也不去close(),既然没有人去close(),这个连接自然就是长连接了,反之就是短连接。

flask 是长连接么? 我负责的说 是,长连接。既然长连接是借助select、epoll模型来实现,那么为毛flask是阻塞模式,随意加一个 time.sleep(xxx) 就io阻塞了。

这是Python flask的框架介绍… Werkzeug 是 Flask的wsgi server ,gunicorn 跟 flask做结合时,gunicorn可以理解为是 flask 的wsgi server。

以前讲过wsgi server的设计实现,这里就不多扯淡了,有兴趣的可以看看该文 http://xiaorui.cc/2016/04/16/%E6%89%93%E9%80%A0mvc%E6%A1%86%E6%9E%B6%E4%B9%8Bwsgi%E5%8D%8F%E8%AE%AE%E7%9A%84%E4%BC%98%E7%BC%BA%E7%82%B9%E5%8F%8A%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0/ 。

gunicorn、uwsgi遇到普遍的问题是502 504问题, 一说到502 ,我们知道后端处理过慢需要扩展worker,一说到504,我们知道处理超时,一般调整timeout就可以。那么502,504该问题的根本原因是什么? socket 内部是有两个队列,一个syn队列,一个是accept队列,这两个队列都在accept()之间就有了。 backlog是syn和accept队列之和,当你后端处理不及时,backlog又到限制时,会出现502,也就是说新的客户端不能建立,因为没有syn的槽位供你三次握手。 504 就很好理解了,处理超时,中断处理,直接范围错误信息。

Python

<textarea class="crayon-plain print-no" readonly="readonly" cols="22" rows="3" style="box-sizing: border-box; outline: none; border: 0px; resize: vertical; overflow: hidden; padding: 0px; margin: 0px; overflow-wrap: break-word; vertical-align: top; color: rgb(0, 0, 0); z-index: 0; font-family: Monaco, MonacoRegular, "Courier New", monospace !important; font-size: 12px !important; line-height: 15px !important;"></textarea>

|

1

2

3

4

5

6

7

|

[program:test]

command = /usr/bin/gunicorn -w 16 app:app -b localhost:8100

timeout = 60*60

backlog = 10000

|

转自: https://blog.csdn.net/shudaqi2010/article/details/71172683

以上是关于深入理解uwsgi和gunicorn网络模型的主要内容,如果未能解决你的问题,请参考以下文章

深入理解计算机系统 第十一章 网络编程

深入浅出-网络七层模型

深入理解卷积与模型大小问题,解决显存不足

uWSGI和Gunicorn

nginx+uwsgi 和nginx+gunicorn区别、如何部署

用gunicorn+gevent启动Flask项目