百万tcp并发测试

article/2025/11/5 10:31:57

前言

都说haproxy很牛x, 可是测试的结果实在是不算满意, 越测试越失望,无论是长连接还是并发, 但是测试的流程以及工具倒是可以分享分享。也望指出不足之处。

100w的长连接实在算不上太难的事情,不过对于网上关于测试方法以及测试工具的相关文章实在不甚满意,才有本文。

本文有两个难点,我算不上完全解决。

  • 后端代码的性能.
  • linux内核参数的优化.

环境说明

下面所有的测试机器都是基于openstack云平台,kvm虚拟化技术创建的云主机。

由于一个socket连接一般占用8kb内存,所以百万连接至少需要差不多8GB内存.

建立长连接主要是需要内存hold住内存,理论上只需要内存就足够了,不会消耗太多cpu资源, 相对内存而言.

而并发则对cpu很敏感,因为需要机器尽可能快的处理客户端发起的连接。

本文的并发主要指每秒处理的请求.

硬件配置

类型配置数量
后端16核32GB1
客户端2核4GB21

软件配置

类型长连接并发
后端python && geventgolang
客户端locust && pdshlocust & pdsh

IP地址

haproxy 192.168.111.111
client-master 192.168.111.31
client-slave 192.168.111.1[13-32]

测试步骤

系统调优

  • 最大文件打开数
  • 进程数
  • socket设置

客户端

在/etc/sysctl.conf加入以下内容

 
  1. # 系统级别最大打开文件

  2. fs.file-max = 100000

  3.  
  4. # 单用户进程最大文件打开数

  5. fs.nr_open = 100000

  6.  
  7. # 是否重用, 快速回收time-wait状态的tcp连接

  8. net.ipv4.tcp_tw_reuse = 1

  9. net.ipv4.tcp_tw_recycle = 1

  10.  
  11. # 单个tcp连接最大缓存byte单位

  12. net.core.optmem_max = 8192

  13.  
  14. # 可处理最多孤儿socket数量,超过则警告,每个孤儿socket占用64KB空间

  15. net.ipv4.tcp_max_orphans = 10240

  16.  
  17. # 最多允许time-wait数量

  18. net.ipv4.tcp_max_tw_buckets = 10240

  19.  
  20. # 从客户端发起的端口范围,默认是32768 61000,则只能发起2w多连接,改为一下值,可一个IP可发起差不多6.4w连接。

  21. net.ipv4.ip_local_port_range = 1024 65535

在/etc/security/limits.conf加入以下内容

 
  1. # 最大不能超过fs.nr_open值, 分别为单用户进程最大文件打开数,soft指软性限制,hard指硬性限制

  2. * soft nofile 100000

  3. * hard nofile 100000

  4. root soft nofile 100000

  5. root hard nofile 100000

服务端

在/etc/sysctl.conf加入以下内容

 
  1. # 系统最大文件打开数

  2. fs.file-max = 20000000

  3.  
  4. # 单个用户进程最大文件打开数

  5. fs.nr_open = 20000000

  6.  
  7. # 全连接队列长度,默认128

  8. net.core.somaxconn = 10240

  9. # 半连接队列长度,当使用sysncookies无效,默认128

  10. net.ipv4.tcp_max_syn_backlog = 16384

  11. net.ipv4.tcp_syncookies = 0

  12.  
  13. # 网卡数据包队列长度

  14. net.core.netdev_max_backlog = 41960

  15.  
  16. # time-wait 最大队列长度

  17. net.ipv4.tcp_max_tw_buckets = 300000

  18.  
  19. # time-wait 是否重新用于新链接以及快速回收

  20. net.ipv4.tcp_tw_reuse = 1

  21. net.ipv4.tcp_tw_recycle = 1

  22.  
  23. # tcp报文探测时间间隔, 单位s

  24. net.ipv4.tcp_keepalive_intvl = 30

  25. # tcp连接多少秒后没有数据报文时启动探测报文

  26. net.ipv4.tcp_keepalive_time = 900

  27. # 探测次数

  28. net.ipv4.tcp_keepalive_probes = 3

  29.  
  30. # 保持fin-wait-2 状态多少秒

  31. net.ipv4.tcp_fin_timeout = 15

  32.  
  33. # 最大孤儿socket数量,一个孤儿socket占用64KB,当socket主动close掉,处于fin-wait1, last-ack

  34. net.ipv4.tcp_max_orphans = 131072

  35.  
  36. # 每个套接字所允许得最大缓存区大小

  37. net.core.optmem_max = 819200

  38.  
  39. # 默认tcp数据接受窗口大小

  40. net.core.rmem_default = 262144

  41. net.core.wmem_default = 262144

  42. net.core.rmem_max = 16777216

  43. net.core.wmem_max = 16777216

  44.  
  45. # tcp栈内存使用第一个值内存下限, 第二个值缓存区应用压力上限, 第三个值内存上限, 单位为page,通常为4kb

  46. net.ipv4.tcp_mem = 786432 4194304 8388608

  47. # 读, 第一个值为socket缓存区分配最小字节, 第二个,第三个分别被rmem_default, rmem_max覆盖

  48. net.ipv4.tcp_rmem = 4096 4096 4206592

  49. # 写, 第一个值为socket缓存区分配最小字节, 第二个,第三个分别被wmem_default, wmem_max覆盖

  50. net.ipv4.tcp_wmem = 4096 4096 4206592

在/etc/security/limits.conf加入一下内容

 
  1. # End of file

  2. root soft nofile 2100000

  3. root hard nofile 2100000

  4. * soft nofile 2100000

  5. * hard nofile 2100000

重启使上述内容生效
不愿意重启就使用以下命令

sysctl -p

宿主机

一般宿主机都会启用防火墙,所以防火墙会记录每一条tcp连接记录,所以如果当虚拟机建立的tcp数量超过宿主机的防火最大记录数,则会drop掉后来的tcp.主要通过/etc/sysctl.conf下的这个配置项。

 
  1. # 将连接改为200w+以满足单机100w长连接.

  2. net.nf_conntrack_max=2048576

测试工具选取

locust

一个用python编写的非常出色的测试框架,满足大多数测试场景.内置http client, 可自定义client, 支持水平扩展.

下载安装参考: https://docs.locust.io/en/latest/index.html

pdsh

用于调试启动多个locust客户端以及一些批量操作.

下载安装使用参考:
https://github.com/chaos/pdsh

http://kumu-linux.github.io/blog/2013/06/19/pdsh/

server脚本编写

长连接通过tcp协议测试, 借助gevent框架.

脚本如下

 
  1. #coding: utf-8

  2. from __future__ import print_function

  3. from gevent.server import StreamServer

  4. import gevent

  5.  
  6. # sleeptime = 60

  7.  
  8. def handle(socket, address):

  9. # print(address)

  10. # data = socket.recv(1024)

  11. # print(data)

  12. while True:

  13. gevent.sleep(sleeptime)

  14. try:

  15. socket.send("ok")

  16. except Exception as e:

  17. print(e)

  18.  
  19. if __name__ == "__main__":

  20. import sys

  21. port = 80

  22. if len(sys.argv) > 2:

  23. port = int(sys.argv[1])

  24. sleeptime = int(sys.argv[2])

  25. else:

  26. print("需要两个参数!!")

  27. sys.exit(1)

  28. # default backlog is 256

  29. server = StreamServer(('0.0.0.0', port), handle, backlog=4096)

  30. server.serve_forever()

并发通过http协议测试,借助golang, 因为golang可以充分利用多核且效率高.
脚本如下

 
  1. package main

  2.  
  3. import (

  4. // "fmt"

  5. "io"

  6. "log"

  7. "net/http"

  8. "os"

  9. "time"

  10. )

  11.  
  12. type myHandler struct{}

  13.  
  14. func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {

  15. // time.Sleep(time.Second * 1)

  16. io.WriteString(w, "ok")

  17. }

  18.  
  19. func main() {

  20. var port string

  21. port = ":" + os.Args[1]

  22.  
  23. srv := &http.Server{

  24. Addr: port,

  25. Handler: &myHandler{},

  26. ReadTimeout: 30 * time.Second,

  27. WriteTimeout: 30 * time.Second,

  28. }

  29.  
  30. log.Fatal(srv.ListenAndServe())

  31. }

client脚本编写

长连接脚本

 
  1. #coding: utf-8

  2. import time

  3. from gevent import socket

  4. from locust import Locust, TaskSet, events, task

  5.  
  6. class SocketClient(object):

  7. """

  8. Simple, sample socket client implementation that wraps xmlrpclib.ServerProxy and

  9. fires locust events on request_success and request_failure, so that all requests

  10. gets tracked in locust's statistics.

  11. """

  12.  
  13. def __init__(self):

  14. # 仅在新建实例的时候创建socket.

  15. self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  16. self.__connected = False

  17.  
  18. def __getattr__(self, name):

  19. skt = self._socket

  20.  
  21. def wrapper(*args, **kwargs):

  22. start_time = time.time()

  23. # 判断是否之前建立过连接,如果是则建立连接,否则直接使用之前的连接

  24. if not self.__connected:

  25. try:

  26. skt.connect(args[0])

  27. self.__connected = True

  28. except Exception as e:

  29. total_time = int((time.time() - start_time) * 1000)

  30. events.request_failure.fire(request_type="connect", name=name, response_time=total_time, exception=e)

  31. else:

  32. try:

  33. data = skt.recv(1024)

  34. # print(data)

  35. except Exception as e:

  36. total_time = int((time.time() - start_time) * 1000)

  37. events.request_failure.fire(request_type="recv", name=name, response_time=total_time, exception=e)

  38. else:

  39. total_time = int((time.time() - start_time) * 1000)

  40. if data == "ok":

  41. events.request_success.fire(request_type="recv", name=name, response_time=total_time, response_length=len(data))

  42. elif len(data) == 0:

  43. events.request_failure.fire(request_type="recv", name=name, response_time=total_time, exception="server closed")

  44. else:

  45. events.request_failure.fire(request_type="recv", name=name, response_time=total_time, exception="wrong data: {}".format(data))

  46.  
  47. return wrapper

  48.  
  49. class SocketLocust(Locust):

  50. """

  51. This is the abstract Locust class which should be subclassed. It provides an XML-RPC client

  52. that can be used to make XML-RPC requests that will be tracked in Locust's statistics.

  53. """

  54.  
  55. def __init__(self, *args, **kwargs):

  56. super(SocketLocust, self).__init__(*args, **kwargs)

  57. self.client = SocketClient()

  58.  
  59. class SocketUser(SocketLocust):

  60. # 目标地址

  61. host = "192.168.111.30"

  62. # 目标端口

  63. port = 80

  64. min_wait = 100

  65. max_wait = 1000

  66.  
  67. class task_set(TaskSet):

  68. @task(1)

  69. def connect(self):

  70. self.client.connect((self.locust.host, self.locust.port))

并发脚本

 
  1. #coding: utf-8

  2. from __future__ import print_function

  3. from locust import HttpLocust, TaskSet, task

  4.  
  5. class WebsiteUser(HttpLocust):

  6. host = "http://192.168.111.30"

  7. # 目标端口

  8. port = 80

  9. min_wait = 100

  10. max_wait = 1000

  11.  
  12. class task_set(TaskSet):

  13. @task(1)

  14. def index(self):

  15. self.client.get("/")

监控工具选择

netdata

通过本工具可以直观的感受到系统的各项指标的变化

效果图如下

百万级别长连接,并发测试指南

下载安装参考:https://github.com/firehol/netdata/wiki/Installation

本机脚本

watch -n 1 "ss -s && uptime &&free -m"

简单查看本机连接数,负载,内存情况。

效果图如下

百万级别长连接,并发测试指南

长连接测试步骤

启动客户端

  • locust master
locust -f /root/loadtest/socket_load_backend.py --master
  • locust slave
pdsh -w 192.168.111.1[13-32] "locust -f /root/loadtest/socket_load_backend.py --slave --master-host=192.168.111.31"

注意: 在slave端一样需要又socket_load_backend.py文件.

启动后端

nohup python /root/loadtest/tcpserver.py 80 550 &> /var/log/tcpserver1.log &

开始测试

登陆locust的web页面: http://192.168.111.31:8089

开始参数如下.

百万级别长连接,并发测试指南

Number of users to simulate
代表最终创建多少的用户.

Hatch rate (users spawned/second)代表每秒创建多少的用户

由上图可知,每秒2000个用户数增长,增长大盘100w需要500秒,所以在后端每个连接保持550秒,以保证至少550秒内达到100w连接.当建立一百万用户以后就会每隔一段时间执行自定义的任务,时间间隔在min_wait与max_wait时间范围内.

测试结果

百万级别长连接,并发测试指南

百万级别长连接,并发测试指南

从面结果可以看出,一共完成了200w左右的请求, 每秒请求数量差不多在1800左右.然后负载在1左右,说明cpu资源差不多达到了100%.因为这里的后端是单进程的.再者内存使用量在11GB左右,还算合理.

并发测试步骤

启动客户端

  • locust master
locust -f /root/loadtest/http_load_backend.py --master
  • locust slave
 
  1.  
  2. pdsh -w 192.168.111.1[13-32] "locust -f /root/loadtest/http_load_backend.py --slave --master-host=192.168.111.31"

  3.  
  4. # 多新建一个终端再次执行以下命令,因为它是单线程的,所以启动的数量一般与cpu个数相等,而上面的长连接消耗的主要是内存,所以不需要多启动一倍的客户端

  5. pdsh -w 192.168.111.1[13-32] "locust -f /root/loadtest/http_load_backend.py --slave --master-host=192.168.111.31"

启动后可以发现有40个slave,效果如下.

百万级别长连接,并发测试指南

启动后端

nohup go run go/src/server.go 80 &> /var/log/goServer.log &

开始测试

注意这地方的测试应该是1w 1.5w 2w的数量依次的往上加,即,第一次user用户数填10000,Hatch rate填10000,然后依次分别增加.

这里就贴最终的结果了.

百万级别长连接,并发测试指南

测试结果

百万级别长连接,并发测试指南

百万级别长连接,并发测试指南

从面结果可以看出,一共完成了10w左右的请求, 每秒请求数量差不多在16000左右.然后负载在9左右,远远没有想想中的强势...其中主要受两方面限制, 一是内核参数, 再者就是宿主机性能的限制.

而性能调优暂时不在这篇文章内容内,主要是积累还不够.再者本文主要是测试.
而负载均衡器暂时还没看到满意的,所以并发到1.6w就算本文的结束了。

总结

工欲善其事必先利其器,动手之前应该选一件称手的工具,locust便是那件不错的工具,但是有了工具还要设定正确的目标,以及步骤,不然很难成功.这里算是抛砖引玉了吧.

不足之处

  1. 没有对吞吐量做测试,即服务端发送不同的文本大小,这里只是测试2字节的相应内容.

  2. 没有测试并发更高的情况下的100w长连接.

后记

之所以想写一篇大数量级的测试方式,是因为,网上大多数文章要么是给测试代码或者工具,要么是给一堆解释的不是很清楚的参数,再者就是只贴连接数的数量,如果只是达到这么多的连接,却不给出成功失败率,实在是有点耍流氓。

有意思的是这么强势的测试框架居然相关内容这么少,有空读读源码.

本文所有的代码可以在以下链接找到
https://github.com/youerning/blog/tree/master/locust-test

参考文档:
Linux之TCPIP内核参数优化:
https://www.cnblogs.com/fczjuever/archive/2013/04/17/3026694.html

理解 Linux backlog/somaxconn 内核参数:
https://jaminzhang.github.io/linux/understand-Linux-backlog-and-somaxconn-kernel-arguments/

Linux下Http高并发参数优化之TCP参数:
https://kiswo.com/article/1017

单台服务器百万并发长连接支持:
http://blog.csdn.net/mawming/article/details/51941771

结合案例深入解析orphan socket产生与消亡:
https://m.aliyun.com/yunqi/articles/91966

出处;http://blog.51cto.com/youerning/2089930?lb


http://chatgpt.dhexx.cn/article/rosUsRIs.shtml

相关文章

postman进行高并发测试

1、在postman中创建collections,并在collections中创建访问接口并保存 2、点击上方的runner进行参数配置 3、iterations就是同时并发请求数量

并发测试

一、Apache Benchmark ab 命令会创建很多的并发访问线程,模拟多个访问者同时对某一 URL 进行访问,可用来测试 Apache 的负载压力,也可以测试 Web 服务器的压力。 1. 安装 liunx 安装:yum install httpd Windows安装:下…

ApiFox高并发测试用例

介绍 在开发中我们经常会测试高并发场景下的业务,下面来看看如何使用ApiFox编写一个高并发的测试用例 编写接口 第一步我们要编写测试的接口,并且建立一个用例 自动化测试 将上面的测试用例添加到自动化测试中,设置并发参数即可&#xff…

JMeter进行并发测试

下载安装 前提:本地需要安装jdk1.8或以上版本 1、下载地址:https://jmeter.apache.org/download_jmeter.cgi,选择zip包下载。 2、在合适的安装目录解压压缩包,然后配置环境变量。 2.1、新建一个系统变量,变量名称为&am…

接口并发测试

这段时间一直在走流程测试,今天大哥过来,让他看了下我的代码还有点问题,回头还要修改下以前购买商品的帖子,今天先说下接口的并发测试吧,以前都是用Jmeter来做并发测试,今天本来也打算用来着,大…

浅谈并发测试

fiddler并发-多条 步骤: 打上断点找到接口,右键Replay-shiftReissue Requests,弹框填写100点击ok,在列表出现100条url点击go,进行并发 fiddler并发-2条 步骤: 打上断点找到要并发的接口,右…

Jmeter并发测试

什么是并发测试? 并发测试指的是指N个客户端同时发请求给服务器,服务器收到并处理 栗子: 100个人同时在登录QQ,100个人同时使用支付,100个人同时访问淘宝 并发怎么做? 1.添加HTTP请求,意在…

1、并发测试的几种简单方法

文章目录 一、AB二、postman1、添加 Collection2、添加要测试的URL3、选中添加的Collection,设置运行时参数4、结果如下 三、JMeter四、代码模拟五、Testng 并发测试1、注解方式2、配置文件方式 本文介绍几种简单的并发测试方法。 本文分为五部分,即ab、…

Jmeter实现并发测试

在实际测试过程中,需要对某些业务进行并发测试,模拟绝对并发时系统的处理是否有异常。比如支付场景,如果有一些秒杀或者其他优惠活动时,很可能有多用户并发的场景发生,下面就针对支付的并发场景,使用Jmeter…

pearson特征选择matlab,常用的特征选择方法之 Pearson 相关系数

众所周知,特征选择是机器学习活动至关重要的一步。最理想的情况下,我们把所有影响目标的独立因素给找出来,然后使用合适的量化手段,就能够得到完美描述目标问题的特征列表,用这些特征去建立合适容量的模型,…

机器学习特征选择方法

文章目录 前言特征选择过滤法Pearson系数卡方检验互信息和最大信息系数距离相关系数方差选择法 包装法嵌入法 总结 前言 最近在看吴恩达的深度学习机器学习课程。地址:deeplearningai。课程在机器学习特征工程的课程中提到特征选择。在机器学习项目生命周期里&…

特征选择-单变量特征选择

1.SelectKBest可以依据相关性对特征进行选择,保留k个评分最高的特征。 ①方差分析 分类问题使用f_classif,回归问题使用f_regression。 f_classif:分类任务 跟目标的分类,将样本划分成n个子集,S1,S2,..,Sn&#xf…

特征选择的几种方法

目录 1、 过滤法(Filter) 1.1 方差选择法 1.2 相关系数法 1.3 卡方检验 1.4 互信息法 1.5 relief算法 2、包裹法(Wrapper) 2.1 递归特征消除法 2.2 特征干扰法 3、嵌入法(Embedded) 3.1 基于惩罚…

特征选择-嵌入式选择

嵌入式特征选择是将特征选择过程与学习器训练过程融为一体,两者在同一个优化过程中完成,即在学习器训练过程中自动地进行了特征选择。 基于惩罚项的特征选择法 给定数据集 D { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x n , y n ) }…

特征选择案例

特征降维(P443) 特征降低维度分两种方法:特征选择和特征抽取。 特征选择:选择全部特征的一个子集作为特征向量。 特征抽取:通过已有特征的组合建立一个新的特征子集。 主成分分析方法(PCA)就…

机器学习-特征选择

特征选择 1、简述特征选择的目的。 减轻维数灾难问题:特征个数越多,模型也会越复杂,其泛化能力会下降。 降低学习任务的难度: 特征个数越多,分析特征、训练模型所需的时间就越长。 特征选择能够明显的改善学习器的精…

lasso特征选择python_特征选择怎么做?这篇文章告诉你

原标题:特征选择怎么做?这篇文章告诉你 照片由 Clem Onojeghuo 发布在 Unsplash 上面 作者 | Pier Paolo Ippolito 翻译 | Skura 编辑 | 唐里 原文标题:Feature Selection Techniques 原文链接:https://towardsdatascience.com/feature-selection-techniques-1bfab5fe0784 …

python方差特征选择_特征选择-Filter过滤法(方差)

3.1 Filter过滤法 过滤方法通常用作预处理步骤,特征选择完全独立于任何机器学习算法。它是根据各种统计检验中的分数以及相关 性的各项指标来选择特征。 3.1.1 方差过滤 3.1.1.1 VarianceThreshold 这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小,就表示…

特征选择与特征提取

目录 一、 特征选择1、特征2、特征选择3、扩展——特征选择算法(有兴趣和精力可了解)拓展--完全搜索:拓展--启发式搜索:拓展--随机搜索:拓展--遗传算法: 二、 特征提取三、特征提取主要方法——PCA(主成分分析)1、PCA算法是如何实现的?PCA--零均值化(中心…

特征选择简述

目录 一、 特征选择的意义 二、特征选择的方法 1. 排序方法(Ranking methods) 2. 过滤方法(Filter approaches) 3.包装器方法(wrapper methods) 4.混合方法(Hybrid methods) …