公司动态

测试20万qps的web接口(四)

📅 2026/8/29 4:23:02
测试20万qps的web接口(四)
测试20万qps的web接口四本篇文章主要描述探讨两种新的的方式 – 让压测程序自动轮询使用不同的源ip来建立tcp连接。在上篇文章中想借助LVS来线性增加client端的tcp最大连接数发现这种方式不行。client端只有1个源ipserver端也只有1个目的ipclient端与server端之间建立连接的tcp四元组是client_ip port_range server_ip server_port。这里只有port_range是可变的因此client端建立的最大连接数是固定的。在使用LVS之前原本以为client端会与LVS的dip或rip建立连接没想到client端是与LVS的vip建立连接。在本篇文章中换了一种思维方式来突破最大连接数限制。现在设想是client端拥有多个源ipserver端只有1个目的ip在client端向server端建立连接时自动轮询使用client端的多个源ip以此来线性增加client端的最大连接数。无论是上篇文章中的方案还是本篇文章中的方案都期望不影响client端的压测程序的使用方式。比如curl和jmeter可以通过参数指定tcp连接使用的源ip而wrk没有这样的功能。如果让压测程序适配源ip轮询通过参数或是修改源码会带来不可预知的复杂度。使用netfilter错误方式和AI拉扯了好多天AI给出了一个方案使用netfilter的mangle表的OUTPUT链来对进出的数据包做修改然后通过路由策略来动态设置不同的源ip测试后发现这个方案不行。AI又给出了另一个方案使用netfilter的raw表的OUTPUT链来对进出的数据包做修改测试后发现这种方式也不行。用了这么久的AI经常发现在一些比较少见或复杂的问题上AI知道一些大致的解决方案但是它不能保证是否正确可行。看来还是要懂一些原理要不然碰到这些问题死磕也没用。以下是具体的配置命令实验没有成功如果有大佬看出了问题可以指点一下哈。# 修改rt_tables定义路由表id和别名之间的映射[rootclient ~]# vi /etc/iproute2/rt_tables201src_1202src_2203src_3# 添加多个ip[rootclient ~]# ip addr add 10.0.0.201/24 dev ens33[rootclient ~]# ip addr add 10.0.0.202/24 dev ens33[rootclient ~]# ip addr add 10.0.0.203/24 dev ens33# 添加路由规则# 10.0.0.132是目的地址iprouteadd10.0.0.132 dev ens33 src10.0.0.201 table src_1iprouteadd10.0.0.132 dev ens33 src10.0.0.202 table src_2iprouteadd10.0.0.132 dev ens33 src10.0.0.203 table src_3# 添加路由策略[rootclient ~]# ip rule add pref 201 fwmark 1 lookup src_1[rootclient ~]# ip rule add pref 202 fwmark 2 lookup src_2[rootclient ~]# ip rule add pref 203 fwmark 3 lookup src_3# 设置netfilteriptables-tmangle-Fiptables-tmangle-AOUTPUT-d10.0.0.132-jCONNMARK --restore-mark iptables-tmangle-AOUTPUT-d10.0.0.132-mmark--mark0-mstate--stateNEW-mstatistic--modenth--every3--packet0-jMARK --set-mark1iptables-tmangle-AOUTPUT-d10.0.0.132-mmark--mark0-mstate--stateNEW-mstatistic--modenth--every3--packet1-jMARK --set-mark2iptables-tmangle-AOUTPUT-d10.0.0.132-mmark--mark0-mstate--stateNEW-mstatistic--modenth--every3--packet2-jMARK --set-mark3iptables-tmangle-AOUTPUT-d10.0.0.132-mstate--stateNEW-jCONNMARK --save-mark# 查看连接情况[rootclient ~]# for i in $(seq 1 6); do curl --no-keepalive -s http://10.0.0.132 -o /dev/null; done[rootclient ~]# conntrack -L | grep 10.0.0.132conntrack v1.4.4(conntrack-tools):8flow entries have been shown. tcp69TIME_WAITsrc10.0.0.134dst10.0.0.132sport51252dport80src10.0.0.132dst10.0.0.134sport80dport51252[ASSURED]mark2secctxsystem_u:object_r:unlabeled_t:s0use1tcp612TIME_WAITsrc10.0.0.134dst10.0.0.132sport51258dport80src10.0.0.132dst10.0.0.134sport80dport51258[ASSURED]mark3secctxsystem_u:object_r:unlabeled_t:s0use1tcp611TIME_WAITsrc10.0.0.134dst10.0.0.132sport51256dport80src10.0.0.132dst10.0.0.134sport80dport51256[ASSURED]mark1secctxsystem_u:object_r:unlabeled_t:s0use1tcp64TIME_WAITsrc10.0.0.134dst10.0.0.132sport51250dport80src10.0.0.132dst10.0.0.134sport80dport51250[ASSURED]mark1secctxsystem_u:object_r:unlabeled_t:s0use1tcp610TIME_WAITsrc10.0.0.134dst10.0.0.132sport51254dport80src10.0.0.132dst10.0.0.134sport80dport51254[ASSURED]mark0secctxsystem_u:object_r:unlabeled_t:s0use1使用LD_PRELOAD正确方式在失败次数过多时会时不时怀疑自我。有好几次都想放弃这种方式想着要不要去修改压测程序的源码来支持源ip轮询。看到AI说“src_ip在connect时已经确定了没法做修改”突然想到了一个测试方案。还好自己会点C语言也懂点Socket C API的基本使用。想着能不能在connect函数中来对源ip做动态轮询。通过搜索发现Linux上有LD_PRELOAD这个环境变量可以有选择性的载入函数覆盖libc中的函数通过简单的测试发现这个方案的确可行。以下是我参考的几篇文章。https://www.cnblogs.com/sandeepin/p/ld-preload-inject.htmlhttps://blog.csdn.net/chen_jianjian/article/details/80627693https://github.com/yongboy/bindp具体的一些源码、配置。# 添加源ip[rootclient ~]# ip addr add 10.0.0.201/32 dev ens33 label ens33:201[rootclient ~]# ip addr add 10.0.0.202/32 dev ens33 label ens33:202[rootclient ~]# ip addr add 10.0.0.203/32 dev ens33 label ens33:203# 查看源ip[rootclient ~]# ip addr show ens332: ens33:BROADCAST,MULTICAST,UP,LOWER_UPmtu1500qdisc pfifo_fast state UP group default qlen1000link/ether 00:50:56:20:79:ae brd ff:ff:ff:ff:ff:ff inet10.0.0.100/24 brd10.0.0.255 scope global noprefixroute ens33 valid_lft forever preferred_lft forever inet10.0.0.201/32 scope global ens33:201 valid_lft forever preferred_lft forever inet10.0.0.202/32 scope global ens33:202 valid_lft forever preferred_lft forever inet10.0.0.203/32 scope global ens33:203 valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe20:79ae/64 scopelinkvalid_lft forever preferred_lft forever# mybind.c关键源码片段参考自github上的bindp项目# 覆盖connect函数int connect(int fd, const struct sockaddr *sk, socklen_t sl){unsigned short _pfget_address_family(sk);if(_pfAF_INET){char *src_ip;int rnrand()%3;if(rn0){src_ip10.0.0.201;}elseif(rn1){src_ip10.0.0.202;}else{src_ip10.0.0.203;}// 手动bindstruct sockaddr_in local_addr;memset(local_addr,0, sizeof(local_addr));local_addr.sin_familyAF_INET;local_addr.sin_porthtons(0);//0表示随机端口if(inet_pton(AF_INET, src_ip,local_addr.sin_addr)0){perror(inet_pton);return1;}if(bind(fd,(struct sockaddr *)local_addr, sizeof(local_addr))0){perror(bind);return1;}returnreal_connect(fd, sk, sl);}else{returnreal_connect(fd, sk, sl);}}# 编译成共享库gcc-shared-fPICmybind.c-omybind.so-ldl# 设置环境变量exportLD_PRELOAD$PWD/mybind.so# 删除环境变量unsetLD_PRELOAD测试curl使用可以看到nginx日志中的源ip是自动变化的。# 执行10次curlrootclient:~# for in in {1..10}; do LD_PRELOAD/root/mybind.so curl 10.0.0.101; sleep 1; done# server端的nginx访问日志10.0.0.202 - -[27/Aug/2026:10:57:21 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.203 - -[27/Aug/2026:10:57:22 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.202 - -[27/Aug/2026:10:57:23 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.202 - -[27/Aug/2026:10:57:24 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.203 - -[27/Aug/2026:10:57:25 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.201 - -[27/Aug/2026:10:57:26 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.201 - -[27/Aug/2026:10:57:27 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.202 - -[27/Aug/2026:10:57:28 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.201 - -[27/Aug/2026:10:57:29 0000]GET / HTTP/1.120013-curl/8.5.010.0.0.201 - -[27/Aug/2026:10:57:30 0000]GET / HTTP/1.120013-curl/8.5.0# client端的连接状态可以看到使用的源ip是随机的rootclient:~# ss -ant | grep 80TIME-WAIT0010.0.0.201:3578310.0.0.101:80 TIME-WAIT0010.0.0.202:5322310.0.0.101:80 TIME-WAIT0010.0.0.201:4233710.0.0.101:80 TIME-WAIT0010.0.0.202:5907310.0.0.101:80 TIME-WAIT0010.0.0.203:5874110.0.0.101:80 TIME-WAIT0010.0.0.203:4070710.0.0.101:80 TIME-WAIT0010.0.0.202:5676710.0.0.101:80 TIME-WAIT0010.0.0.201:4727110.0.0.101:80 TIME-WAIT0010.0.0.203:3344110.0.0.101:80 TIME-WAIT0010.0.0.201:5644110.0.0.101:80# server端的连接状态# 由于是client主动断开连接所以server的连接状态没有处于TIME_WAIT状态rootserver:/usr/local/nginx# ss -ant | grep 80LISTEN05110.0.0.0:800.0.0.0:*测试wrk使用可以看到nginx日志中的源ip是自动变化的。rootclient:~# LD_PRELOAD$PWD/mybind.so wrk -t1 -c1 -d10s -T5s -H Connection: Close --latency http://10.0.0.101:80/_init invoked.Running 10stest http://10.0.0.101:80/1threads and1connections Thread Stats Avg Stdev Max /- Stdev Latency415.41us189.67us6.60ms95.09% Req/Sec573.8192.75820.0068.00% Latency Distribution50%382.00us75%427.00us90%510.00us99%1.06ms5722requestsin10.02s,1.33MBreadRequests/sec:571.25Transfer/sec:135.56KB# server端的nginx访问日志rootserver:/usr/local/nginx/logs# grep 10.0.0.201 access.log | wc -l1887rootserver:/usr/local/nginx/logs# grep 10.0.0.202 access.log | wc -l1848rootserver:/usr/local/nginx/logs# grep 10.0.0.203 access.log | wc -l1906# client处于TIME_WAIT状态的连接很少rootclient:~# ss -ant | grep 80TIME-WAIT0010.0.0.202:5713110.0.0.101:80 TIME-WAIT0010.0.0.202:3375310.0.0.101:80 TIME-WAIT0010.0.0.202:3797510.0.0.101:80 TIME-WAIT0010.0.0.201:3965510.0.0.101:80 TIME-WAIT0010.0.0.202:3388310.0.0.101:80 TIME-WAIT0010.0.0.201:6051910.0.0.101:80 TIME-WAIT0010.0.0.201:4629910.0.0.101:80 TIME-WAIT0010.0.0.201:6065910.0.0.101:80 TIME-WAIT0010.0.0.203:4309110.0.0.101:80 TIME-WAIT0010.0.0.201:5633710.0.0.101:80# server处于TIME_WAIT状态的连接很多rootserver:/usr/local/nginx# ss -ant | grep 80LISTEN05110.0.0.0:800.0.0.0:* TIME-WAIT0010.0.0.101:8010.0.0.203:34219 TIME-WAIT0010.0.0.101:8010.0.0.202:38709 TIME-WAIT0010.0.0.101:8010.0.0.202:35259 TIME-WAIT0010.0.0.101:8010.0.0.201:54637 TIME-WAIT0010.0.0.101:8010.0.0.201:48991... rootserver:/usr/local/nginx# ss -ant | grep 80 | wc -l5314结论通过LD_PRELOAD覆盖libc中的connect函数可以动态修改源ip不需要修改压测程序的参数或源码使得压测程序的使用保持了中立。无论是自己写的简单测试程序还是curl和wrk它们在运行时都没有指定源ip而是由connect函数自动设置的源ip。这样的话只要源ip足够多可以创建的最大连接数就会足够大感觉可以支持每秒20w短连接。后续规划确认方案可行后后续就需要做一些具体的细化和测试工作。大致包含以下内容优化connect函数例如源ip是写死的轮询源ip时使用了简单的随机对Linux上的tcp和其它参数做调整分配100个源ip做测试