最近在nginx1.9.1中支持了reuse_port这个功能 是准许多个socket监听同一个端口?

最近在nginx1.9.1中支持了reuse_port这个功能 ,是准许多个socket监听同一个端口的选项。(在很多情况下就是允许多个进程/线程监听同一端口)。 但这个对于nginx这样的单master多worker的服务器来说,已经通过master的fork调用,让多个worker都监听了同一个端口,这样的话,这个选项还有意义么?或者说它在哪些情况下,对nginx有意义呢?
关注者
76
被浏览
2630

5 个回答

这个我刚好知道,这是Linux内核新支持得一个TCP选项,这样不需要fork也可以在不同进程中监听同个端口,对nginx来说有什么用我就不清楚了,也的确想不出什么实际的例子……对其他应用来说,可以不需要实现fork-server就能支持多进程模式了。
能想到的就是在需要服务重启的时候,可以先把新的实例启动起来,再退出旧实例,保证服务不中断?但是这个以前也只要通过某种方式通知旧的实例停止listen就行了。
我来回答一下自己的问题,抛砖引玉吧

先描述下Nginx的网络模型
1 nginx的master进程创建一系列的监听套接字(比如需要监听不同的端口,80,443等),下面都以监听80端口为例来说明。

2 fork出多个worker进程,这些worker进程继承了监听套接字。

3 而这种多进程监听同一端口的模型会有惊群现象(先不讨论内核是否解决了惊群,nginx作为一个跨平台的server,从自身解决了惊群),即监听套接字上有请求到来时,内核会唤醒所有的进程。
4 在linux平台下采用epoll网络事件驱动,每个worker创建自己的epoll。

5 Nginx的惊群解决手段: 如果有多个worker进程,且开启了accept_mutux 锁(默认开启)
这时候每个worker不会将监听套接字加入到自己的epoll中,而是会去抢一把自旋锁,即对监听套接字“权力”,抢到的worker进程会将监听套接字加入自己的epoll中,accept新请求,然后释放锁。
所以,如果没有这种强锁机制,每个worker的epoll中都会监视监听套接字,这样每次请求到来时,每个worker都会被唤醒,而最终accept这个请求的只能有一个,其它的worker的唤醒是浪费的。

应该说在低并发情况下,这种处理机制会很好地提升cpu效率。
现在很多实例证明:在并发很高的情况下,nginx这种处理惊群的机制会导致处理效率的下降,所以现在很多建议是关闭accept_mutux锁,这样每个worker中的epoll中都会监视监听套接字。
幸运的是,从nginx的高版本开始,这个锁默认是已经关闭的了。

为什么在高并发的情况下,关闭锁导致使惊群现象产生,也会提升性能呢?网上有个很形象的例子,这里我借用下:
试想,有一群鸡(是真正的鸡),你撒谷粒给这群鸡吃。
a,一粒粒撒的时候,如果不加处理,每个鸡都会跳起来,但最终只有一只鸡能够吃到这粒米,
所以在一粒粒撒的时候,需要有锁,不能让每个鸡都跳起来,这样浪费它们的精力,必须要让它们遵守秩序,一个个来(加锁)
b,然而,如果你撒了一大把谷粒,这时候还让它们一个个来,岂不是很不合理,所以,在撒大把谷粒的情况下,这些鸡全部跳起来抢食才是科学的,这样才能更加快速地消耗掉这些谷粒。(不加锁)。

上面的例子虽然有些粗糙,但是很形象。

6 回到我们的话题SO_REUSEPORT这个选项,官方在nginx.19.1中支持这个,且经过测试,开启这个选项,会使得的nginx的性能提升3倍。具体测试可见:Socket Sharding in NGINX OSS Release 1.9.1

他们比较了三种情况,常规情况(开启锁),关闭锁,和开启SO_REUSEPORT。

对于关闭锁比开启锁提升性能,这个好理解。但SO_REUSEPORT这个。就是我的疑问了。


7 我的疑问是这样的,通过master fork出多个worker,这些worker都是共同监听了同一个端口,
那么reuse_port这个,根据man手册描述,是让多个套接字共同监听同一个端口。
但是master对于一个端口只创建了一个套接字,哪里来的多个套接字呢?从那个测试的图来看,好像在文件系统中对同一个端口关联了多个套接字。。。这科学么?


8 如果内核真如测试的图中描述的那样,对于每个worker都有自己的监听套接字,且这些监听套接字都bind同一个端口,这样,当请求来时,内核会负责将这些请求均匀分配到不同的监听套接字,这样看来 确实会提升性能。

但问题的关键是,对于同一个端口来说,这些不同的套接字是哪里来的呢??