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
3672 次点击
所在节点    程序员
41 条回复
contractswif
2023-02-18 13:49:21 +08:00
很棒,收藏了
Nazz
2023-02-18 14:08:58 +08:00
grafana 配置上传到 assets/config 下面了
Trim21
2023-02-18 15:52:24 +08:00
gobwas/ws 怎么个异常法…
Nazz
2023-02-18 16:00:57 +08:00
@Trim21 RPS 只有预期的一半
Nazz
2023-02-18 16:05:17 +08:00
@Trim21 gobwas/ws 单独跑数据是正常的, 晚点放一下数据 😂
Nazz
2023-02-18 16:33:32 +08:00
@Trim21 数据放出来了, gobwas/ws 的 higher-level example, rps, cpu 表现都是最差的
lesismal
2023-02-18 17:31:49 +08:00
@Trim21 @Nazz
1m-go-websocket 和 gobwas/ws 都是错误的方案,有类似线头阻塞或者 for loop 内阻塞的问题的:io for loop 内单个 conn 阻塞(ws Upgrade 、Read 等调用都有可能当前只收到了 half-packet 而导致阻塞读的等待),这样会导致该 poller 的 for loop 内其他 conn 等待,我有在它的 repo 和 example 的 repo 里聊到相关的,但该作者似乎并不想解决问题、或者他们目前的方案(只是自定制了事件监听、读写并不是非阻塞)无法解决反而是 close issue 假装看不见来解决问题。。。
lesismal
2023-02-18 17:33:04 +08:00
所以,没有必要测试 gobwas/ws ,它还不适合用于商业项目
FightPig
2023-02-18 17:33:55 +08:00
gorilla/websocket 不维护了吧
Nazz
2023-02-18 17:40:27 +08:00
@FightPig 是的
Nazz
2023-02-18 17:42:18 +08:00
@lesismal 一开始和 nhooyr 测试的 gobwas ,rps 只有预期的一半😂
后面单独测试,rps 这块还是垫底的
Nazz
2023-02-18 17:53:12 +08:00
@lesismal 正确的道路太艰难,纯异步地解析应用层协议😂
Trim21
2023-02-18 17:54:54 +08:00
@lesismal 看了你的 issue ,没理解错的话,只要少数恶意 ws 客户端就能造成 gobwas/ws 服务器拒绝服务?
lesismal
2023-02-18 18:08:50 +08:00
@Trim21
对。
使用 grilla/websocket 或者其他多数基于标准库的 ws 库,如果有广播业务,也要注意不只是处理读要单独协程,处理写也要单独协程,否则单个 conn 的写可能阻塞,造成类似的 for loop 内其他 conn 等待的问题。
基于 OP 的库也需要注意同样的问题。melody 对 gorilla/websocket 的封装是单独的写协程,代码质量还不错,可以作为参考
lesismal
2023-02-18 18:11:17 +08:00
@Trim21
我总结了下,使用 gobwas/ws 的项目大概需要几个“靠”:
1. 一靠运气好(公网速度稳定)
2. 二靠没人搞
3. 三靠业务小

手动狗头:dog:
liuxu
2023-02-18 18:12:00 +08:00
gws 在 write 时非常直接,一个锁就发数据了,其他 2 个框架 write 前或多或少的用管道或锁处理了下别的状态情况,然后才一个锁发数据
对比来看,gws 用一个全局 emitError 来处理各种错误,有点像整个项目用一个 try 处理,只返回 http code 错误码,可以说是简洁,也可以说是简陋,看个人喜好了

gorilla 现在被 fasthttp 接着开发了: https://github.com/fasthttp/websocket
lesismal
2023-02-18 18:14:50 +08:00
@Nazz #11 很正常,for loop 处理单个慢、其他要等,我在其他帖子里说的跨协程的各种逃逸成本、亲和性差之类的,异步部分只有在连接数达到阈值才有优势,所以我自己的库以前只支持异步的时候性能就是干不过基于标准库的,无奈加上了多种 io 模式支持后、阻塞 io 的 conn 就能干过了。
基于 fasthttp 的 fastws 那个实现好像是个前几年的在校生搞的,性能和资源占用好像更拉跨,也可以对比下看看
lesismal
2023-02-18 18:18:53 +08:00
@liuxu
以前基于 fasthttp 的有个 github.com/dgrr/fastws ,好像有点拉跨。这个基于 gorilla 的改天我也看看

> gws 在 write 时非常直接,一个锁就发数据了,其他 2 个框架 write 前或多或少的用管道或锁处理了下别的状态情况,然后才一个锁发数据

不管是锁还是 chan ,只要不是发送队列而是直接 conn write ,就都需要注意广播业务存在 #14 的问题
Nazz
2023-02-18 18:20:25 +08:00
@liuxu gws IO 错误最终会通过 OnError 返回给用户, 实际上 gorilla 的 IO 错误它内部也已经处理好了, Read/Write 返回的错误是用来退出循环的
lesismal
2023-02-18 18:27:19 +08:00
@Nazz 这样似乎有个问题,就是执行 WriteMessage 的地方没有收到 err 、出错了不好处理后续流程、比如应该踢掉连接,OnError 里收到 err 却不知道 WriteMessage 的上下文

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

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

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

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

© 2021 V2EX