《动手测试单机百万连接》实验记录(一)

最近参考动手测试单机百万连接这篇文章的方案二做实验,中间碰到一些坑,记录如下:

实验介绍

  • 机器A:8核4G的虚拟机,ip是192.168.2.44,centos 7,内核3.10.0
  • 机器B:8核8G的物理机,ip是192.168.2.120,ubuntu 22.04,内核5.15.0
  • 机器B启20个tcp server,监听端口范围是8100~8119
  • 机器A启20个客户端进程,各自选择一个端口向机器B发起5万连接
  • 最终目标:机器A最终向机器B建立100万个tcp连接

问题一

此时两个机器的内核参数均做了如下修改:

sysctl -w net.ipv4.ip_local_port_range="5000 65000"
sysctl -w fs.file-max=1100000
sysctl -w fs.nr_open=1100000
sysctl -w net.core.somaxconn=40960
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_orphans=1000000
sysctl -w net.ipv4.tcp_max_syn_backlog=40960

ulimit -n 1000000 
echo -e "* hard nofile 1000000\n* soft nofile 1000000" >> /etc/security/limits.conf

失败现象:建立了约17577个连接后就卡住了,机器B上用dmesg|tail查到如下错误:

nf_conntrack: nf_conntrack: table full, dropping packet

此时查看机器B的连接跟踪数conntrack -C是65536,机器B的内核配置net.netfilter.nf_conntrack_max也是这个数

解决办法:把net.netfilter.nf_conntrack_max设置成6553600

问题二

解决问题一后,再次启动实验,发现可以轻松的很快建立10w多连接,但是随后达到20w连接的时候速度就变慢了,差不多每秒建立150连接,达到40w的时候每秒只能建立20个连接了,似乎遇到了性能瓶颈。我也曾经等待了一个晚上,最终也只能达到60多w的样子。

这个时候,我尝试在机器A上抓包tcpdump -i ens33 -n -nn host 192.168.2.120 and port 8100,看到的现象是机器A发个机器B的syn包,有的能立即收到syn+ack响应,有的需要发送若干次syn包才能收到syn+ack响应,有的在尝试发送6次syn包后宣告失败。在机器B上抓包tcpdump -n -nn host 192.168.2.44 and port 8100,现象同机器A。那么问题是为什么有时候机器B在收到syn包的时候会没有回应呢?

我尝试使用bpftrace工具来捕获丢包的场景,运行bpftrace kfree_skb.bt,kfree_skb.bt的源码如下:

kprobe:kfree_skb
{
        $skb = (struct sk_buff*) arg0;
        $ip_header = ((struct iphdr *) ($skb->head + $skb->network_header));
        // 这里只捕获 daddr 是 192.168.2.120 的包
        if ($ip_header->daddr == 0x7802a8c0)
        {
                $tcp_header= ((struct tcphdr *) ($skb->head + $skb->network_header + ($ip_header->ihl << 2)));
                $sport = $tcp_header->source;
                $dport = $tcp_header->dest;
                $dport = ($dport >> 8) | (($dport << 8) & 0xff00);
                $sport = ($sport >> 8) | (($sport << 8) & 0xff00);
                // 这里只捕获 dport 是 8100 的包
                if ($dport == 8100) {
                        time("%H:%M:%S ");
                        printf("%s:%d > %s:%d\n", ntop($ip_header->saddr), $sport, ntop($ip_header->daddr), $dport);
                }
        }
}

此时可以观察到,在内核在调用kfree_skb丢包的时候,saddr和sport正好是tcpdump上没有收到回应的syn包。

再用bpftrace -e 'kprobe:kfree_skb { @[kstack(15)] = count(); }'获取kfree_skb的上游调用链,得到结果如下:

kfree_skb+1
ip_local_deliver+209
ip_sublist_rcv_finish+103
ip_sublist_rcv+385
ip_list_rcv+245
__netif_receive_skb_list_core+531
netif_receive_skb_list_internal+398
napi_complete_done+122
e1000e_poll+203
__napi po1l+51
net_rx_action+294

再用./funcgraph ip_local_deliver获取ip_local_deliver下游调用链。funcgraph是基于内核ftrace技术的工具。当发生丢包时刻的调用栈如下:

 5)               |    ip_local_deliver() {
 5)   0.897 us    |      irq_enter_rcu();
 5)               |      __sysvec_irq_work() {
 5)               |        __wake_up() {
 5)               |          __wake_up_common_lock() {}
 5) + 46.952 us   |        }
 5) + 48.697 us   |      }
 5)               |      irq_exit_rcu() {
 5)   0.788 us    |        idle_cpu();
 5)   2.309 us    |      }
 5)               |      nf_hook_slow() {
 5)               |        nf_nat_ipv4_local_in [nf_nat]() {
 5)               |          nf_nat_inet_fn [nf_nat]() {
 5)               |            __nf_nat_alloc_null_binding [nf_nat]() {
 5)               |              nf_nat_setup_info [nf_nat]() {
 5)   0.732 us    |                nf_ct_invert_tuple [nf_conntrack]();
 5)               |                get_unique_tuple [nf_nat]() {
 5)   0.880 us    |                  in_range [nf_nat]();
 5)   0.764 us    |                  nf_ct_invert_tuple [nf_conntrack]();
 5)               |                  nf_conntrack_tuple_taken [nf_conntrack]() {
 5)   0.922 us    |                    hash_conntrack_raw [nf_conntrack]();
 5)   0.750 us    |                    rcu_read_unlock_strict();
 5)   4.408 us    |                  }
 5)   9.114 us    |                }
 5)   0.937 us    |                hash_by_src [nf_nat]();
 5)   0.816 us    |                _raw_spin_lock_bh();
 5)               |                _raw_spin_unlock_bh() {
 5)   0.660 us    |                  __local_bh_enable_ip();
 5)   2.132 us    |                }
 5)   0.741 us    |                irq_enter_rcu();
 5)               |                __sysvec_irq_work() {
 5)               |                  __wake_up() {
 5)               |                    __wake_up_common_lock() {
 5)   0.603 us    |                      _raw_spin_lock_irqsave();
 5)               |                      __wake_up_common() {
 5)               |                        autoremove_wake_function() {
 5)               |                          default_wake_function() {
 5)               |                            try_to_wake_up() {
 5)   0.538 us    |                              _raw_spin_lock_irqsave();
 5)   0.507 us    |                              _raw_spin_unlock_irqrestore();
 5)   2.521 us    |                            }
 5)   3.532 us    |                          }
 5)   4.772 us    |                        }
 5)   6.077 us    |                      }
 5)   0.476 us    |                      _raw_spin_unlock_irqrestore();
 5)   9.408 us    |                    }
 5) + 10.557 us   |                  }
 5) + 12.095 us   |                }
 5)               |                irq_exit_rcu() {
 5)   0.812 us    |                  idle_cpu();
 5)   2.285 us    |                }
 5) + 38.743 us   |              }
 5) + 40.207 us   |            }
 5) + 41.882 us   |          }
 5) + 43.329 us   |        }
 5)               |        ipv4_confirm [nf_conntrack]() {
 5)               |          nf_confirm [nf_conntrack]() {
 5)               |            __nf_conntrack_confirm [nf_conntrack]() {
 5)   1.009 us    |              hash_conntrack_raw [nf_conntrack]();
 5)               |              nf_conntrack_double_lock.constprop.0 [nf_conntrack]() {
 5)               |                nf_conntrack_lock [nf_conntrack]() {
 5)   0.787 us    |                  _raw_spin_lock();
 5)   2.157 us    |                }
 5)   0.673 us    |                _raw_spin_lock();
 5)   4.664 us    |              }
 5)               |              nf_ct_del_from_dying_or_unconfirmed_list [nf_conntrack]() {
 5)   0.551 us    |                _raw_spin_lock();
 5)   1.716 us    |              }
 5)               |              nf_ct_add_to_dying_list [nf_conntrack]() {
 5)   0.723 us    |                _raw_spin_lock();
 5)   2.199 us    |              }
 5)   0.641 us    |              __local_bh_enable_ip();
 5) + 15.020 us   |            }
 5) + 16.478 us   |          }
 5) + 17.824 us   |        }
 5)               |        kfree_skb() {
 5)               |          skb_release_head_state() {
 5)               |            nf_conntrack_destroy() {
 5)               |              nf_ct_destroy [nf_conntrack]() {
 5)   0.638 us    |                nf_ct_remove_expectations [nf_conntrack]();
 5)               |                nf_ct_del_from_dying_or_unconfirmed_list [nf_conntrack]() {
 5)   0.632 us    |                  _raw_spin_lock();
 5)   1.965 us    |                }
 5)   0.758 us    |                __local_bh_enable_ip();
 5)               |                nf_conntrack_free [nf_conntrack]() {
 5)               |                  nf_ct_ext_destroy [nf_conntrack]() {
 5)   0.694 us    |                    rcu_read_unlock_strict();
 5)               |                    nf_nat_cleanup_conntrack [nf_nat]() {
 5)   0.844 us    |                      hash_by_src [nf_nat]();
 5)   0.557 us    |                      _raw_spin_lock_bh();
 5)               |                      _raw_spin_unlock_bh() {
 5)   0.735 us    |                        __local_bh_enable_ip();
 5)   2.115 us    |                      }
 5)   6.347 us    |                    }
 5)   0.713 us    |                    rcu_read_unlock_strict();
 5)   0.533 us    |                    rcu_read_unlock_strict();
 5)   0.537 us    |                    rcu_read_unlock_strict();
 5)   0.714 us    |                    rcu_read_unlock_strict();
 5)   0.685 us    |                    rcu_read_unlock_strict();
 5)   0.759 us    |                    rcu_read_unlock_strict();
 5)   0.715 us    |                    rcu_read_unlock_strict();
 5)   0.690 us    |                    rcu_read_unlock_strict();
 5)   1.190 us    |                    kfree();
 5) + 22.548 us   |                  }
 5)   1.096 us    |                  kmem_cache_free();
 5)   0.665 us    |                  rcu_read_unlock_strict();
 5) + 27.145 us   |                }
 5) + 34.083 us   |              }
 5)   0.710 us    |              rcu_read_unlock_strict();
 5) + 37.040 us   |            }
 5) + 38.558 us   |          }
 5)               |          skb_release_data() {
 5)               |            skb_free_head() {
 5)   0.994 us    |              kfree();
 5)   3.123 us    |            }
 5)   4.551 us    |          }
 5)               |          kfree_skbmem() {
 5)   1.068 us    |            kmem_cache_free();
 5)   2.555 us    |          }
 5) + 48.565 us   |        }
 5) ! 113.116 us  |      }
 5)   0.811 us    |      rcu_read_unlock_strict();
 5) ! 175.328 us  |    }

在确定了丢包时刻的内核调用链后,可以看到在kfree_skb之前,有进行一些conntrack连接跟踪相关的逻辑处理,而要进一步查找原因,则需要耐心的分析这个过程的源码了。功力不够,我看了一段时间没有头绪,只有放弃了。但是这里可以确定是conntrack的逻辑导致的丢包。

实验最后

在网上找个一个解决办法,即建立连接时去掉conntrack的逻辑,conntrack是内核netfilter的一部分,如果我们的业务并不需要NAT,那么完全可以去掉它,还能换来一定的性能提升。去掉逻辑也很简单:

// 添加PREROUTING规则:去掉目标ip是192.168.2.120的连接跟踪
iptables -t raw -A PREROUTING -p tcp -d 192.168.2.120 -j NOTRACK
// 查看当前的PREROUTING规则
iptables -t raw -L PREROUTING -n --line-numbers
// 删除PREROUTING规则,1表示的是规则行号
iptables -t raw -D PREROUTING 1

再次运行实验,可以最终建立100万的连接了。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!