实验性项目,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 ,并且响应在微秒级
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
|  |      101realpg PRO @u20237 #100  100W 并发的后端自从 golang 出来以后 猴子都写得出来 没啥 io 的立刻返回一个简易逻辑 | 
|  |      102realpg PRO 你们这帮人宁可相信 OP 的鬼扯,都不相信我是秦始皇 V 我 50 | 
|      103shaoyie OP @u20237 后端很简单 不解析协议,在我的仓库 reactor 的 example 目录中,qps 主要看机器配置 | 
|  |      106rrfeng      2023-09-13 19:50:14 +08:00  1 | 
|  |      108E1n      2023-09-13 20:17:07 +08:00 脑残:) | 
|      109Dart      2023-09-13 20:25:28 +08:00 请问你们真的有这么大的实际业务流量吗? | 
|      110shaoyie OP 那就给有这么大流量的公司提供一种选项呗 | 
|  |      111lesismal      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 为此惊讶这件事让我感到狠惊讶! | 
|  |      112lesismal      2023-09-13 23:02:16 +08:00 #111 编辑的时候窜行了,更正下 > 这个可能不太准确:Edo while 条件+ONESHOT 需要重新添加事件,这种才会需要更多 syscall ,如果都是单次读完当前数据的话,ET 和 LT 是一样的。 更正为: > 这个可能不太准确:ET+ONESHOT 需要重新添加事件,这种才会需要更多 syscall ,如果都是单次读完当前数据的话,ET 和 LT 是一样的。 | 
|      113shaoyie 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%就被抹平了 | 
|  |      115lesismal      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 。 你是不是又搞混了什么。。 | 
|  |      116lesismal      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` 这说法也是不准确的。 | 
|      117shaoyie OP 好吧,每次你聊都容易扯远,扯着扯着就忘了最开始提出的疑问了,聊 niubix 的问题呢,你非要聊其他模式的应用。这里不继续讨论了, | 
|  |      118rrfeng      2023-09-14 10:02:04 +08:00 via Android 虽然前面嘲讽了一波,op 态度好起来了那就正经评价一下: 你在完全不懂 Nginx 的情况下写了个超越 Nginx 的玩意,就差不多等于「我不认识你,但你是个傻逼」。哪怕你花半天找到任何一个 Nginx 实现不合理的地方然后用自己的方式写出来。 and HTTP proxy 不支持 HTTP 协议,更是贻笑大方。哪怕你只支持 HTTP 1.0 。 所以发出来供大家评判的东西,需要准备充分一点,否则就接受冷嘲热讽。受嘲讽恼羞成怒还要喷回去,就更小孩子气了。 | 
|  |      119rrfeng      2023-09-14 10:03:33 +08:00 via Android 另外 c++ 多线程,了解下 envoy ? | 
|      120shaoyie 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 这中间的性能差异,可不是你们觉得功能不完善就能跑出来的。这中间的空间留给我发挥的空间很大 | 
|      121shaoyie OP 大部分怼我的可能你们没有理解反向代理它的程序逻辑是怎么走的,数据流是流转的,只是觉得心中的神被对比了,就喷 | 
|      122shaoyie 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 了 | 
|      124barriosdillot      2023-09-21 11:06:45 +08:00 @shaoyie 兄弟可以,能坚持就值得鼓励! | 
|      125shaoyie OP @barriosdillot 感谢 |