Nginx Plus Uwsgi
Date: 2019/05/03 Categories: 工作 Tags: uwsgi nginx
uwsgi是python应用服务器, 支持多进程,多线程和协程。
uwsgi自带的http反向代理在使用中出现了负载不均匀的问题,现象是最忙的进程处理了 大多数请求,其他进程几乎闲置,导致延迟不稳定。
经过调研,原因如下
- 打开了gevent协程,每个进程内部有多个协程,在负载不是特别高的情况下这些协程不会全部阻塞。 因此最忙的进程总能处理队列中新的请求。
- uwsgi的woker进程需要和worker进程通信,方式是epoll监听同一个fd,linux比较老的版本(<4.5) epoll实现会唤醒所有的进程,导致惊群。 同时epoll唤醒的顺序并不是round robin而是总会唤醒队列里最后一个, 导致负载不均匀。
解决方案有:
- 每个worker监听一个socket,然后用nginx做负载均衡.这种方案比较粗暴,而且nginx自身 也会有epoll带来的负载不均匀的问题。但在实际中,nginx作为反向代理,每个请求处理时间很短,不是大问题。
- 开启uwsgi自带的
--thunderlock --lock-engine ipcsem
选项,Sysv Semaphore可以解决惊群,同时避免多个进程同时调用epoll导致的问题。
如果是自己写网络编程代码,最好的方式是使用share-nothing方式,每个线程都监听自己的
fd,或者使用较新内核(>4.5)+EPOLLEXCLUSIVE
选项来调用epoll(2)
Reference
- Serializing accept(), AKA Thundering Herd, AKA the Zeeg Problem, 这篇uwsgi文档解释了thunder-lock的由来和惊群的解决方法
- Thread Pools in NGINX Boost Performance 9x!, 来自nginx博客,nginx基本上是event-driven + multi-process,线程池是为了解决事件驱动模型里阻塞操作的延迟问题, 我们的服务目前是multi-process + gevent模型,也可以考虑引入线程池解决一些长时间的阻塞操作,比如磁盘访问
- Epoll is fundamentally broken ,解释了
epool(2)
为什么很容易用错 - Why does one NGINX worker take all the
load?
来自cloudflare,解释了为什么总是第一个worker
accept新连接,原因也是epoll唤醒顺序。看来nginx面临同样的问题,但因为nginx作为reverse
proxy一般负载比较低,这个问题不是很严重。他们给出的解决方案是
SO_REUSEPORT
, 这个方案在高负载的时候会影响平均延时,每个进程有独立的queue,如果这个进程阻塞了,这条queue里的请求延迟都会升高,如果不用SO_REUSEPORT
是我们所有的worker都可以处理同一条queue。 低负载的时候本来也不需要这些。所以还是各有优缺点。 - epoll: add EPOLLEXCLUSIVE flag