c++多线程实现反向代理 QPS 达到 Haproxy/Nginx 的 3 倍

2023-09-12 16:32:08 +08:00
 shaoyie

NiubiX.

项目地址

实验性项目,NiubiX 只提供反向代理功能,大家轻拍有不好的地方可以留言或提 issue/pr. 觉得好就点个 star ,我会持续完善它

与 Nginx/Haproxy 对比测试

Linux 5.19.0-1030-gcp #32~22.04.1-Ubuntu
Instacne 1 GCP cloud VM, 2 cores, 4GB RAM 10.146.0.2 (nginx,haproxy, niubix run at here)
Instacne 2 GCP cloud VM, 2 cores, 4GB RAM 10.146.0.3 (backend, wrk run at here)

nginx version config

nginx version: nginx/1.18.0 (Ubuntu)

server {
    listen       8082 reuseport;
    server_name  localhost;

    access_log  off;
    error_log off;

    location / {
        proxy_pass http://10.146.0.3:8080;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

root         516       1  0 Aug24 ?        00:00:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data  417322     516  0 12:13 ?        00:00:06 nginx: worker process
www-data  417323     516  0 12:13 ?        00:00:08 nginx: worker process

haproxy version config

HAProxy version 2.4.22-0ubuntu0.22.04.2 2023/08/14

listen niubix
    bind 0.0.0.0:8083
    mode http
    option forwardfor
    server s1 10.146.0.3:8080

ps -eLf | grep haproxy
root      449421       1  449421  0    1 15:11 ?        00:00:00 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
haproxy   449423  449421  449423  0    2 15:11 ?        00:00:05 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
haproxy   449423  449421  449429  0    2 15:11 ?        00:00:05 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock

单独测试后端程序处理能力, 确保不存在吞吐量瓶颈

run at 10.146.0.2

wrk -t 2 -c 100 -d 10s  http://10.146.0.3:8080/xxx
Running 10s test @ http://10.146.0.3:8080/xxx
  2 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   520.95us  203.98us   4.09ms   68.03%
    Req/Sec    59.25k     2.68k   63.62k    52.50%
  1179133 requests in 10.00s, 173.17MB read
Requests/sec: 117888.45
Transfer/sec:     17.31MB

为了数据真实性,我只取了 1 次测试结果,连续对 3 个服务测试截图

对于 nginx 的数据声明一下:只有偶尔能跑到 1.7w 的 qps ,如果 proxy_pass http://10.146.0.3:8080; 换到 127.0.0.1:8080 ,qps 能到 9000 qps ,至于局域网内为什么这么低通过 strace 也没看到异常,而且 cpu 也通跑满,不知道它在干嘛

tcpdump tcp port 8080 抓包查看 niubix 实际数据,包含 X-Real-IP, XFF ,并且响应在微秒级

目前具备功能:

测试声明

接下来开发计划

8288 次点击
所在节点    程序员
125 条回复
shaoyie
2023-09-13 16:20:51 +08:00
抱歉,搞错了, #80 是在 nignx 主机上测试的
在 wrk 的主机上测试,数据没那么好,
wrk -t 2 -c 100 -d 20s http://10.146.0.2:8082/xxx
Running 20s test @ http://10.146.0.2:8082/xxx
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.87ms 18.90ms 172.84ms 88.01%
Req/Sec 7.64k 6.23k 25.84k 74.00%
304115 requests in 20.01s, 46.69MB read
Requests/sec: 15195.51
Transfer/sec: 2.33MB
shaoyie
2023-09-13 16:24:18 +08:00
#81 又鸡巴贴错了,抱歉
```
wrk -t 2 -c 100 -d 10s -H 'Connection: keep-alive' http://10.146.0.2:8082/xxx
Running 10s test @ http://10.146.0.2:8082/xxx
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 8.30ms 12.55ms 116.67ms 86.81%
Req/Sec 12.44k 8.43k 23.21k 55.50%
247471 requests in 10.00s, 38.00MB read
Requests/sec: 24736.85
Transfer/sec: 3.80MB
```
lesismal
2023-09-13 16:34:37 +08:00
简单扫了几眼代码,没全看,大概明白为啥 niubix 性能这么高了,只扫了几分钟、不保证我说的是准确的,如果有误,OP 和各位多多指正。

1. 对 client 端请求的处理
https://github.com/shaovie/niubix/blob/main/src/http_conn.cpp#L168
这里的 http request 处理,目测似乎并没有做完整的 http 协议解析所以也没有请求合法性校验之类的,也没有 nginx 那些完善的对请求进行中间处理的功能,主要是添加 realip/x-forward-for

2. 对 backend 端的处理
https://github.com/shaovie/niubix/blob/main/src/backend_conn.cpp#L74
看这里是对 backend 读取到的数据直接转发给 client 、没有进行 http 协议的中间处理。但 nginx 是有处理功能的、比如开了 gzip ,backend 没开 gzip ,nginx 是会自己加上 gzip 逻辑的,也包括其他通用功能的处理。
另外啊,如果只是需要盲转发数据,其实用 splice 这种 zero copy 的 syscall 性能更好。

所以综合下来看,相比于 nginx 完善的功能,niubix 目前版本对 client 、backend 的处理都是功能不完善的、还属于 demo 级代码,或许适用于自家特定业务的优化,不适合作为通用功能的反代。

这种 demo 级的测试,跟商业级稳定大项目来对比性能本身就是不合理的,就像我之前给 OP 说的那样:拿不完整功能的 http server 去参加 TechEmpower Plaintext 没意义。
hankai17
2023-09-13 16:55:03 +08:00
@lesismal + 1
曾经也写过一个类似的 server 也是不解析 http 协议 在实际压测时性能略高于 nginx
shaoyie
2023-09-13 16:59:01 +08:00
@lesismal 又看到你,
1. 虽然解析过滤不完整,但是我也走了完整的循环,只是没有过滤所有 header 而已,但这正常单一功能的优势(仅提供反向代理功能)
2. splice 是限于 pipe 。ZEROCOPY 是异步的增加复杂度了,buf 机制也要改,整体提升非常有限
3. 其他选项,根本不会触发啊,比如 gzip:我发的内容很短"hello world"。
4. 我声明了啊,本来就是个实验性项目,功能不完整

只是证明一种可行性,数据要辩证的分析
shaoyie
2023-09-13 17:00:26 +08:00
@hankai17 我的 backend server 就不解析协议,qps 直接就是 11w ,我上边贴了
starinmars
2023-09-13 17:17:54 +08:00
@1423 一楼,我信你。
ruanimal
2023-09-13 17:18:48 +08:00
@lesismal 你就说快不快吧[doge]
rrfeng
2023-09-13 17:22:06 +08:00
我用 shell 写了个 proxy ,可以支持 10w qps 。

目前只支持 echo "hello,world"
lesismal
2023-09-13 17:24:54 +08:00
@shaoyie #85

1/3/4: 既然是这样,就不要用 qps x3 nginx 、haproxy 这种标题了,完全没有可比性,至少标题指明用于特定场景优化会好些。
nginx 的行业地位比自定制的玩具 demo 稳多了,这样标题、大家看了第一感觉就是你在碰瓷 nginx 、吹牛+踩 nginx 弱鸡,就像突然蹦出个民科一顿言论吊打数学家科学家一样。
goev 的帖子也是如此。

> 2. splice 是限于 pipe 。ZEROCOPY 是异步的增加复杂度了,buf 机制也要改,整体提升非常有限

按现在的功能,如果只是转发 backend 给 client ,应该不需要改太多
shaoyie
2023-09-13 17:48:25 +08:00
@rrfeng 来呀,我接受你的挑战,搞不定你就吃粑粑
shaoyie
2023-09-13 17:59:17 +08:00
@lesismal 我标题做了定语啊:反向代理。

而且我项目首页就写了 “Just a reverse proxy service”
nginx 是功能很多,但是做为反向代理,大部分功能不需要,非要比功能多不多干吗?不是还有 haproxy 做对比吗

“应该不需要改太多”,想当然了吧,部分的栈内存要换成堆内存,还要增加一次系统调用查询结果综合提升非常有限。再有,程序还没有到过滤优化的阶段。
tool2d
2023-09-13 18:01:37 +08:00
帖子刷了差不多 3200 个点击,也只有 4 个人 star ,足够证明了这项目没前途。

真不是 QPS 的问题。
shaoyie
2023-09-13 18:06:56 +08:00
@tool2d 哈哈,开心就好
e7
2023-09-13 18:09:08 +08:00
grep -Rn EPOLLET src
src/event/ngx_event.h:353:#define NGX_CLEAR_EVENT EPOLLET
src/event/modules/ngx_epoll_module.c:32:#define EPOLLET 0x80000000
src/event/modules/ngx_epoll_module.c:292: ee.events = EPOLLIN|EPOLLET;
src/event/modules/ngx_epoll_module.c:412: ee.events = EPOLLIN|EPOLLET;
src/event/modules/ngx_epoll_module.c:476: ee.events = EPOLLET|EPOLLIN|EPOLLRDHUP;
src/event/modules/ngx_epoll_module.c:705: ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;

其实 37L 说得挺好
jeesk
2023-09-13 18:11:42 +08:00
哈哈,路过看看。 周末有时间我自己测试一下。
shaoyie
2023-09-13 18:26:32 +08:00
@e7
ET 模式不是性能的标配,读的话不管哪种模式都是尽量一次性读完,减少 syscall 次数,而且有了这个前提 大部分情况 ET 模式反而会浪费一次 syscall ,因为必须判断 ret=-1 && errno = EAGAIN ,而在水平模式下,读出来的数量小于你的 buf 长度 就可以了,不需要再尝试一次

个人感觉它适合在 write 方面,减少 epoll_ctl 的调用

欢迎讨论
shaoyie
2023-09-13 18:27:01 +08:00
@jeesk 欢迎哦,有问题随时找我
shaoyie
2023-09-13 18:28:11 +08:00
@e7 咱想复杂了,就讨论反向代理这块,语言和技术发展方向暂避不谈
u20237
2023-09-13 18:57:59 +08:00
问个不该问的,这个问题的后端 比代理端更厉害
我猜测这个后端能上 100W 并发 这样才会实现代理端和后端各 40W+
如果后端有底层限制跑不到 30W 20W 并发,代理端很难突破限制

直接测试后端的 QPS 值是什么呢?总数据量?状态码?
如果同意,后端的软件名和版本号?

如果是两机测试,有可能带宽跑满,但结果对第三机无意义。第三计算机会有各种因素被限速甚至丢包/屏蔽

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/973075

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX