Linux 3.9 加入了 SO_REUSEPORT 选项,可以提高 UDP 和 TCP server 的伸缩性,Linux 4.5/4.6 分别进一步改进了 UDP 和 TCP 的 SO_REUSEPORT 实现。本文以 UDP 的实现为例来讲解,TCP 与之类似。
UDP 协议的主要数据结构是两张 hash 表,指向 UDP 协议控制块 struct udp_sock。其中 hash 以 port 为 key,hash2 以 IP+port 为 key。
这两张 hash 表的大小可以从 dmesg 中读到,在陈硕家的一台机器上,两个表各有 32K 个 buckets:
[ 0.356008] UDP hash table entries: 32768 (order: 8, 1048576 bytes)
在收到 UDP datagram 之后,从 hash 表中找到对应的 udp_sock,代码位于 net/ipv4/udp.c : __udp4_lib_lookup,再把 datagram 放到 udp_sock 的接收队列中。
在启用 SO_REUSEPORT 之后,相同 port 的 udp_sock 会加入同一个 struct sock_reuseport 对象。在收到 UDP datagram 之后,先找到任何一个 udp_sock,再找到对应的 sock_reuseport,然后根据地址四元组的哈希值来选择由哪个 udp_sock 处理,代码见 __udp4_lib_lookup/reuseprt_select_sock。下图是两个 udp_sock bind 到同一个 port 的情况:
如果有 N 个 udp_sock,来自于多个客户端的 UDP datagram 会被均匀地分配给这些 udp_sock 处理,同一个客户端的数据总是分配给同一个 udp_sock。我们在写 UDP server 的时候,为了提高处理能力,可以起多个线程,每个线程读写自己的 UDP socket,这样比多个线程读写同一个 UDP socket 要少很多 contention。(值得一提的是,通过 dup(2) 复制 UDP socket 达不到 SO_REUSEPORT 的效果,因为这些 fd 会指向同一个 udp_sock,不会减少 contention。)
commit e32ea7e747271a0abcd37e265005e97cc81d9df5
Author: Craig Gallek <kraig@google.com>
Date: Mon Jan 4 17:41:46 2016 -0500
soreuseport: fast reuseport UDP socket selection
Include a struct sock_reuseport instance when a UDP socket binds to
a specific address for the first time with the reuseport flag set.
When selecting a socket for an incoming UDP packet, use the information
available in sock_reuseport if present.
This required adding an additional field to the UDP source address
equality function to differentiate between exact and wildcard matches.
The original use case allowed wildcard matches when checking for
existing port uses during bind. The new use case of adding a socket
to a reuseport group requires exact address matching.
Performance test (using a machine with 2 CPU sockets and a total of
48 cores): Create reuseport groups of varying size. Use one socket
from this group per user thread (pinning each thread to a different
core) calling recvmmsg in a tight loop. Record number of messages
received per second while saturating a 10G link.
10 sockets: 18% increase (~2.8M -> 3.3M pkts/s)
20 sockets: 14% increase (~2.9M -> 3.3M pkts/s)
40 sockets: 13% increase (~3.0M -> 3.4M pkts/s)
This work is based off a similar implementation written by
Ying Cai <ycai@google.com> for implementing policy-based reuseport
selection.
Signed-off-by: Craig Gallek <kraig@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>