Linux 4.5/4.6 中对 SO_REUSEPORT 的改进

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。

 

Linux 4.5/4.6 中对 SO_REUSEPORT 的改进

这两张 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 的情况:

 

Linux 4.5/4.6 中对 SO_REUSEPORT 的改进

如果有 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>
上一篇:Deploying Portainer CE in Docker


下一篇:Python 网络编程(聊天室的基础实现)