剖析 TCP - SO_REUSEPORT 使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剖析 TCP - SO_REUSEPORT 使用相关的知识,希望对你有一定的参考价值。

参考技术A 在 TCP 应用中,SO_REUSEPORT 是 TCP 的一个选项设置,它能开启内核功能:网络连接分配负载均衡。

该功能允许多个进程/线程 bind/listen 相同的 IP/PORT,提升了新连接的分配性能。

nginx 开启 reuseport 功能后,性能有立竿见影的提升,我们结合 nginx 分析一下 reuseport 功能。

从下面这段英文提取一些关键信息:

SO_REUSEPORT 是网络的一个选项设置,它允许多个进程/线程 bind/listen 相同的 IP/PORT,在 TCP 的应用中,它是一个新连接分发的负载均衡功能,它提升了新连接的分配性能(针对 accept )。

SO_REUSEPORT 功能使用,可以通过网络选项进行设置,在 bind 前面设置即可,使用比较简单。

SO_REUSEPORT 功能解决了什么问题?我们先看看 2013 年提交的这个 Linux 内核功能 补丁 的注释。

soreuseport 主要解决了两个问题:

其实它还解决了一个很重要的问题:

在 tcp 多线程场景中,(B 图)服务端如果所有新连接只保存在一个 listen socket 的全连接队列中,那么多个线程去这个队里获取(accept)新的连接,势必会出现多个线程对一个公共资源的争抢,争抢过程中,大量资源的损耗。

(C 图)有多个 listener 共同 bind/listen 相同的 IP/PORT,也就是说每个进程/线程有一个独立的 listener,相当于每个进程/线程独享一个 listener 的全连接队列,不需要多个进程/线程竞争某个公共资源,能充分利用多核,减少竞争的资源消耗,效率自然提高了。

TCP 客户端连接服务端,第一次握手,服务端被动收到第一次握手 SYN 包,内核就通过哈希算法,将客户端的连接分派到内核半连接队列,三次握手成功后,再将这个连接从半连接队列移动到某个 listener 的全连接队列中,提供 accept 获取。

nginx 是多进程架构模型,在内核还没有添加 reuseport 功能前,nginx 为了解决单个 listener 暴露出来的问题,花了不少心思。

2013 年 Linux 内核添加了 reuseport 功能后,nginx 在 2015 年,1.9.1 版本也增加对应功能的支持,nginx 开启 reuseport 功能后,性能是原来的 2-3 倍,效果可谓立竿见影!

接下来我们看看 nginx 是如何支持 reuseport 的。

修改 nginx 配置,在 nginx.conf 里,listen 关键字后面添加 'reuseport'。

启动测试 nginx,1 master / 4 workers,监听 80 端口。

nginx 是多进程模型,Linux 环境下一般使用 epoll 事件驱动。

可以在Unix域套接字上使用SO_REUSEPORT吗?

Linux内核> = 3.9允许通过设置SO_REUSEPORT来共享内核负载平衡进程之间的套接字:http://lwn.net/Articles/542629/

如何将它用于AF_UNIX类型的插座?

看来,它只适用于TCP,而不适用于Unix域套接字。

这是一个Python测试程序:

import os
import socket

if not hasattr(socket, 'SO_REUSEPORT'):
   socket.SO_REUSEPORT = 15

if True:
   # using TCP sockets
   # works. test with: "echo data | nc localhost 8888"
   s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
   s.bind(('', 8888))
else:
   # using Unix domain sockets
   # does NOT work. test with: "echo data | nc -U /tmp/socket1"
   s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
   s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
   try:
      os.unlink("/tmp/socket1")
   except:
      pass
   s.bind("/tmp/socket1")

s.listen(1)
while True:
   conn, addr = s.accept()
   print('Connected to {}'.format(os.getpid()))
   data = conn.recv(1024)
   conn.send(data)
   conn.close()

启动2个实例,并通过多次运行以下测试:

  • 用于TCP的echo data | nc localhost 8888
  • echo data | nc -U /tmp/socket1用于Unix域套接字

使用TCP时,传入的客户端将与两台服务器保持平衡。使用Unix域套接字,传入的客户端都连接到最后启动的服务器。

答案

这个特定的内核补丁在这里记录:

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=c617f398edd4db2b8567a28e899a88f8f574798d

从修补文件列表中可以看出,补丁仅影响了net/ipv4net/ipv6套接字。 Unix域套接字在net/unix中实现。所以,答案是:不,SO_REUSEPORT不适用于AF_UNIX类型的套接字。

另一答案

一个小补丁,在UNIX套接字SO_REUSEPORT上增加了对was posted的支持,但是被拒绝了。但是这个补丁并没有在多个套接字上实现负载均衡,如果套接字文件已经存在,它只会导致bind()不会失败。

这个用例被认为是

从用户的角度来看,这是一个非常奇怪的角落案例

因此,仍有可能通过SO_REUSEPORT实现UNIX套接字负载平衡的不同补丁将被接受。

以上是关于剖析 TCP - SO_REUSEPORT 使用的主要内容,如果未能解决你的问题,请参考以下文章

可以在Unix域套接字上使用SO_REUSEPORT吗?

SO_REUSEADDR和SO_REUSEPORT区别

电子书 深入剖析tomcat.pdf

SO_REUSEPORT 和 SO_REUSEADDR

TCP 连接断连问题剖析

setsockopt() 的 macOS SO_REUSEPORT