1、Nginx reuseport导致偶发性卡顿
2、Why does one NGINX worker take all the load?
3、The SO_REUSEPORT socket option
开启so_reuseport的多个tcp/udp套接字可以绑定相同的套接字地址。
4、 SO_REUSEPORT man-page
5、soreuseport: Bind multiple sockets to the same port
6、惊群解决EPOLLEXCLUSIVE、REUSEPORT方案 .
在每个worker进程有各自的epoll对象,并监视同一个监听套接字上事件的情况下。当该监听套接字返回事件时, EPOLLEXCLUSIVE会找到并唤醒第一个处于休眠状态的worker进程来处理该事件。
EPOLLEXCLUSIVE惊群:当监听套接字上有读事件时遍历该套接字的等待队列项,调用每一个等待队列项的ep_poll_callback,ep_poll_callback会首先把事件添加到该epoll的就绪链表,但是当该epoll对应的worker进程处于运行状态时,会继续遍历等待队列项,直到找到一个休眠状态的worker进程。当唤醒该worker进程时,该事件可能已经被前面的处于运行状态的worker进程处理。
7、 再谈Linux epoll惊群问题的原因和reuseport解决方案
8、EPOLLEXCLUSIVE man-page
9、EPOLLEXCLUSIVE 条件:Linux 4.5, glibc 2.24
10、接收数据时有关的等待队列项
在同步阻塞模式下,socket的等待队列项是
wait_queue_t name = {
.private = current, # 当前进程ID
.func = autoremove_wake_function
}
当socket接收队列没有数据(足够)可读时,添加一个等待队列项到socket的等待队列中,并阻塞当前进程;当socket接收队列有数据可读时,调用autoremove_wake_function唤醒等待队列项中的进程来接收数据。
在epoll模式下,socket的等待队列项是
wait_queue_t {
.flag = 0
.private = NULL
.func = ep_poll_callback
}
当调用epoll_ctl 把socket上事件注册到epoll时,把该等待队列项添加到socket的等待队列,由于该等待队列项的作用不是唤醒当前进程,所有private为NULL;当socket接收队列有数据可读时,调用ep_poll_callback找到相应的epitem和epoll对象,把epitem添加到epoll对象的就绪链表中,epoll_wait返回读事件给用户进程。
eventpoll对象的等待队列项是
wait_queue_t {
.flags = WQ_FLAG_EXCLUSIVE
.private = current #当前进程
.func = default_wake_function
}
当epoll_wait没有返回事件时,添加一个等待队列项到eventpoll对象的等待队列中,并阻塞当前进程。当socket接收队列有数据可读时,调用default_wake_function唤醒进程,从epoll_wait阻塞时暂停的代码处继续执行。
11、reuseport解决Nginx惊群问题
Nginx每个worker进程都有一个epoll对象,每一个epoll对象的等待队列里只会有一个等待队列项,对应相应的阻塞worker进程。
当没有开启so_reuseport时,对于一个端口所有的worker进程共用一个监听套接字,每一个worker进程会把该监听套接字上的读事件注册到自己的epoll中,该监听套接字的等待队列也会有n(进程数量,每注册一次该套接字上的读事件,添加一个等待队列项)个等待队列项。当该监听套接字上有新连接来时,会遍历套接字的等待队列项,找到每一个进程监视的epoll,epoll会唤醒相应的worker进程,从而产生惊群问题。
当开启so_reuseport后,对于一个端口每个worker进程都有一个监听套接字,每个监听套接字的等待队列只有一个等待队列项。当有新连接来时,内核会通过四元组hash调度其中一个监听套接字来建立连接,只有一个相应的worker进程被唤醒。
12、udp套接字开了reuseport后,同一个会话的报文只会(由内核通过四元组hash调度)打到一个特定的udp套接字。