4C-2G 来战 [ Golang Websocket 百万连接测试 ]

2023-06-05 10:08:44 +08:00
 lesismal

4C-2G 来战 [ Golang Websocket 百万连接测试 ]

代码代码: https://github.com/lesismal/go-websocket-benchmark

使用框架: https://github.com/lesismal/nbio

总结:4cpu 核心,2G 内存,100 万连接,1k 数据载荷,500 万次 Echo 测试,TPS 10+万,详情请继续往下看。

看到隔壁帖子一些人上来就说这没用那没用 https://www.v2ex.com/t/945616

想起之前自己帖子也是类似: https://www.v2ex.com/t/755862

之前几次也是被好些人说 nbio 这没用那没用,所以有感而发。

PS:期待 @byte10 大神百忙之中能再来与我讨论技术。

前置声明:

  1. 绝大多数人不需要百万级连接场景的优化,但确实有公司有项目有人需要,搞这些东西就是为了满足这些需要;
  2. 标题 4C-2G 只是作为一个参考指标,用不是特别高的配置更能体现框架的承载力。采用这个配置并不是鼓励实际场景就要用这么低的配置去处理百万连接,实际场景应从实际出发;
  3. 否定别人只需要动动嘴,但技术是实在的,如果也有兄弟姐妹想说 nbio 之类的 poller 没用,请确认自己真正了解相关知识,然后带上实际的论据观点再来讨论,如果实在想这么讲,也请先看下旧帖。

以前很多次遇到很多人先入为主地以为异步框架就是要写回调、golang 框架也如此。 为了避免误解,这里也再对 nbio 的同步异步做下简要说明: nbio 底层非阻塞、异步 io ,但使用逻辑协程池处理 http 请求、websocket 消息,由于 golang 协程不像进程线程成本那么高,所以逻辑协程池 size 比 c/cpp 或者其他语言的逻辑线程数量大得多,所以用户仍然可以写同步逻辑,实际上也是这样处理的。

为了避免既当裁判又当运动员、甚至误导用户,每当有人问我性能时,我通常是建议用户以自己实测得到的性能数据为准,而不是直接相信测试库作者提供的数据。所以建议有兴趣的兄弟姐妹在自己环境进行测试。

如果测试库代码有误,欢迎 Issue/PR 来指正更正。

在这里也邀请并欢迎大家来跑下多个 go websocket 框架的测试,并留言到这里供参考: https://github.com/lesismal/go-websocket-benchmark/issues/11

另外:除了 nbio 以外的其他 go websocket 框架多数主要是基于 golang 标准库、每个连接一个协程,这种普通配置的硬件上无法跑到海量连接,所以百万连接测试的脚本默认只针对 nbio 自己,如果想测试更多参数,请自行修改脚本。 gev 支持百万但不支持 TLS ,gobwas+netpoll 有 for loop 阻塞问题,所以目前没有添加它们,以后可能会添加。

下面是我的 ubuntu vm 上跑的数据,仅供参考

环境:

--------------------------------------------------------------
os:

Ubuntu 20.04.6 LTS \n \l

--------------------------------------------------------------
cpu model:

model name	: AMD Ryzen 7 5800H with Radeon Graphics
--------------------------------------------------------------
              total        used        free      shared  buff/cache   available
Mem:       16362568      396988    15151676        1636      813904    15656380
Swap:             0           0           0
--------------------------------------------------------------
# taskset 0-3, nbio server 只占 4 cpu 核心
run nbio_nonblocking server on cpu 0-3
--------------------------------------------------------------

压测结果:

--------------------------------------------------------------
BenchType  : Connections
Framework  : nbio_nonblocking
TPS        : 26545    # 每秒建立连接数
Min        : 20ns     # 建立单个连接最小耗时
Avg        : 74.80ms  # 建立单个连接平均耗时
Max        : 37.67s   # 建立单个连接最大耗时(实际压测并发度大,有一些容易失败,目前测试逻辑会重试、多次重试时间导致最大值时间较长)
TP50       : 30ns     # 前 50%次建立连接最大耗时
TP75       : 30ns     # 前 75%次建立连接最大耗时
TP90       : 30ns     # 前 90%次建立连接最大耗时
TP95       : 30ns     # 前 95%次建立连接最大耗时
TP99       : 31ns     # 前 99%次建立连接最大耗时
Used       : 37.67s   # 总耗时
Total      : 1000000  # 建立连接数
Success    : 1000000  # 成功建立连接数
Failed     : 0        # 建立连接成功数(实际压测并发度大,有一些容易失败,目前测试逻辑会重试、多次重试都失败才算失败)
Concurrency: 2000     # 并发度( 2000 个协程,每个协程循环建立连接)
--------------------------------------------------------------
BenchType  : BenchEcho
Framework  : nbio_nonblocking
TPS        : 113789     # 每秒 Echo 次数
Min        : 182.56us   # 单次 Echo 最小耗时
Avg        : 435.80ms   # 单次 Echo 平均耗时
Max        : 1.69s      # 单次 Echo 最大耗时
TP50       : 407.61ms   # 前 50%次 Echo 最大耗时
TP75       : 554.56ms   # 前 75%次 Echo 最大耗时
TP90       : 698.06ms   # 前 90%次 Echo 最大耗时
TP95       : 800.52ms   # 前 95%次 Echo 最大耗时
TP99       : 1.07s      # 前 99%次 Echo 最大耗时
Used       : 43.94s     # 总耗时
Total      : 5000000    # 测试 Echo 次数
Success    : 5000000    # 测试 Echo 的成功次数
Failed     : 0          # 测试 Echo 的失败次数
Conns      : 1000000    # 测试的连接数
Concurrency: 50000      # 并发度( 5w 个协程,每个协程循环取当前可用的连接进行 Echo )
Payload    : 1024       # websocket body size
CPU Min    : 95.96%     # CPU 最小值(采集开始时较小)
CPU Avg    : 347.80%    # CPU 平均值
CPU Max    : 380.94%    # CPU 最大值
MEM Min    : 1.82G      # MEM 最小值( Benchmark 开始前有进行 Warmup ,所以起始内存最低值已经较大)
MEM Avg    : 1.92G      # MEM 平均值
MEM Max    : 1.94G      # MEM 最大值
---------------------------------------------------------------------------------------------------
3408 次点击
所在节点    程序员
34 条回复
qbmiller
2023-06-16 17:21:01 +08:00
nbio VS gnet
lesismal
2023-06-17 14:39:03 +08:00
> nbio VS gnet

@qbmiller

对于七层协议:gnet 不支持 TLS/HTTP/Websocket ,gnet examples 对这些 7 层协议相关的示例似乎并不是完整功能,离实用还差很多,gnet 的 README 里所说的 HTTP 性能并不是真正的完整功能 HTTP 框架,当初那个只是简单地解析 HTTP 换行符之类的这种,所以用于跟别人完整功能的 HTTP 框架对比性能本身就是不合理的。有给 gnet 作者聊过这个问题,他解释了原因:
https://github.com/panjf2000/gnet/issues/182
好像有其他人在 gnet 之上封装过 TLS ,但易用性相比于 nbio 应该还差很多吧,结合实际场景的应用不知道效果如何。

对于四层:我也有做测试: https://github.com/lesismal/go-net-benchmark/issues/1
nbio 的 used by 列表里有 gnet-io/gnet-benchmarks 但现在的 gnet-io/gnet-benchmarks 好像是没有包含 nbio ,可能是测过之后删掉了吧:
https://github.com/lesismal/nbio/network/dependents?dependents_after=MjQ3MjQyNjExMzI

根据我自己压测的情况是,单就四层而言,nbio 和 gnet 性能差不多,跑多轮测试有时候 nbio 高一点有时候 gnet 高一点,我的环境里跑 10 轮,可能 nbio 有 7 、8 次会略高一些。

因为遇到过多次如下情况:benchmark 库官方提供的压测数据与我自己实际跑他们官方测试代码的数据差异较大,也包括一些类似 gnet HTTP 性能这种压测数据不符合实际场景的情况。
所以我通常不建议直接以压测仓库作者提供的压测数据作为参考依据,包括我自己写的压测代码。所以我建议有兴趣的兄弟姐妹还是自己跑下实际代码试试看,并且也可以根据实际测试参数、尽量把各个框架的参数、配置对齐来公平对比而不是被作者自己号称的性能忽悠。
qbmiller
2023-06-25 13:29:56 +08:00
@lesismal 感谢大佬解答
joyanhui
2023-11-12 19:56:51 +08:00
@lesismal 看到晚了,我早先上了 gnet 的车
lesismal
2023-11-13 22:12:52 +08:00
@joyanhui 随时欢迎换乘~
shockerli
2023-12-08 19:49:45 +08:00
不错,OP 加油
xbchaonba
2023-12-09 22:33:21 +08:00
这个 js websocket 怎么发 ping , 我连上后 120 秒总是断开一直有发消息也断
lesismal
2023-12-09 23:52:33 +08:00
@xbchaonba #27

为了避免僵尸连接耗尽 server 资源,尤其是进电梯导致信号终断、移动信号切换信号塔、设备掉电等情况时,收不到 TCP 协议的 FIN ,连接一直无法退出。TCP 设置 keepalive 是检测四层链路健康,可以避免这种情况,但它并不能解决七层僵尸连接、慢连接的问题,因为四层链路可能一直是健康的,但这个连接什么数据都不发或者只发很少数据、仍然会耗尽 server 资源,所以七层的 keepalive 是需要的,有了七层、再做四层的 TCP 设置 keepalive 就画蛇添足了、没必要。既然应该做七层,所以就应该都设置读超时比较好,所以与其他很多框架默认不设置读超时不太一样,nbio 默认设置。

一直有发数据仍然断开是因为 Upgrade 过程中默认设置了 120s 读超时,但后续收到消息时没有自动更新读超时。
当前版本,server 端设置 upgrader.KeepaliveTime = YourKeepaliveDuration 即可自动更新读超时,设置 upgrader.KeepaliveTime = YourKeepaliveDuration 后,不只是 PING 、其他类型的 message 也都可以,因为是读到消息就更新超时。僵尸连接检测也不是必须要 PING 协议才行、有效的协议即可。
我考虑下,要不要把这个心跳间隔也做成默认配置、自动更新读超时。

如果不想设置读超时,可以在 Upgrade 后 SetReadDeadline(time.Time{}) 清空超时关闭。
lesismal
2023-12-10 13:00:27 +08:00
@xbchaonba #27

dev 修改了下,默认会开启读超时的更新,可以试下:
go get -u github.com/lesismal/nbio@a81e8e2

过阵子再发到新版
xbchaonba
2023-12-10 13:22:19 +08:00
@lesismal conn.SetReadDeadline(time.Now().Add(time.Second / 120)) 是要用这个方法更新吧,昨天试了好多次 u.KeepaliveTime = time.Second / 120 直接这样重新赋值没有用
xbchaonba
2023-12-10 13:25:15 +08:00
@lesismal #29 好的感谢你的解答
lesismal
2023-12-11 23:26:07 +08:00
@xbchaonba #30

> conn.SetReadDeadline(time.Now().Add(time.Second / 120)) 是要用这个方法更新吧

是用这个方法。
但你这个是 120 分之一秒,设置完后很快就过期了,如果没有立刻收到新消息,就 close 了

> 昨天试了好多次 u.KeepaliveTime = time.Second / 120 直接这样重新赋值没有用

同样的问题,估计也是因为 120 分之一秒太快了
xbchaonba
2023-12-12 10:53:39 +08:00
@lesismal 32 哈哈,* 被我打成/ 了,后面发现改过来了但是评论删除不了; u.KeepaliveTime 我刚开始把它弄到 onmassage 里面去了;现在看明白了感谢解答
lesismal
2023-12-12 12:26:04 +08:00
@xbchaonba #33 好嘞!欢迎使用,如果有疑问、bug ,也欢迎来 issue 、PR

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

https://www.v2ex.com/t/945827

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

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

© 2021 V2EX