go websocket rps, cpu, latency 全面测评

2023-02-18 10:34:46 +08:00
 Nazz

本来想测试四款websocket package的, 但是gobwas/ws数据太异常还是不放了. 今天测试的三个库分别是

测试代码地址: github

Env

WebSocket Protocol

正确性高于性能, 首先测试一下WebSocket协议. 每个包基本都是用的默认配置,为了节省时间,本项测试关闭了压缩. 可以看到,尽管 gorilla/websocket 和 nhooyr/websocket 宣称通过了所有 autobahn-testsuite 测试,但可能还需要开发者额外写一些代码.

docker run -it --rm \
  -v ${PWD}/config:/config \
  -v ${PWD}/reports:/reports \
  crossbario/autobahn-testsuite \
  wstest -m fuzzingclient -s /config/fuzzingclient.json
package Pass Info Non-Strict Unclean Failed
lxzan/gws 294 3 4 0 0
gorilla/websocket 223 3 0 85 75
nhooyr/websocket 173 3 0 0 125

RPS

// 1000 connections, 500 messages/second, 1000 Byte Payload
tcpkali -c 1000 --connect-rate 500 -r 500 -T 30s -f assets/1K.txt --ws 127.0.0.1:${port}/connect
Destination: [127.0.0.1]:8000
Interface lo address [127.0.0.1]:0
Using interface lo to connect to [127.0.0.1]:8000
Ramped up to 1000 connections.
Total data sent:     12919.8 MiB (13547411965 bytes)
Total data received: 12854.5 MiB (13478908970 bytes)
Bandwidth per channel: 7.178⇅ Mbps (897.2 kBps)
Aggregate bandwidth: 3594.175↓, 3612.441↑ Mbps
Packet rate estimate: 316194.9↓, 581166.7↑ (3↓, 2↑ TCP MSS/op)
Test duration: 30.0017 s.
Destination: [127.0.0.1]:8001
Interface lo address [127.0.0.1]:0
Using interface lo to connect to [127.0.0.1]:8001
Ramped up to 1000 connections.
Total data sent:     7077.0 MiB (7420776528 bytes)
Total data received: 7089.8 MiB (7434174595 bytes)
Bandwidth per channel: 3.961⇅ Mbps (495.1 kBps)
Aggregate bandwidth: 1982.319↓, 1978.746↑ Mbps
Packet rate estimate: 272613.9↓, 173441.2↑ (2↓, 12↑ TCP MSS/op)
Test duration: 30.0019 s.
Destination: [127.0.0.1]:8002
Interface lo address [127.0.0.1]:0
Using interface lo to connect to [127.0.0.1]:8002
Ramped up to 1000 connections.
Total data sent:     5103.5 MiB (5351431830 bytes)
Total data received: 5140.6 MiB (5390317539 bytes)
Bandwidth per channel: 2.856⇅ Mbps (357.0 kBps)
Aggregate bandwidth: 1437.359↓, 1426.990↑ Mbps
Packet rate estimate: 135048.1↓, 124004.1↑ (1↓, 14↑ TCP MSS/op)
Test duration: 30.0012 s.

Latency

  PID   USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  18305 caster    20   0  720780  38116   7332 S 248.8   1.0  24:29.55 gorilla-linux-amd64
  18325 caster    20   0  720952  52544   7180 S 161.1   1.3  15:57.80 gws-linux-amd64
  18346 caster    20   0  721460  50064   7364 R 311.3   1.3  20:49.94 nhooyr-linux-amd64

  PID   USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  19430 caster    20   0 1070196 395408   6924 S 294.0   9.9   3:44.56 gws-linux-amd64
  19618 caster    20   0  930480 267108   7268 S 313.0   6.7   9:01.10 gorilla-linux-amd64
  20939 caster    20   0 1067980 372916   7236 R 455.8   9.3  12:12.72 nhooyr-linux-amd64

Final Result

可以看到, 除了内存, 每一项都是

gws > gorilla >> nhooyr
3715 次点击
所在节点    程序员
41 条回复
Nazz
2023-02-18 18:32:00 +08:00
@lesismal 断开连接是自动的
DefoliationM
2023-02-18 18:32:21 +08:00
前几天也搞了个 weboscket,写的比较简单,就是包装 header+data,直接写到 tcp 里,感觉又简单性能还好。
Nazz
2023-02-18 18:33:09 +08:00
@lesismal 使用 channel 异步写会增加一倍的常驻协程,我更倾向于广播的时候开启一小批临时协程
Nazz
2023-02-18 18:42:08 +08:00
@DefoliationM CloseCode 和错误处理比较恶心
Nazz
2023-02-18 18:48:49 +08:00
@lesismal 以我多年 crud 的经验来看,似乎很少有人关心 write 是否返回了错误. 一般来说,在 write 之前业务逻辑都处理好了,或者开启了协程去处理错误,有错误关闭连接退出就好
Nazz
2023-02-18 18:50:13 +08:00
@lesismal 笔误,开启了协程去处理业务逻辑.
Nazz
2023-02-18 19:19:22 +08:00
@lesismal 标准库对于广大中小企业够用了;大厂没准会自研
Nazz
2023-02-18 19:28:56 +08:00
@lesismal 异步 IO 适合「海量连接,少量活跃」的场景,连接太少跑 benchmark 就吃亏了😆

我就和基于 std net 的库比一比
Nazz
2023-02-18 19:37:37 +08:00
@lesismal async write 这块我还有个 idea ,可以维护一个全局的 WriteMessageQueue
lesismal
2023-02-18 21:45:36 +08:00
@Nazz

> 断开连接是自动的
> 以我多年 crud 的经验来看,似乎很少有人关心 write 是否返回了错误. 一般来说,在 write 之前业务逻辑都处理好了,或者开启了协程去处理错误,有错误关闭连接退出就好

不是能不能断开的问题,而是 WriteMessage 后及时性的问题。按 go 的习俗,应该是 if err != nil 就处理了,但是 WriteMessage 不返回 err ,如果这后面还有其他逻辑,就造成了后面逻辑代码的浪费,虽然整体可能不影响,但你提供 err 返回毕竟也没什么复杂度。标准库也好、其他 repo 也好大家都是这样,你这里的设计会显得不符合习惯。
而且其实这样做,相当于是基于标准库的同步 io 、能够顺序代码的情况下,糅杂进来了异步框架的回调机制。nbio 的 OnError 这些也是回调,但那是因为 io 和逻辑协程是在不同协程,为了避免用户再占用一个协程来处理,只能回调,并且 nbio 的 WriteMessage 这些也是提供 err 返回的,所以也还算符合 go 的习惯

> 使用 channel 异步写会增加一倍的常驻协程,我更倾向于广播的时候开启一小批临时协程
> async write 这块我还有个 idea ,可以维护一个全局的 WriteMessageQueue

这两种实际使用的前提都是:业务无所谓,卡了就卡了吧

但对于工程严谨性和高实时要求的业务而言,都无法允许你说的这两种策略,因为都解决不了我说的问题,只要你是在单个循环中处理多个 conn ,就都可能存在一些健康 conn 被某些网络不佳的 conn 造成卡顿的问题。
举个例子,RPG 游戏,地图上广播的消息非常多,如果某个玩家的连接卡了,其他人都跟着卡,这是不可接受的,否则游戏服务提供者上线用不了多久就可以解散项目了,除非项目本来也没人、本来挣不到钱。
其他的游戏类型,比如 FPS 、Moba 类、其他 PVP 的动作类,都是同样的无法忍受这种一人卡多人的问题的。
复杂的 APP 业务同样会存在类似的消息推送及时性需要。
既然是做通用框架,就不要投机取巧了。协程多了硬件不够用,你还可以加机器解决,但是业务卡了公司都可能直接倒闭的,这是不行的。技术方案该硬刚的地方需要硬刚,该加机器的时候就要加机器。

> 我就和基于 std net 的库比一比

nbio 是支持直接使用标注库 std net 、不使用 nbio 自己的 poller io 的,这里例子代码就是基于标准库的,另外也有描述多种 io 模式的特点、可以根据自家业务特性来 cover 不同场景:
https://github.com/lesismal/nbio/releases/tag/v1.3.5
lesismal
2023-02-18 21:52:49 +08:00
@Nazz
对于异步写的问题,其实 gws 只对标 gorilla/websocket 的话就可以不考虑它,因为只是提供 ws 的基础库,gorilla/websocket 和 net.TCPConn 也同样需要用户自己去封装读写。
但是用户使用涉及广播时,应该建议用户自己注意写阻塞可能导致的问题。
而且读写个一个协程虽然并不复杂,但细节也并不那么简单,一致性、时序性、timeout 等细节,都需要仔细处理。而且我个人就遇到过 5+以上的朋友来让我帮忙 review 他们 ws 的代码,其中有些代码的封装,其实他们是知道写阻塞可能导致的问题的,所以他们封装了单独协程+chan 写的代码,但一些细节上仍然存在问题比如导致 panic 、状态不一致、timer 泄露甚至协程泄露等。
Nazz
2023-02-18 22:05:40 +08:00
@lesismal 下次更新把写操作的错误返回加上吧. 不打算在 gws 里面对广播场景做优化了, 如果有需要, 开发者可以自己为每个连接多开一个协程, 对于不需要处理广播的业务场景就节省了一般的协程. 确实, 用少量协程处理大量连接写入, 碰到大量慢连接的场景会很卡.
Nazz
2023-02-18 22:07:11 +08:00
@lesismal 好的, 我去 README 里面提醒下
lizhenda
2023-02-19 00:13:48 +08:00
支持,自己项目还没替换 gorilla/websocket ,等有空了试试
lizhenda
2023-02-19 00:16:44 +08:00
gorilla/websocket 目前用的不爽的地方是如果是主动关闭链接,需要手动调用 CloseHandler ,而不是自动的
Nazz
2023-02-19 07:35:48 +08:00
@lizhenda gws api 更清晰一些
Nazz
2023-02-19 09:09:59 +08:00
@lizhenda gws 在连接生命周期管理这块可以说是自动挡, IO 过程中一旦出现错误就会关闭连接
Trim21
2023-02-25 19:54:34 +08:00
@lesismal 之前的回复没提醒,又看到 v2 有 websocket 相关的帖子才想起来这个帖子...

那个作者还写了一篇 medium 博文,他是用在 mailru 的,应该不算是业务小吧 ...
lesismal
2023-02-25 22:30:57 +08:00
@Trim21
不知道他有没有把 gobwas/ws 用于他们大业务量的项目。
但 gobwas/ws 的实际问题是存在的,所以跟它用到哪里了不是直接关系,有问题就是有问题,只是可能问题影响范围他们可以接受,但并不代表别人的业务也能接受,尤其是被别人盯上随便来攻击一下的时候,影响要更大得多。
而且你看他的 issue 列表里也确实是别人提出了生产环境遇到了问题,我在那里讨论,他都是想着 timeout 之类的方式去解决,但那并不能解决问题。所以我才说它需要“三靠”才能稳定。。。但总不能大家项目都“三靠”保平安吧。。。
Trim21
2023-02-26 03:51:45 +08:00
@lesismal 会不会他们的 ws 服务器是运行在反向代理后面的,反向代理做了 buffer 所以没遇到这个问题…

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

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

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

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

© 2021 V2EX