高并发服务器day21

epoll_wait 一次拿到了一批已经就绪的事件,但处理中间某个事件耗时太久,导致定时器认为后面的连接"超时",如果此时直接把这些连接释放掉,那么后面继续处理这批旧事件时,就可能访问已经释放的对象,造成崩溃。

1、2、3、4、5 理解成 epoll_wait() 一次返回的 5 个就绪事件。

假设:

复制代码
epoll_wait 返回:
[ 1号事件, 2号事件, 3号事件, 4号事件, 5号事件 ]

EventLoop 会一个一个处理:

复制代码
for (auto &event : events)
{
    event.HandleEvent();
}

第一种情况:1~5 都是普通客户端连接

例如:

复制代码
1:conn1
2:conn2
3:conn3
4:conn4
5:conn5

开始处理 conn1

复制代码
处理 conn1
    ↓
业务特别慢
    ↓
卡了 30 秒

假设服务器设置的非活跃超时只有:

复制代码
10 秒

那么理论上 conn2~conn5 已经有 30 秒没有刷新活跃时间了。

但是此时还不一定出事

因为接下来 EventLoop 会继续:

复制代码
处理 conn2 → 刷新 conn2 活跃时间
处理 conn3 → 刷新 conn3 活跃时间
处理 conn4 → 刷新 conn4 活跃时间
处理 conn5 → 刷新 conn5 活跃时间

也就是说:

虽然时间已经超过 10 秒,但是还没有执行"超时检查任务",所以它们暂时没有被释放。


第二种情况:2号事件偏偏是"定时器事件"

这才是这段话真正想讲的问题。

现在 epoll_wait() 返回的是:

复制代码
1:conn1
2:timerfd
3:conn3
4:conn4
5:conn5

处理过程:

复制代码
① conn1 开始处理

conn1 业务执行 30 秒
        ↓
超过 10 秒超时时间
        ↓
conn3、conn4、conn5 都已经很久没刷新活跃度

然后处理第二个事件:

复制代码
② timerfd

定时器开始检查:

复制代码
if (conn3 超时)
    释放 conn3;

if (conn4 超时)
    释放 conn4;

if (conn5 超时)
    释放 conn5;

于是:

复制代码
conn3 → 已释放
conn4 → 已释放
conn5 → 已释放

但是有一个致命问题。

这次 epoll_wait() 返回的事件列表里,依然还有:

复制代码
3号事件
4号事件
5号事件

EventLoop 接下来还会继续处理:

复制代码
③ 处理 conn3
④ 处理 conn4
⑤ 处理 conn5

可是 conn3 刚刚已经在定时器事件里被释放了。

于是就可能变成:

复制代码
conn3->HandleEvent();

实际上:

复制代码
conn3
 ↓
对象已经销毁
 ↓
访问野指针 / 已释放内存
 ↓
程序崩溃

这就是图里说的:

"接下来在处理 345 事件的时候就会导致程序崩溃(内存访问错误)。"


所以为什么不能在定时器里立刻释放?

因为现在还处于:

复制代码
本轮 epoll_wait 返回的事件处理过程中

应该改成:

复制代码
timer发现 conn3 超时

不是:
    立刻释放 conn3 ❌

而是:
    添加一个"释放 conn3"的任务
            ↓
       放入任务队列

先把这一轮事件全部处理完:

复制代码
处理1
处理2(定时器)
处理3
处理4
处理5

----------- 本轮事件处理结束 -----------

然后再:

复制代码
执行任务队列

释放 conn3
释放 conn4
释放 conn5

这样就不会出现:

复制代码
对象已经释放
↓
后面还要处理它的事件

的问题。

一张图就能理解

复制代码
epoll_wait
    ↓
一次返回
[ conn1 ][ timer ][ conn3 ][ conn4 ][ conn5 ]
    ↓
处理conn1
    ↓
卡了30秒
    ↓
处理timer
    ↓
发现conn3/4/5超时
    ↓
❌ 不能马上释放
    ↓
放入任务队列
    ↓
继续处理conn3/4/5
    ↓
本轮事件处理结束
    ↓
执行任务队列
    ↓
真正释放conn3/4/5

所以这段课程内容真正想强调的是:

连接的销毁不能随意发生在一次事件分发过程中,否则 epoll_wait 已经返回的事件中可能还保存着这个连接对应的对象。要采用"延迟释放",等当前这一批事件处理结束后再销毁。

而你上一问说的"一个连接一个线程互不干扰",也正好和这里连起来:这里是一条 EventLoop 线程串行处理一批连接事件,所以 conn1 卡 30 秒,后面的定时器和连接事件全部都会被拖后 30 秒。

client4 5 6

测试大文件传输:

测并发量

性能压力测试

为了验证服务器在高并发场景下的处理能力,对服务器进行压力测试,主要关注两个指标:

① 并发量

并发量表示服务器在同一时间能够处理的客户端请求数量。

并发量越高,说明服务器同时处理大量客户端连接的能力越强。

② QPS

QPS(Queries Per Second)表示服务器每秒能够处理的请求数量。

QPS 越高,说明服务器单位时间内的请求处理能力越强。

一、压力测试工具------WebBench

本次测试使用 WebBench 对服务器进行压力测试。

WebBench 的基本原理是:

创建多个客户端进程 → 每个进程连接服务器 → 发送 HTTP 请求 → 接收服务器响应 → 关闭连接 → 重复上述过程

通过大量客户端持续向服务器发送请求,可以测试服务器在高并发场景下的处理能力。

二、测试环境

性能测试结果与硬件配置、网络带宽、客户端环境等因素密切相关,因此必须说明具体测试环境,否则单独讨论并发量和 QPS 没有太大意义。

测试环境:

服务器环境:

1 核 CPU

2 GB 内存

1 Mbps 带宽

云服务器

客户端环境:

VMware 虚拟机(Linux 环境)

使用 WebBench 作为压力测试工具

测试参数:

并发数:10000

测试时间:24 小时

请求方式:HTTP 请求

三、测试过程

使用 WebBench 创建大量客户端并发访问服务器,将并发数设置为 10000,并持续向服务器发送 HTTP 请求。

测试过程中主要观察:

• 是否出现连接失败

• 是否出现服务器崩溃

• 请求是否能够正常响应

• QPS 是否稳定

• CPU、内存等资源占用情况

四、测试结果

在上述测试环境下,对服务器进行了 24 小时持续压力测试。

最终测试结果:

并发量:xxx

QPS:xxx

连接失败数:xxx

服务器运行状态:xxx

注意:

这里最好不要直接写"服务器支持 10000 并发"。

如果 WebBench 参数设置为 10000,只能说明"测试时设置了 10000 并发",最终是否真正稳定支持 10000 并发,需要结合成功请求数、失败请求数、QPS、CPU/内存占用等结果进行判断。

所以写:

"使用 WebBench 将并发数设置为 10000,对服务器进行了 24 小时持续压力测试。"

会比:

"服务器支持 10000 并发。"

更加严谨。

复制代码
服务器环境:
VMware 虚拟机(Linux)
CPU:2 核
内存:8 GB
网络:虚拟机网络环境

客户端环境:
VMware 虚拟机(Linux)
使用 WebBench 进行压力测试
测试环境:服务器与 WebBench 压测客户端均运行于同一 Linux 虚拟机中,通过本机回环地址 127.0.0.1 进行测试。因此测试结果主要用于服务器优化前后的性能对比,不代表真实跨网络环境下的绝对性能。
相关推荐
DFT计算杂谈1 小时前
FeSe超薄膜在CaF2衬底上的电子结构DFT研究
java·服务器·前端
Brilliantwxx1 小时前
【Linux】 第一个程序终端进度条
linux·运维·服务器
AI创界者1 小时前
【网络安全运维】Kali Linux 下 Medusa(美杜莎)工具的高效部署、故障排查与安全测试实战
linux·运维·web安全
FinelyYang2 小时前
CentOS 7.6 自建 LiveKit 部署指南
linux·运维·centos
Huangjin007_2 小时前
【Linux 系统篇(十四)】进程 (二) :PCB、task_struct、fork系统调用
linux·运维·服务器
HiDev_2 小时前
【非标自动化】硬核阅读理解620行梯形图(70~619行)
运维·自动化
雨田言炎2 小时前
十、QThread多线程
linux·服务器·开发语言·前端·qt
向上的车轮2 小时前
GitHub Actions 自动化运维实战:Java + TypeScript 全栈项目 CI/CD 至阿里云
运维·自动化·github
重庆小透明2 小时前
深入探寻微服务【第三篇微服务的组件】
运维·微服务·架构