文章目录
-
- 从零手写高并发服务器(十):性能测试与项目总结
- 一、压测工具安装
-
- [1.1 安装webbench](#1.1 安装webbench)
- [1.2 webbench用法](#1.2 webbench用法)
- 二、压测准备
-
- [2.1 优化编译选项](#2.1 优化编译选项)
- [2.2 调整系统参数](#2.2 调整系统参数)
- [2.3 启动服务器](#2.3 启动服务器)
- 三、压测实战
-
- [3.1 第一轮:低并发测试](#3.1 第一轮:低并发测试)
- [3.2 第二轮:中等并发测试](#3.2 第二轮:中等并发测试)
- [3.3 第三轮:高并发测试](#3.3 第三轮:高并发测试)
- [3.4 第四轮:极限测试](#3.4 第四轮:极限测试)
- [3.5 不同线程数对比](#3.5 不同线程数对比)
- 四、压测结果分析
-
- [4.1 本机实测结果汇总](#4.1 本机实测结果汇总)
- [4.2 结果分析](#4.2 结果分析)
- [4.3 可能遇到的问题](#4.3 可能遇到的问题)
- 五、项目架构完整回顾
-
- [5.1 整体架构图](#5.1 整体架构图)
- [5.2 主从Reactor模型工作流程](#5.2 主从Reactor模型工作流程)
- [5.3 一次完整的HTTP请求处理流程](#5.3 一次完整的HTTP请求处理流程)
- [5.4 关键设计思想](#5.4 关键设计思想)
- 六、完整文件清单
- 七、提交最终代码
- 八、整个系列博客目录回顾
- 九、写在最后
从零手写高并发服务器(十):性能测试与项目总结
💬 开篇:代码写完了不算完,得测!本篇我们用压测工具对HTTP服务器进行高并发测试,验证服务器的性能表现,然后做一个完整的项目回顾总结。
👍 点赞、收藏与分享:这是整个系列的最后一篇,完整收官!
🚀 内容:压测工具安装 → 压测实战 → 结果分析 → 项目架构回顾 → 总结。
一、压测工具安装
我们使用 webbench 进行压力测试,它是一个经典的轻量级Web压测工具。
1.1 安装webbench
bash
cd ~
sudo apt-get install -y ctags # webbench依赖
git clone https://github.com/EZLippi/WebBench.git
cd WebBench
make
sudo make install
验证安装:
bash
webbench --help
如果输出帮助信息就说明安装成功。
1.2 webbench用法
bash
webbench -c 并发数 -t 持续时间(秒) URL
参数说明:
-c:并发客户端数量-t:测试持续时间(秒)- 还可以加
-2使用HTTP/1.1,-k使用keep-alive
二、压测准备
2.1 优化编译选项
压测时要用Release模式编译,开启优化:
bash
cd ~/TcpServer/test
g++ -std=c++17 -O2 http_server.cpp -o http_server -lpthread
-O2 开启编译器优化,性能会比 -g 调试模式好很多。
2.2 调整系统参数
高并发测试前需要调整文件描述符限制:
bash
# 查看当前限制
ulimit -n
# 临时调大(当前终端有效)
ulimit -n 65535
如果需要永久修改:
bash
sudo vim /etc/security/limits.conf
# 添加以下两行
# * soft nofile 65535
# * hard nofile 65535
2.3 启动服务器
bash
cd ~/TcpServer/test
./http_server
注意:服务器要在一个终端运行,压测在另一个终端执行。
三、压测实战
3.1 第一轮:低并发测试
先用100个并发连接测试10秒,验证基本功能:
bash
webbench -c 100 -t 10 http://127.0.0.1:8500/hello
输出示例:
bash
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1
Runing info: 100 clients, running 10 sec.
Speed=194316 pages/min, 421018 bytes/sec.
Requests: 32386 susceed, 0 failed.
关注两个指标:
pages/min:每分钟处理的请求数failed:失败请求数,应该为0
3.2 第二轮:中等并发测试
500个并发连接,测试10秒:
bash
webbench -c 500 -t 10 http://127.0.0.1:8500/hello
输出示例:
cpp
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1
Runing info: 500 clients, running 10 sec.
Speed=171834 pages/min, 372307 bytes/sec.
Requests: 28639 susceed, 0 failed.
3.3 第三轮:高并发测试
1000个并发连接,测试10秒:
bash
webbench -c 1000 -t 10 http://127.0.0.1:8500/hello
输出示例:
bash
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1
Runing info: 1000 clients, running 10 sec.
Speed=156330 pages/min, 338715 bytes/sec.
Requests: 26055 susceed, 0 failed.
3.4 第四轮:极限测试
5000个并发连接,测试10秒:
bash
webbench -c 5000 -t 10 http://127.0.0.1:8500/hello
输出示例:
bash
wsh@VM-16-2-ubuntu:~/TcpServer/test$ webbench -c 5000 -t 10 http://127.0.0.1:8500/hello
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.
Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1
Runing info: 5000 clients, running 10 sec.
Speed=1782 pages/min, 3861 bytes/sec.
Requests: 297 susceed, 0 failed.
3.5 不同线程数对比
修改 http_server.cpp 中的线程数,分别测试:
| 线程数 | 并发1000 | 说明 |
|---|---|---|
| 0(单线程) | 基准值 | 所有连接都在主线程处理 |
| 1 | 对比 | 主线程监听+1个工作线程 |
| 2 | 对比 | 主线程监听+2个工作线程 |
| 3 | 对比 | 主线程监听+3个工作线程 |
| 4 | 对比 | 主线程监听+4个工作线程 |
一般来说,线程数设置为CPU核心数效果最好:
bash
# 查看CPU核心数
nproc
说明:本节所有压测数据均来自同一台 2核4G 腾讯云服务器,且压测程序与被测服务运行在同一主机。 因此结果会受到 CPU资源争抢、调度开销等因素影响,数据仅用于演示压测方法与趋势,不代表服务器在独立压测环境下的真实性能上限。
受限于当前机器带宽与硬件资源,暂无法进行压测机与服务端分离的标准化测试。
四、压测结果分析
4.1 本机实测结果汇总
| 并发数 | 线程数 | 成功请求数(10秒) | QPS(每秒请求数) | 失败数 |
|---|---|---|---|---|
| 100 | 2 | 32386 | 3238.6 | 0 |
| 500 | 2 | 28639 | 2863.9 | 0 |
| 1000 | 2 | 26055 | 2605.5 | 0 |
| 5000 | 2 | 297 | 29.7 | 0 |
QPS计算方式:
QPS = 成功请求数 / 压测时长(秒)例如:
32386 / 10 = 3238.6
4.2 结果分析
bash
性能曲线(本次实测趋势):
QPS
^
| ●
| \
| \
| \
| \
| \
| \
| ●
+──────────────────→ 并发数
100 500 1000 5000
- 在本次同机测试中,并发升高后QPS没有继续增长
- 5000并发时QPS明显下降,说明资源竞争/调度开销很大
- failed为0,说明功能正确性正常,但吞吐受环境限制明显
影响性能的因素:
- 同机压测资源争抢:压测端与服务端抢占CPU,导致结果偏低
- CPU核心数:决定可并行处理能力(2核机器上限有限)
- 线程数配置:线程过少利用不充分,过多会增加上下文切换
- 文件描述符限制:影响可同时维持的连接数
- 编译优化:
-O2对吞吐影响明显 - epoll模型:在高连接场景下优于select/poll,但仍受硬件上限约束
4.3 可能遇到的问题
问题1:大量failed请求
bash
# 原因:文件描述符上限不足
ulimit -n 65535
问题2:每轮压测后服务异常,必须重启
bash
# 可能原因:
# 1) 连接未及时释放(TIME_WAIT/CLOSE_WAIT堆积)
# 2) 某些状态或资源未正确回收
# 3) 异常信号处理不完整(如SIGPIPE)
#
# 建议排查:
ss -lntp | grep 8500
ss -ant | grep 8500 | awk '{print $1}' | sort | uniq -c
问题3:QPS偏低
bash
# 原因1:编译没开优化
g++ -O2 ...
# 原因2:线程数不合理
# 建议线程数与CPU核心数接近(本机2核可先用2线程)
# 原因3:压测端与服务端同机,CPU争抢严重
# 更准确的方式是压测机与服务机分离
五、项目架构完整回顾
5.1 整体架构图
bash
┌─────────────────────────────────────────────────────────────┐
│ 用户代码层 │
│ │
│ EchoServer / HttpServer应用 │
│ 注册回调 → 处理业务逻辑 │
├─────────────────────────────────────────────────────────────┤
│ HTTP协议层 │
│ │
│ HttpServer → 路由分发、静态资源服务 │
│ HttpContext → HTTP请求解析状态机 │
│ HttpRequest → 请求数据存储 │
│ HttpResponse → 响应数据组织 │
│ Util → URL编解码、文件操作、MIME类型 │
├─────────────────────────────────────────────────────────────┤
│ TCP服务器层 │
│ │
│ TcpServer → 整合所有模块,对外提供接口 │
│ Acceptor → 监听套接字管理,获取新连接 │
│ Connection → 通信连接管理(读写、缓冲区、状态) │
├─────────────────────────────────────────────────────────────┤
│ Reactor核心层 │
│ │
│ EventLoop → 事件循环(epoll_wait + 任务队列 + 定时器) │
│ Poller → epoll封装 │
│ Channel → 文件描述符事件管理 │
│ TimerWheel → 时间轮定时器 │
│ LoopThread → 线程与EventLoop绑定 │
│ LoopThreadPool→ 线程池,轮询分配连接 │
├─────────────────────────────────────────────────────────────┤
│ 基础组件层 │
│ │
│ Buffer → 用户态缓冲区 │
│ Socket → 套接字操作封装 │
│ Any → 通用类型容器 │
│ Log → 日志宏 │
└─────────────────────────────────────────────────────────────┘
5.2 主从Reactor模型工作流程
bash
主线程 (baseloop)
│
┌────┴────┐
│ Acceptor │ ← 监听端口,等待新连接
└────┬────┘
│ accept() 获取新连接fd
│
┌────┴────┐
│ TcpServer│ ← NewConnection()
└────┬────┘
│ pool.NextLoop() 轮询选择
│
┌────────────┼────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│SubLoop_1 │ │SubLoop_2 │ │SubLoop_3 │ ← 子线程EventLoop
│ │ │ │ │ │
│ Conn A │ │ Conn B │ │ Conn C │ ← 每个连接绑定到一个子线程
│ Conn D │ │ Conn E │ │ Conn F │
│ ... │ │ ... │ │ ... │
└──────────┘ └──────────┘ └──────────┘
│ │ │
epoll_wait epoll_wait epoll_wait
处理读写事件 处理读写事件 处理读写事件
5.3 一次完整的HTTP请求处理流程
bash
1. 客户端发起TCP连接
└→ 主线程Acceptor的监听socket可读
└→ accept()获取新fd
└→ TcpServer::NewConnection()
└→ 创建Connection对象
└→ 轮询分配到子线程EventLoop
└→ Connection::Established()
└→ Channel::EnableRead() 开始监听可读事件
2. 客户端发送HTTP请求数据
└→ 子线程EventLoop中epoll_wait返回可读事件
└→ Channel::HandleEvent()
└→ Connection::HandleRead()
└→ Socket::NonBlockRecv() 读取数据到Buffer
└→ HttpServer::OnMessage() 回调
└→ HttpContext::RecvHttpRequest() 解析请求
├→ 解析请求行(方法、路径、版本)
├→ 解析头部(一行一行)
└→ 接收正文(根据Content-Length)
3. 请求解析完毕
└→ HttpServer::Route() 路由匹配
├→ 静态资源?→ FileHandler() 读文件返回
└→ 功能请求?→ Dispatcher() 查路由表,调用handler
4. 生成响应
└→ HttpServer::WriteResponse()
└→ 组装响应行+头部+空行+正文
└→ Connection::Send()
└→ 数据写入out_buffer
└→ Channel::EnableWrite()
└→ epoll监控可写事件
└→ Connection::HandleWrite()
└→ Socket::NonBlockSend() 发送数据
5. 连接管理
├→ keep-alive → 保持连接,等待下一个请求
└→ close → Connection::Shutdown() → Release() → 关闭连接
5.4 关键设计思想
1. One Thread One Loop
每个线程拥有一个独立的EventLoop,线程内的所有操作都在这个EventLoop中完成,避免了多线程竞争。
2. 非阻塞IO + epoll
所有socket都设置为非阻塞模式,配合epoll的事件驱动,实现高效的IO多路复用。
3. 任务队列实现跨线程调用
当线程A需要操作线程B的资源时,不直接操作,而是把任务放到线程B的任务队列中,由线程B自己执行,避免加锁。
bash
线程A 线程B(EventLoop)
│ │
│ RunInLoop(task) │
│ ──→ 放入任务队列 ──→ │
│ ──→ eventfd通知 ──→ │
│ epoll_wait返回
│ 执行任务队列中的task
4. 时间轮定时器
用环形数组实现O(1)的定时任务添加和删除,利用shared_ptr的引用计数实现任务的自动延迟和取消。
5. Buffer缓冲区
用户态缓冲区解决了TCP粘包问题,支持动态扩容,读写位置分离。
6. shared_ptr管理Connection生命周期
Connection使用shared_ptr管理,配合enable_shared_from_this,确保在异步回调中Connection对象不会被提前释放。
六、完整文件清单
bash
~/TcpServer/
├── source/
│ └── server.hpp # 整个服务器框架(所有模块在一个头文件中)
├── test/
│ ├── buffer_test.cpp # Buffer类测试
│ ├── socket_test.cpp # Socket类测试
│ ├── channel_poller_test.cpp # Channel+Poller测试
│ ├── timer_eventloop_test.cpp# TimerWheel+EventLoop测试
│ ├── threadpool_test.cpp # 线程池测试
│ ├── echo_server.cpp # Echo服务器
│ ├── http_server.cpp # HTTP服务器
│ └── client.cpp # 测试客户端
├── wwwroot/
│ └── index.html # 静态资源
└── README.md
七、提交最终代码
bash
cd ~/TcpServer
git add .
git commit -m "完成性能测试,项目收官"
git push
八、整个系列博客目录回顾
| 篇数 | 标题 | 核心内容 |
|---|---|---|
| 第一篇 | 前置知识 | C++11特性、正则表达式、Any类、通用型函数包装器 |
| 第二篇 | 环境搭建与项目规划 | 项目结构、Makefile、架构设计 |
| 第三篇 | Buffer缓冲区 | 用户态缓冲区设计与实现 |
| 第四篇 | Socket套接字封装 | 网络操作抽象、非阻塞IO |
| 第五篇 | Channel与Poller | 事件管理、epoll封装 |
| 第六篇 | EventLoop事件循环 | Reactor核心、任务队列、eventfd |
| 第七篇 | 定时器与线程池 | TimerWheel时间轮、LoopThread、LoopThreadPool |
| 第八篇 | Connection、Acceptor、TcpServer | 连接管理、监听管理、服务器组装、EchoServer |
| 第九篇 | HTTP协议支持 | HttpRequest/Response/Context/Server |
| 第十篇 | 性能测试与项目总结 | 压测、架构回顾、设计思想总结 |
九、写在最后
到这里,整个「从零手写高并发服务器」项目就全部完成了。我们从最基础的Buffer缓冲区开始,一步步搭建起了一个完整的高性能TCP/HTTP服务器框架:
- 底层用epoll实现高效IO多路复用
- 用One Thread One Loop模型实现多线程并发
- 用时间轮实现高效的连接超时管理
- 用主从Reactor模型实现负载均衡
- 在TCP之上实现了完整的HTTP协议支持
这个项目涵盖了网络编程、多线程编程、设计模式、性能优化等多个方面的知识,是一个非常好的C++后端学习项目。