c++项目 网络聊天服务器 实现;QPS测试

源码 https://github.com/DBWGLX/SZU_system_programming

技术设计

编码

JSON的替换

JSON 不提供强类型,必须手动检查字段类型。而Protobuf 反序列化时,会自动检查字段类型。

JSON编程时,每个 k 的设置解析都得检查。
json_t *response = json_object(); 对象创建,最后还要释放。

Protobuf

已结合 K-L-V

官方编码文档学习 https://blog.youkuaiyun.com/JK01WYX/article/details/146919585?spm=1001.2014.3001.5501

网络

线程池更高效率

网络字节序的考虑

htol

send可能无法一次性发送全部数据!

EPOLLHUP , EPOLLERR 的正确处理

直接看 events 部分:https://blog.youkuaiyun.com/JK01WYX/article/details/132699613

IO

数据库操作的更高性能

数据库查询需要时间,可以不去等其操作完

开发日志

2025.3

项目搭好,跑了下,感觉一点问题没有。

a.粘包问题

压测下,注册5W个用户: !json解析错误,粘包了!json是一起解析的,毕竟找 {} 即可。

和同学交流后得知,json报文不是很好的选择,启动protobuf

2025.4

protobuf + KLV 启动

b.多个线程同时读取同一个文件描述符问题

任务队列recv 出问题

接着注册5w个用户

我用一个客户端发起5w份注册请求 —— 只注册了前5个

epoll 收到 IO 只放到任务队列,那同一个socket文件描述符就可能被多个线程同时读取,这是错误的

可以分离 接收数据 和 数据处理,后期接收数据用协程和高性能的io_uring

第一次批量注册成功1W用户

通过减慢发送速度,提升缓冲区 net.core.rmem_max,维护 recv接收完整 (服务器这边是一个线程再处理)

用时 2m40s (第一个创建时间和最后一个创建时间之差)

在这里插入图片描述
在这里插入图片描述

用的默认 net rmem 缓冲区大小

wyx@ubuntu:~/work/SZU_system_programming/ChatServer$ sysctl net.core.rmem_max
net.core.rmem_max = 212992
wyx@ubuntu:~/work/SZU_system_programming/ChatServer$ sysctl net.core.rmem_default
net.core.rmem_default = 212992

不限制发送方发送速度,程序正常运行

2m18s
在这里插入图片描述

提升 net.core.rmem_max 为 16MB,速度提升至 52s

sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.rmem_default=8388608

在这里插入图片描述

【500qps】

现在分出4个客户端,并行请求:(此时服务器4个线程并行处理)

18s => 1e4 / 18 = 555 queries per second!
在这里插入图片描述
(插入时分配主键,根据主键id排序的)

c.数据库连接池没必要问题
【700qps】

数据库连接池的思想是不用频繁创建释放数据库连接。然而多线程从连接池中取连接,也会出现竞态条件
我这里4核4任务线程,不算多,可以给每个线程分配一个连接

14s => 1w注册请求 : 1e4 ➗ 14 = 714 qps !
在这里插入图片描述

(top)运行时占用:
在这里插入图片描述

客户端发送第一个前的时间:
在这里插入图片描述
服务器同一时刻收到注册请求:
在这里插入图片描述

d. 高并发均匀处理问题 [ EPOLLONESHOT ]

在 epoll 的边缘触发(Edge Triggered,ET)模式下结合 EPOLLONESHOT 使用时,一个 socket 文件描述符在加入 epoll 实例前收到消息,加入后仍可以收到这次 IO 事件

连接一直连接断开前,recv可以一直阻塞,导致任务线程阻塞,服务器没法处理其他请求了。

所以应设置超时,或者就按任务队列顺序处理。

但是!按照任务队列顺序处理,之前补丁加的set 有漏洞,可能会忽视一些边缘驱动, 应使用 EPOLLONESHOT

19s => 1e4 / 19 = 526 qps
在这里插入图片描述

接着就可以写 io_uring 啦!

1万个套接字分别发送测试

用时 18s :555qps
证明该服务器处理大量连接请求基本没有问题~
在这里插入图片描述

细节:

正是不同的连接,尽管客户端是按顺序发送,服务器处理顺序却有所不同:

在这里插入图片描述

测试客户端是 for循环创建1w个套接字,然后 for循环发送 1w个注册请求的。
创建套接字用时4s:

在这里插入图片描述

不过 connect 并不是同时到达,服务器这边是先接收了6000+后,就开始接收注册请求了:

e. 10000连接,服务器先接收了6300+

可能是 服务器的 accept() 处理速度无法跟上客户端的连接建立速率

在这里插入图片描述
开始处理:
在这里插入图片描述
最后到达的连接:

在这里插入图片描述

这说明了 阻塞 connect() ≠ 等服务器处理完

这和 网络协议栈 + 内核调度 + 服务端处理模型 有关。内核在连接,而服务器层只是处理~

最大连接数?

在这里插入图片描述
16000+

限制因素:

  • 每个进程最大文件描述符数目
  • socket数量限制
    cat /proc/sys/fs/file-max
    cat /proc/sys/net/core/somaxconn
    cat /proc/sys/net/ipv4/ip_local_port_range
    在这里插入图片描述
  • 端口耗尽 (28K个)

解决方法:

# 临时提升当前会话文件描述符数目限制
ulimit -n 1000000

# 设置允许的最大文件数
sysctl -w fs.file-max=2097152

# 设置 backlog 队列长度
sysctl -w net.core.somaxconn=65535

# 设置临时端口范围(默认通常是 32768-60999)
sysctl -w net.ipv4.ip_local_port_range="1024 65535"

多进程/多机压测
异步化,使用 io_uring 处理网络 IO

只改服务器接收,测试为 14s : 700 qps

在这里插入图片描述

但输入输出都改后为 16s : 600 qps
在这里插入图片描述

典型的“过度异步化”的现象:在低延迟、任务轻量的场景下,io_uring 提交+回调+回收的额外开销可能反而拖慢速度。

再测试下混血接收万点查询:

14s
在这里插入图片描述

分析:

现在接收请求的方式大改了,是给每个特定的客户端追加完全数据,然后直接解析全部后放入任务队列。(而之前是每次从内核中读一个报文,这里系统调用就比较耗时)

考虑更多任务线程;达到 【1000QPS】 !

四个线程接收万个连接各一个请求:13s

在这里插入图片描述

五个线程: 12s !
在这里插入图片描述

在这里插入图片描述

八个线程:10s !
在这里插入图片描述

在这里插入图片描述

十六个线程:8s ! 1250 QPS
在这里插入图片描述

在这里插入图片描述
32个线程: 8s
在这里插入图片描述

在这里插入图片描述

64个线程:9s

在这里插入图片描述

在这里插入图片描述

2 个线程:25s

在这里插入图片描述

在这里插入图片描述

增加线程数能缓解数据库和网络发送带来的“局部阻塞”,提升整体并发效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值