前言:

去年10月份建了一个python技术群,到现在为止人数已经涨到700人了。最一开始我经常在群里回应大家的问题,不管是简单还是困难的,我都会根据自己的经验来交流。 让人新奇的是一些初学者关注最多的话题不是怎么学好python,反而是高并发,高性能这类高大上的话题。

记得有次几个不懂网络io、io多路复用含义网友,居然在群里吵了有半个小时,说出来的理论实在是让人哭笑不得。 群里当然有人在反驳,后来越聊越欢。 群里不少人在问我 uwsgi、gevent、tornado的一些设计,先前我尽量详细的作答,后来发现问我这些问题的朋友,并没有实际网络编程的经验,而我的回答更偏重于底层的实现,其实这样的解答不利于别人的理解的。我常常自己也在想 如何更好的回答别人的问题,一方面让人听着好懂,另一方面不让人觉得是在装逼。

没有人喜欢被别人说装逼,我同样也不喜欢,我是个谦虚善良的人。 我现在算是找到了自己的路数,我在回答问题的时候,习惯性的问别人几个问题,这样能把握好对方的深浅。 记得以前在腾讯的时候,名叫 院长 的前辈总是跟我说一句话,没有场景瞎谈高并发流氓行为。 胡粗理不粗呀,要避免自己纸上谈兵。

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新.     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
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
1
2

/usr/local/bin/uwsgi  --gevent500--gevent-monkey-patch  --http127.0.0.1:5000  --callableapp--wsgi-fileapp.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。

Flask is a microframework for Python based on Werkzeug, Jinja 2 and good intentions. And before you ask: It’s BSD licensed!

以前讲过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/。

简单说作为 wsgi server 他的意义在于 让我们专心去写web application,而不用专注于网络底层实现。 我们拿 flask 的Werkzeug来说,Werkzeug使用 Thread Local来实现的,所以才会有flask.request 、 flask.g 、 flask.session 这么便利的模块。引用的时候就像使用单例对象一样,但实际上对它们的所有赋值操作都只会影响到当前请求(当前线程),另外生存的周期也仅仅是这次请求而已。Thread Local 模式的实现一定要有一个 Thread Identity 作为标识。  为了测试我写一个测试发包程序,一个完整的http请求报文被切分成好几份,特意缓慢的发给Werkzeug服务端,直到结束,期间另开几个线程用来不断模拟正常的并发请求访问,可以成功获取结果。  接着我们在Flask的业务逻辑里加入io阻塞,发现这时候阻塞了。   通过不断strace追查查明,Werkzeug 在接收http请求和返回response结果的时候是异步非阻塞的。 随着我们单步调试的数据报文的到达,可以看到epoll wait由阻塞变为成功返回。

我通过阅读gunicorn、uwsgi 的代码得知,他们在单进程单线程下是和Werkzeug一样的。默认情况下,gunicorn会异步非阻塞的积攒tcp报文,通过http协议来解决tcp粘包的问题,当构建出一个完整http包,才会让这些worker来处理下一步的逻辑,也就是业务逻辑。  到此为止,我已经解释了 flask 使用Werkzeug epoll还是会发生阻塞的原因,也解释情况了gunicorn、uwsgi如何处理http请求 。

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

Python
1
2
3
4
5
6
7

# xiaorui.cc
[program:test]
command=/usr/bin/gunicorn-w16app:app-blocalhost:8100
timeout=60*60
backlog=10000

Uwsgi、gunicorn 应该如何做选择?  这里需要注意的是 uwsgi http模式一定要慎用,这个rep req模式实在是奇葩呀。试想一想,linux做请求发起时,往大里说一共可以创建65535-1000的连接。Nginx通常是跟uwsgi一台服务器的,那么nginx收到一个请求时,会主动跟uwsgi建立连接,然后uwsgi跟worker又发起连接,那么连接数降到3w的理论值 。  又因为uwsgi转发了一个请求造成时间消耗。

uwsgi的功能是要比gunicorn丰富的多,通过丰富的配置参数就知道了。 但根据我几个项目的线上测试结果,gunicorn要比uwsgi稳定。 单笔性能的化,gevent性能是最好的。 推荐的配置是 unix domain socket、多进程、gevent协程池 组合。 线程池的方式不太推荐使用,pyhton的线程是内核的pthread线程,在繁多的线程数目下,对比协程的消耗可想而知。

后面我会依次分析下 uwsgi、gunicorn实现的源码,你会发现这些实现还是很精妙的。

深入理解uwsgi和gunicorn网络模型相关推荐

  1. 关于 WSGI、uwsgi和uWSGI的区别及基于 uWSGI和gunicorn 部署python web 简述

    关于 WSGI.uwsgi和uWSGI的区别及基于 uWSGI和gunicorn 部署python web 简述 引言 最近基于 Flask Web框架在开发一个后端项目,在Web Server和 F ...

  2. 使用uwsgi和gunicorn部署Django项目

    https://uwsgi-docs.readthedocs.io/en/latest/Management.html https://uwsgi-docs.readthedocs.io/en/lat ...

  3. uwsgi模式_nginx+uwsgi 和nginx+gunicorn区别、如何部署

    [线上环境部署Django,nginx+uwsgi 和nginx+gunicorn,这两种方案,应该如何选择?] 大家是采用的何种部署方式? 第一种,高并发稳定一点 我们公司使用的是nginx+gun ...

  4. python uwsgi_python Web开发你要理解的WSGI uwsgi详解

    WSGI协议 首先弄清下面几个概念: WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web ...

  5. 学python开发必须要会wsgi么_python Web开发你要理解的WSGI uwsgi详解

    WSGI协议 首先弄清下面几个概念: WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web ...

  6. 高性能框架gevent和gunicorn在web上的应用及性能测试

    比如 Flask,webpy,Django.CherryPy 都带着 WSGI server .当然性能都不好,自带的web server 更多的是测试用途, 线上发布时,则使用高性能的 wsgi s ...

  7. [原创]智能化运维平台部署(gunicorn+nginx+gevent+supervisor部署flask+vue)

    2019年6月13日 前言 简要介绍一下,为小白普及基础知识,老司机请自行略过~ Flask本身带着 WSGI server,但是性能差强人意,自带的web server 更多的是测试用途.线上发布时 ...

  8. Kubernetes容器网络及网络模型

    1.Docker 网络模型 在讨论Kubernetes网络之前,让我们先来看一下Docker网络.Docker采用插件化的网络模式,默认提供bridge.host.none.overlay.macla ...

  9. ICLR 2022 | 香侬科技提出基于图神经网络的语义理解模型,获单项满分

    ©作者 | 香侬科技 来源 | 香侬科技 近日,深度学习国际顶级会议ICLR 2022向作者公布了论文录用结果.香侬科技3篇论文被大会收录,研究内容覆盖通用自然语义理解.NLP神经网络模型安全性.自动 ...

  10. 理解K8S的编排和网络

    0. K8s简介 Kubernetes 这个单词是希腊语,它的中文翻译是"舵手".我们知道,container 这个英文单词也有另外的一个意思就是"集装箱".K ...

最新文章

  1. python 在字典插入值和修改字典value值的方法
  2. Wordpress: contact form 7 表单内容同行
  3. 原生php开发cms,海洋cms视频内容管理系统v10.3 原生PHP代码+影视管理系统+视频点播系统+开源CMS...
  4. 四年级计算机期末质量分析,信息技术四年级(下)质量分析——刘千慧
  5. mysql left join 结果怎么这么慢
  6. 每日一笑 | 程序员和产品经理打架了,怎么办?
  7. [笔试面试题] 8-面向对象篇
  8. java 中的instanceof
  9. 2015计算机二级java真题_2015年计算机二级考试java试题
  10. 第一篇博客 记录自己对spring bean的理解
  11. 小白的学习笔记 —— React环境构建 常用语法
  12. 都2021年了,不会还有人连深度学习都不了解吧(七)- 评估指标篇
  13. Java项目实战--健康管理系统设计实现【Springboot+mybatis+layui等实现】
  14. Android 开发日志之LBS相关开发
  15. 2019大裁员!年关将至,最高裁员比例竟达90%?
  16. self paced learning(自步学习)
  17. 啦啦啦啦啦 报道 报道
  18. qt textbrowser的边界框怎样改变颜色_专访天使投资人续沛川:用深度思考打破人生边界,拥有张力一生...
  19. 线性规划 - 数学建模
  20. 加载繁体字体时遇到的问题

热门文章

  1. Python3实现简易的学生选课系统
  2. linux清除占用端口,Linux中解除端口占用的方法
  3. 给android开发者的rx,给Android开发者的RxJava 详解,解你多年困扰!
  4. wp 一次简单的攻防训练
  5. 华为设备如何将接口配置为中继模式_(完整版)华为设备基本配置命令
  6. [EMNLP2017]Global Normalization of Convolutional Neural Networks for Joint Entity and Relation(阅读笔记)
  7. Flutter状态管理学习手册(三)——Bloc
  8. 使用 data URI scheme 在网页中内嵌图片[转]
  9. C++ STL 整理
  10. 【小窍门tip】oracle sequence 修改增量值