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

333 天前
 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 最大值
---------------------------------------------------------------------------------------------------
2653 次点击
所在节点    程序员
34 条回复
byte10
333 天前
嗯,我觉得当初错了,我觉得你做的这个应该是有应用场景的,适合做一些中间件。

但还是保持一些观点,如果放在业务层去使用,大部分开发人员还是会用 协程去写同步代码,那么就跟你省内存的初衷违背了。
lesismal
333 天前
@byte10 之前好多次没回复我,还以为你把我 block 了 :joy: ,感谢回复!

> 但还是保持一些观点,如果放在业务层去使用,大部分开发人员还是会用 协程去写同步代码,那么就跟你省内存的初衷违背了。

其实你还是误解了,我解释过好多次了,框架本身是提供了逻辑协程池的,用户仍然是同步代码,比如 http handler ,这个跟使用标准库写同步代码没什么区别:
https://github.com/lesismal/nbio-examples/blob/master/http/server/server.go#L19

nbio 里,每个 http 请求到来时取一个协程处理,这个请求处理完了这个协程可以继续服务其他请求,并不是每个连接固定、持续占用一个协程。

c/cpp 那些框架,线程太贵,所以线程池数量少,很多框架是逻辑单线程,所以需要各种回调。
但 golang 协程不一样,协程便宜,百万链接那是协程数量太大了,但是 1-10w 协程压力不大,所以逻辑协程池 size 弄个几千几万个协程是可以的。而且 golang 其他的 io ,比如到数据库,也是有连接池限制的。即使 100w 个逻辑协程也是可能被数据库连接池卡着等待,所以太大逻辑协程数量也意义不大,反倒是几千几万这种协程池数量,已经足够动态均衡了。

总结下就是,逻辑协程数量多但是可配置、不好过硬件能力:
1. 如果是快业务,每次请求处理很快、协程能很快释放给其他请求去复用
2. 如果是慢业务,逻辑协程再多也是要被阻塞,但逻辑协程数量通常远多余下游(比如数据库)的限制,所以仍有足够的空闲协程处理其他请求


所以对于通用需求,根本不存在你先入为主地以为的那种用了 nbio 就要写回调的问题。

而有一些需求,即使是用标准库,也可能是需要写回调的。特殊问题,特殊处理就可以了。

比较均衡的并发模式是:
1. 纵向的不同分层上(比如网络库、框架、业务层),各层限制好自己的资源使用,比如协程池、buffer pool
2. 横向的不同模块上(比如 A 功能 B 功能 C 功能),各模块限制好自己的资源使用,比如协程池、buffer pool


架构是灵活的,人也应该灵活,欢迎来试试 nbio
buffzty
333 天前
用了好久了 没啥问题
lesismal
333 天前
@byte10
但如果想基于 poller 4 层 TCP 去自己实现 7 层框架比如 HTTP/Websocket ,那没什么好办法了,还是需要异步。但 nbio 提供的 http/websocket 并不需要用户全去写异步!不需要!不需要!不需要!我都给你说了好多次了而且这个帖子里也写了 “以前很多次遇到很多人先入为主地以为异步框架就是要写回调、golang 框架也如此。 为了避免误解”

你先看明白了,别再回复我说要异步了!
我快成复读机了快被你们逼疯了!!!
😇😇😇😇😇😇😇😇
lesismal
333 天前
@buffzty 66666
byte10
333 天前
@lesismal 嗯好的,之前也有点固执😂,我多了解下。
lesismal
332 天前
@byte10
嗯嗯,欢迎多来交流,java 只是社区积累的框架多,但性能相关的实在是太不友好了

平时少把 java 搞,内存杀手不环保
重心多往 golang 转,护发节能走得远
monkeyWie
332 天前
好东西,兼容了标准库确实不错
wslzy007
332 天前
唉...C1M 问题很久就有各种方案了(几乎是 10 年前),似乎重要的不是同时多少连接在线(一般只和内存大小相关),而是保持大链接下的高 QPS (调度能力),个人经验 4c 配置 QPS 很容易到达 30w 左右的(小报文)
blankmiss
332 天前
用 rust 不更节能 为什么要特别去在意内存
lesismal
332 天前
> 唉...C1M 问题很久就有各种方案了(几乎是 10 年前),似乎重要的不是同时多少连接在线(一般只和内存大小相关),而是保持大链接下的高 QPS (调度能力),个人经验 4c 配置 QPS 很容易到达 30w 左右的(小报文)

@wslzy007

epoll 异步非阻塞早就解决 C1M 了,这没错,c/cpp 里也早就不是问题了。

但这个帖子说的不是 c/cpp/rust 或者 java netty 或者其他脚本语言那些基于 c/cpp 这些底层,而是 go ,搞 go 的 poller 目的是解决 go 标准库方案每个连接一个协程导致的内存爆炸 OOM 、GC 负担过重 STW 的问题,主要是针对 go 自己,而不是说用 go 解决了其他语言解决不了的 C1M 问题。
lesismal
332 天前
@blankmiss 欢迎来杠。

我想问一下,那 c/cpp 也都性能杠杠的,为啥还搞 rust ?
rust 解决 c/cpp 的安全问题

为啥 go 成为并发、云各种场景下的宠儿?因为它除了性能还不错,更重要的是开发效率高。

java 开发效率高,但是它对于性能场景和硬件消耗实在太不堪入目了,所以 go 替换 java 可以实现节能环保,并且开发效率也高。
别给我杠说什么 java 开发功能快,java 发展了多少年积累了多少轮子?而且很多说 go 开发效率低的真的熟悉 go 吗?而且时间久了 go 的轮子也越来越多越来越完善。如果为了当下谁轮子最多就用谁,那性能、占用之类的各种问题永远没法优化,甚至 java 社区自己都不需要进化、因为现在已经能做功能只是消耗高一些罢了。

你用 rust 更节能环保,你有 go 的这种开发效率吗?再说句不好听的,100 个人学 rust ,有几个能短时间玩熟练的?
c/cpp 三年不出门,go 三天 curd ,你 rust 学多久能快速开发?
blankmiss
332 天前
@lesismal 我觉得 golang 的 if nil 不行,至于 golang 不是 yaml 开发工程师吗。而且我不想在语言上去说什么优劣,谁爱用什么就用什么。(我只是想说 可能现在硬件条件已经很好,动态扩容,不是非常需要说对内存扣扣省省)当然我是个菜比 可能了解的没你深入
lesismal
332 天前
@blankmiss

帖子前置条件里我也说了,绝大多数人没这个扣内存的需求,但是确实也有少量比例的团队需要去扣这里,因为这部分人面临的在线量很大,扣这个能省的成本、对服务稳定性的提高也是很客观的

早几年 uber 就对海量并发各种 手动 gc 优化,甚至去改 go 源码。但其实如果用 nbio ,可能比他们的这些方式更有效一些

> 可能现在硬件条件已经很好,动态扩容,不是非常需要说对内存扣扣省省

动态扩容确实能解决一些问题,但成本也是不小,我知道的一些团队的一些服务,用的 java ,上百个云节点,而且这种公司还不是头部企业的业务量级。如果改成 nbio 这种,往多了说能省 95% 的成本,往少了说,也能省 80%

nbio 关注的用户里,估计大概三分之一是外国人吧,早期就有外国人是来用 nbio 处理高在线量的业务的
ruanimal
332 天前
好奇什么业务会用到百万链接,看测试 echo 都 500ms 以上了
lysS
331 天前
话说怎么测的? 100w 连接端口不够吧?而且 loopback 肯定有优化的
lysS
331 天前
@ruanimal 这么大的连接,几乎做不了重的任务,一般都是简单的 ping-pong ;比如说广告计数、上线通知之类的
lesismal
331 天前
> 好奇什么业务会用到百万链接,看测试 echo 都 500ms 以上了
> 这么大的连接,几乎做不了重的任务,一般都是简单的 ping-pong ;比如说广告计数、上线通知之类的

低配硬件:高在线、低频的功能,比如推送业务
高配硬件:高在线、高频的也可以做。主帖已经说明过了,测试用的 4C-2G "只是作为一个参考指标,用不是特别高的配置更能体现框架的承载力",实际业务可以根据业务需要来部署硬件规格


> 话说怎么测的? 100w 连接端口不够吧?而且 loopback 肯定有优化的

套接口对四元组包括:server ip, server port, client ip, client port ; port 是 short int 2 字节 64k ,加上系统 port range 配置项这些限制,这四元组在 server ip 、client ip 各只有 1 个的情况下,每个 server port 可以建立小于 64k 个连接,N 个 server port 可以建立 64k*N 个连接。测试为了省去虚拟网络或者 docker 方式的麻烦、使用了 50 个端口:
https://github.com/lesismal/go-websocket-benchmark/blob/main/config/config.go#L27
lesismal
331 天前
@ruanimal @lysS #18 回复忘记 at 了,请查看 #18
lesismal
331 天前

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

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

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

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

© 2021 V2EX