Nginx Plus Uwsgi

Date: 2019/05/03 Categories: 工作 Tags: uwsgi nginx



uwsgi是python应用服务器, 支持多进程,多线程和协程。

uwsgi自带的http反向代理在使用中出现了负载不均匀的问题,现象是最忙的进程处理了 大多数请求,其他进程几乎闲置,导致延迟不稳定。

经过调研,原因如下

  1. 打开了gevent协程,每个进程内部有多个协程,在负载不是特别高的情况下这些协程不会全部阻塞。 因此最忙的进程总能处理队列中新的请求。
  2. uwsgi的woker进程需要和worker进程通信,方式是epoll监听同一个fd,linux比较老的版本(<4.5) epoll实现会唤醒所有的进程,导致惊群。 同时epoll唤醒的顺序并不是round robin而是总会唤醒队列里最后一个, 导致负载不均匀。

解决方案有:

  1. 每个worker监听一个socket,然后用nginx做负载均衡.这种方案比较粗暴,而且nginx自身 也会有epoll带来的负载不均匀的问题。但在实际中,nginx作为反向代理,每个请求处理时间很短,不是大问题。
  2. 开启uwsgi自带的--thunderlock --lock-engine ipcsem选项,Sysv Semaphore可以解决惊群,同时避免多个进程同时调用epoll导致的问题。

如果是自己写网络编程代码,最好的方式是使用share-nothing方式,每个线程都监听自己的 fd,或者使用较新内核(>4.5)+EPOLLEXCLUSIVE选项来调用epoll(2)

Reference

  1. Serializing accept(), AKA Thundering Herd, AKA the Zeeg Problem, 这篇uwsgi文档解释了thunder-lock的由来和惊群的解决方法
  2. Thread Pools in NGINX Boost Performance 9x!, 来自nginx博客,nginx基本上是event-driven + multi-process,线程池是为了解决事件驱动模型里阻塞操作的延迟问题, 我们的服务目前是multi-process + gevent模型,也可以考虑引入线程池解决一些长时间的阻塞操作,比如磁盘访问
  3. Epoll is fundamentally broken ,解释了epool(2)为什么很容易用错
  4. 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。 低负载的时候本来也不需要这些。所以还是各有优缺点。
  5. epoll: add EPOLLEXCLUSIVE flag