实验性项目,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 ,并且响应在微秒级
nginx的问题搞定了,wrk 加上 -H 'Connection: keep-alive'了,wrk默认不带,nginx按照 Connection: close处理了。
重新更正一下连续测试结果
wrk -t 2 -c 100 -d 10s -H 'Connection: keep-alive' http://10.146.0.2:8081/xxx
Running 10s test @ http://10.146.0.2:8081/xxx
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 1.22ms 800.49us 19.71ms 94.57%
Req/Sec 26.67k 1.92k 29.53k 76.00%
530996 requests in 10.01s, 77.99MB read
Requests/sec: 53032.21
Transfer/sec: 7.79MB
(base) root@instance-1:~# 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 10.16ms 13.47ms 93.49ms 85.88%
Req/Sec 8.64k 7.59k 23.31k 68.50%
172028 requests in 10.01s, 26.41MB read
Requests/sec: 17188.44
Transfer/sec: 2.64MB
(base) root@instance-1:~# wrk -t 2 -c 100 -d 10s -H 'Connection: keep-alive' http://10.146.0.2:8083/xxx
Running 10s test @ http://10.146.0.2:8083/xxx
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 6.49ms 8.64ms 141.16ms 97.99%
Req/Sec 8.89k 1.25k 13.48k 85.86%
176005 requests in 10.00s, 21.82MB read
Requests/sec: 17598.35
Transfer/sec: 2.18MB
101
realpg 2023-09-13 19:12:36 +08:00
@u20237 #100
100W 并发的后端自从 golang 出来以后 猴子都写得出来 没啥 io 的立刻返回一个简易逻辑 |
102
realpg 2023-09-13 19:13:24 +08:00
你们这帮人宁可相信 OP 的鬼扯,都不相信我是秦始皇 V 我 50
|
103
shaoyie OP @u20237 后端很简单 不解析协议,在我的仓库 reactor 的 example 目录中,qps 主要看机器配置
|
106
rrfeng 2023-09-13 19:50:14 +08:00 1
|
108
E1n 2023-09-13 20:17:07 +08:00
脑残:)
|
109
Dart 2023-09-13 20:25:28 +08:00
请问你们真的有这么大的实际业务流量吗?
|
110
shaoyie OP 那就给有这么大流量的公司提供一种选项呗
|
111
lesismal 2023-09-13 22:40:38 +08:00
@shaoyie #97
> 减少 syscall 次数,而且有了这个前提 大部分情况 ET 模式反而会浪费一次 syscall 这个可能不太准确:Edo while 条件+ONESHOT 需要重新添加事件,这种才会需要更多 syscall ,如果都是单次读完当前数据的话,ET 和 LT 是一样的。 > 而在水平模式下,读出来的数量小于你的 buf 长度 就可以了,不需要再尝试一次 除非你的读 buf 长度大于 socket 设置的读缓冲区 size ,否则不管 ET 还是 LT ,读本身是没法保障单次读出来的数量小于 buf 长度的,因为有可能 socket 读缓冲区数据量大于读 buf 长度 这里的 do while 条件也不是尽量读完,例如 socket 读缓冲区有 33k 数据,buf 是 32k ,本次读到 32k ,则不满足你的 (ret == -1 && errno == EINTR) 条件。 但这也不能算 bug ,因为你默认用的是 LT ,即使本次没读完、下一轮 event loop 也会继续触发读,只是相比于单次读完,这样需要内核在下一轮 event loop 继续派发可读事件、这样未必最优。 > 庆幸,昨晚程序能跑起来后我就第一时间做了对比测试,数据很惊讶 niubix 实现的功能本身就不是完整 http 相关功能、比 nginx 、haproxy 的逻辑少很多,所以比它们快也应该是意料之中,OP 为此惊讶这件事让我感到狠惊讶! |
112
lesismal 2023-09-13 23:02:16 +08:00
#111 编辑的时候窜行了,更正下
> 这个可能不太准确:Edo while 条件+ONESHOT 需要重新添加事件,这种才会需要更多 syscall ,如果都是单次读完当前数据的话,ET 和 LT 是一样的。 更正为: > 这个可能不太准确:ET+ONESHOT 需要重新添加事件,这种才会需要更多 syscall ,如果都是单次读完当前数据的话,ET 和 LT 是一样的。 |
113
shaoyie OP @lesismal
1. man 7 epoll 说的很清楚 The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows: a) with nonblocking file descriptors; and b) by waiting for an event only after read(2) or write(2) return EAGAIN. 你必须要读到返回 errno=EAGAIN ,如果我是水平模式,不没必要 2. 你这属于抬杠,在多少情况是 socket 缓冲区暴满了?要考虑大部分情况的代码分支走向 1 )程序处理的慢,读不过来,堆积了 2 )业务类型就是客户端不停的 send (也得看处理的快慢) 这都不是关键问题,多路复用同步的读取 你可以把 recv buf 搞得很大,因为它是共享的,不存在浪费内存的问题。 另外,我回复中写的是 EAGAIN ,不是 EINTER 。 3. 是不完整,我惊讶的是给我的性能发挥空间还很大啊,如果只是性能超过 haproxy 20%,那我就没啥好惊讶的,等我完善 完善 可能这 20%就被抹平了 |
115
lesismal 2023-09-14 00:33:19 +08:00
> 1. man 7 epoll 说的很清楚
> The suggested way to use epoll as an edge-triggered (EPOLLET) interface is as follows: > a) with nonblocking file descriptors; and > b) by waiting for an event only after read(2) or write(2) return EAGAIN. > 你必须要读到返回 errno=EAGAIN ,如果我是水平模式,不没必要 > 2. 你这属于抬杠,在多少情况是 socket 缓冲区暴满了?要考虑大部分情况的代码分支走向 > 1 )程序处理的慢,读不过来,堆积了 > 2 )业务类型就是客户端不停的 send (也得看处理的快慢) > 这都不是关键问题,多路复用同步的读取 你可以把 recv buf 搞得很大,因为它是共享的,不存在浪费内存的问题。 你好像没看懂我在说什么,我说的是你当前这个 LT 单次 event 不读完的实现,与 ET 单次读完的区别,我没说你 bug 啊也没说你一定需要读完啊,你再看下 #111 > 你这属于抬杠,在多少情况是 socket 缓冲区暴满了?要考虑大部分情况的代码分支走向 单次读完相比于你当前的 LT 单次不一定读完,也并不多浪费什么代码,也就循环里多个 if EAGAIN 的判断。而且我也没说必须这样做,只是分析你的 LT 实现与 ET 的区别,没说你这个实现就影响性能了或是怎么样,而是说 `这样未必最优`,我可不是说你这样一定不如单次读完啊。 你这么容易觉得我在抬杠,没必要。人在觉得发现新大陆、搞了大进步的时候最容易自我陶醉、也最容易听不进去跟自己不同的观点,很正常。 你可以先让自己冷静下来再看看,或许能吸收些新东西。 另外: > 你必须要读到返回 errno=EAGAIN 并不是必须这样的,自己想做流控的话,可以自行选择读多少、什么时候继续读,比如 golang ,有数据来了 net.TCPConn 就可读,但是你应用层没有调用 net.TCPConn.Read 也没关系啊,你什么时候想读直接能读就醒了,如果当前没数据不可读、阻塞在那等 runtime event 来了唤醒就可以了 man 手册离的是那是 `The suggested way`, 因为 ET 数据没读完、没有新数据到来是不会再触发可读的、如果用户解析处理逻辑有 bug 并且不继续读和处理就可能僵尸连接了。 但请你看清楚,那 `You must do it`,所以 `必须要读到返回 errno=EAGAIN` 这说法也是不准确的。 > 另外,我回复中写的是 EAGAIN ,不是 EINTER 。 我上面说的不是你回复其他人的 EAGAIN ,而是说你源码中的这块,我上面漏贴了代码链接: https://github.com/shaovie/niubix/blob/main/src/io_handle.cpp#L24 > 3. 是不完整,我惊讶的是给我的性能发挥空间还很大啊,如果只是性能超过 haproxy 20%,那我就没啥好惊讶的,等我完善 完善 可能这 20%就被抹平了 那可以惊讶的事情可真是太多了,你继续加油提高性能天花板吧 > 我说的读的情况多一次 syscall ,指的是 read ,不是 epoll_ctl 读的时候,ET 怎么就可能比 LT 多一次 syscall 了呢?同样一次读事件到来,同样的 read buf 。 你是不是又搞混了什么。。 |
116
lesismal 2023-09-14 00:36:47 +08:00
#115
man 手册离的是那是 `The suggested way`, 因为 ET 数据没读完、没有新数据到来是不会再触发可读的、如果用户解析处理逻辑有 bug 并且不继续读和处理就可能僵尸连接了。 但请你看清楚,那 `You must do it`,所以 `必须要读到返回 errno=EAGAIN` 这说法也是不准确的。 -> man 手册里的那是 `The suggested way`, 因为 ET 数据没读完、没有新数据到来是不会再触发可读的、如果用户解析处理逻辑有 bug 并且不继续读和处理就可能僵尸连接了。 但请你看清楚,那不是 `You must do it`,没人强迫你必须读到 EAGAIN ,也不是只有 event loop 里才能进行读、其他地方就不能读了,所以 `必须要读到返回 errno=EAGAIN` 这说法也是不准确的。 |
117
shaoyie OP 好吧,每次你聊都容易扯远,扯着扯着就忘了最开始提出的疑问了,聊 niubix 的问题呢,你非要聊其他模式的应用。这里不继续讨论了,
|
118
rrfeng 2023-09-14 10:02:04 +08:00 via Android
虽然前面嘲讽了一波,op 态度好起来了那就正经评价一下:
你在完全不懂 Nginx 的情况下写了个超越 Nginx 的玩意,就差不多等于「我不认识你,但你是个傻逼」。哪怕你花半天找到任何一个 Nginx 实现不合理的地方然后用自己的方式写出来。 and HTTP proxy 不支持 HTTP 协议,更是贻笑大方。哪怕你只支持 HTTP 1.0 。 所以发出来供大家评判的东西,需要准备充分一点,否则就接受冷嘲热讽。受嘲讽恼羞成怒还要喷回去,就更小孩子气了。 |
119
rrfeng 2023-09-14 10:03:33 +08:00 via Android
另外 c++ 多线程,了解下 envoy ?
|
120
shaoyie OP @rrfeng 我不认识你,但你是个傻 X
基于你好好说话,我也好好回复你,尊重都是相互的。上边这句不是送给你的,但确是你扣给我的,我从来没有贬低 nginx/haproxy ,只是用他们做对比测试而已。 谁说不支持 http 协议?不支持 wrk 怎么跑出来数据的? 我声明了,只是没有全部解析所有 http header 而已,因为反向代理也不需要解析 Accept, Expires ,Date, Etag, Last-Modified, Cache-* 等等这些部分,也不需要实现 web server 的功能 我只需要解析需要的就可以了(当然现在功能不完善,可能还要解析 cookie ,还不支持 POST Content-Length ),这就是我提到的功能拆分后带来的性能提升 envoy 确实功能很多,值得参考 我还是那句话,要辩证的看数据,1.7w ~ 5.3w 这中间的性能差异,可不是你们觉得功能不完善就能跑出来的。这中间的空间留给我发挥的空间很大 |
121
shaoyie OP 大部分怼我的可能你们没有理解反向代理它的程序逻辑是怎么走的,数据流是流转的,只是觉得心中的神被对比了,就喷
|
122
shaoyie OP 2023-09-20 更新
1. 基于状态机的 http parser ,解析效率超过 nginx 模式 1 倍 2. 灵敏的健康检查机制 3. 增加 POST/DELETE/PUT/HEAD 的支持(主要是 Content-Length 的支持) 4. Frontend Active Check 5. 增加 Admin api web server (简易版),可以通过 http 请求动态更新配置(部分配置) 6. 通过 Host 匹配 app (还不支持模糊匹配) 以上,性能未减 计划 1. Transfer-Encoding: chunked 2. 支持 https (有经验的兄弟可以联系我,这块我没碰过) 3. ip hash 均衡策略 4. 支持 Proxy protocol 完成以上就可以发 v0.1 了 |
124
barriosdillot 2023-09-21 11:06:45 +08:00
@shaoyie 兄弟可以,能坚持就值得鼓励!
|
125
shaoyie OP @barriosdillot 感谢
|