发布个 golang 高性能异步网络框架 nbio,单击百万不是梦!

2021-02-24 16:12:49 +08:00
 lesismal

项目地址

关于另一个框架

设计与实现的一些基本思考和细节方案

  1. 跨平台支持:*nix 用 epoll 、kqueue, windows 用的 std/net
  2. epoll LT,单次读最大可配,避免饿死
  3. epoll 没有使用 trigger 模式,因为较新版本的内核 epoll 相关系统调用以及其他并行流 close 都是安全的,trigger 模式的无锁对性能提升也是伪命题,额外的系统调用以及内核部分的锁仍然是开销,应用层有锁并且单个 Conn 竞争几乎可以忽略,所以 trigger 模式未必有优势
  4. Write 是直接写,写失败才挂到 Conn 的队列,再添加写事件,可写、flush 后清理掉写事件,尽量减少了 epoll/kqueue 的系统调用
  5. 支持 writev
  6. 实现了 net.Conn,支持 DeadLine,并发安全,也方便业务层多个并行流随意操作
  7. 连接的管理,*nix 直接是 fd 做数组下标对应 Conn
  8. 读缓冲、应用层最大写缓冲都可配
  9. 用于读取的内存分配可由应用层定制,方便业务层做更适合的定制,这个不同场景可以玩很多姿势,简单的 pool 未必是最优
  10. Conn 提供 Hash 方法,方便业务层用于消息分发到指定协程进行处理,从而保证每个连接的消息处理有序
  11. 单独的 heap timer,不使用 time.AfterFunc 节约协程
  12. 支持管理标准库的 net.Conn,比如使用 net.Listener Accept 或者 net.Dial 得到 net.Conn 放到 nbio.Gopher 里管理读写
  13. 最少依赖,除了 tls 作为扩展是需要依赖我的另一个仓库以及 go1.6,单纯作为异步框架,nbio 只依赖标准库
  14. server-side/client-side 都支持

与其他一些 golang 异步网络框架对比

  1. 性能:我这里同样配置、参数压测,nbio qps 基本最高,多个框架的 pprof 分析,nbio 的 syscall read/write 占比应该是最高的,进入到 syscall 后的部分是框架层没法再优化的,这说明同一段时间内,nbio 更多的时间是在执行系统调用进行读写、框架本身的消耗占比小于其他框架
  2. 易用性:nbio 实现了 net.Conn,更加业务友好、方便扩展定制,所以我最近花了几天把 go 1.6 std 的 crypto/tls copy 了一份,并重写支持了 nbio 的 tls server-side 、server-side,标准哭 tls 的读写 buffer 有点浪费,还有不少优化空间,但是暂时够用、不着急进一步魔改优化。

一些例子

1. 使用 nbio 管理标准库 net.Conn

2. Echo

3. TLS Examples

更多示例请参考文档和代码

欢迎关注

7544 次点击
所在节点    分享创造
64 条回复
newmlp
2021-02-28 19:48:52 +08:00
单机百万的轮子好多,随便写
lesismal
2021-02-28 19:55:24 +08:00
@wslzy007 只是为了压测做的那种特别简单的 http 解析没什么意义,比如一些网络库加的那个 http echo 压测,只解析了这个测试用的特定的 http 请求,这个写起来也简单,解析到\r\n\r\n 直接回包就行了,但是对于商业项目毫无帮助,用这种压测跟别人完整功能的 http 框架对比性能也没有任何意义。只是对比网络库的话,可以跟其他网络库比吞吐量之类的就可以了,有兴趣可以参考这里:
https://github.com/lesismal/go_network_benchmark

我对 http 的支持可能需要较长时间,暂时规划几步走:
1. http 1x parser 基础功能,为了省体力,考虑直接参考 go 标准库重写解析,尽量兼容标准库,这样的话,wrap 个中间层、其他基于标准库的框架 /router 方案 就能改用 nbio 了
2. tls,现在已经魔改 1.6 标准库的 tls 支持了 nbio 的 non-bloking,但是标准库原来的 tls 代码写得有点性能不友好,很浪费,我还是打算以后有档期了全重写一份,但是目前可以用,暂时不从头搞、太耗体力了
3. websocket 支持
4. http 2

每一项都需要较多时间,慢慢搞
wslzy007
2021-02-28 19:59:40 +08:00
@lesismal 简单报文恰恰可以测试网络库调度能力啊,且直接有效
lesismal
2021-02-28 20:00:07 +08:00
@newmlp 百万连接不是什么难事,难的是大量连接下的高请求量、吞吐量。真实的商业项目也很少真正单机搞百万连接的,避免万一故障。
但是不同框架对高连接数的优化,不只是为了达到百万连接数的目的,而是在这个基础上对整个系统各项资源消耗和指标的优化,对于 golang,同步模式的大量协程带来的内存和 cpu 调度浪费太大,异步框架还是有很大意义的
lesismal
2021-02-28 20:01:51 +08:00
希望能让 go 支持更广泛的业务,否则单就高阶的高并发,还真难干得过 java netty 或者其他异步方案
ERRASYNCTYPE
2021-03-09 14:10:20 +08:00
@byte10 为什么 golang 占的要比 node 高这么多,我的经历是 golang 跟 node 写同样业务的 http 服务,golang 的内存能明显小于 node
byte10
2021-03-09 15:04:04 +08:00
@ERRASYNCTYPE 我也不太清楚,nodejs 的其实也是有协程,跟 Kotlin 应该比较相似。go 的协程叫做纤程,虚拟线程也合适,它更像线程。我估计就是这些占用了内存了吧
lesismal
2021-03-10 12:39:22 +08:00
@ERRASYNCTYPE golang 标准库每个连接一个协程,当前版本一个协程栈应该是 8k,连接数多了内存消耗就巨大了。其他语言多数是异步底层,每个连接不需要固定分配这么大的内存。连接数少的时候 golang 内存浪费不明显,连接数多的时候,会很明显

另外,最近一周多我又撸了份 http 1.x 的 parser,封装了 http server,在 nbio 基础上可以跑 http server 了,兼容标准库 http.Request,百万连接测试请看这里:

https://github.com/lesismal/nbio/tree/master/examples/http/1m

我单虚拟机做这种测试,虚拟机是 6C8T 的,因为客户端服务器都在这个虚拟机,所以没测百万那么多,50w 连接数,2k qps 的情况,server 端 cpu 平均 100%以内( load 小于 1,不到单核),client 请求是多协程并发一直进行的不是平均的所有 server 端有偶发尖刺到百分之几百算正常,内存占用 400-500M
连接数更多、qps 更高内存会消耗更多些,但是跟其他语言异步底层相比,内存已经很省了,并且,50w 连接数,这种简单的 echo 测试,也随便能跑到 qps 5w+,定制少用协程的话可以跑 10w+。

另外,nbio http 从数据的 read 到 parse 到 handle request,这三个流程之间的协程使用都是可定制的,如果是做 nginx 类似的网关代理这种基础设施、不需要数据库等耗时操作,完全可以直接在 read/poller 协程内进行 parse 和 handle request,最大程度减少跨协程数据传递和调度的成本,细节我会在以后的帖子中慢慢整理,最近都忙于编码,还有很多细节或者性能优化可以做
lesismal
2021-03-10 12:45:17 +08:00
@byte10 请看我上一楼的回复,有兴趣的话来跑下 1m 测试,老夫这一两周撸的 http parser 和 nbio 基础之上的 http server,并且兼容标准库的 http.Handler,所以除了 fasthttp 那种没使用标准库的,其他的 gin 、iris 、echo 、beego 等各种,都能比较容易地使用 nbio http 作为网络层,实现内存的巨大节约。不同的业务类型对性能和资源的考量不一样,响应速度和内存可能不能兼得,老夫也正考虑研究下 golang 里更高性能的协程池是否能搞定,如果能搞定,那就可以兼得了

后续还考虑支持 websocket 、http 2.0,但是每一项也都是个大活,http 1.x 已经基本完成,还剩下边边角角的细节优化项和测试,也是要耗费老夫不少时间,慢慢搞,老夫一定要让 golang 更强
lesismal
2021-03-10 12:46:06 +08:00
@lesismal 修正:虚拟机是 6c12t 的
lesismal
2021-03-10 12:51:32 +08:00
@byte10 nbio http 如果想支持 fasthttp 这种也不难,参考默认的支持标准库的 Processor 实现一份 Processor 就行了,不过我暂时没当期在 nbio 自带一份实现给它,以后闲了考虑给它写个
lesismal
2021-03-10 13:21:53 +08:00
@ERR @byte10 为了省去虚拟网络或者 docker 或者多台机器之类的部署环境的麻烦,1m 连接测试的代码是开了多个监听端口,因为即使只是 1 个端口,实际处理接受连接和数据读写的 poller 还是那些个 epoll fd 协程,所以开多个端口对性能影响不大,但是测试起来就方便多了,如果想只用一个端口,代码稍微改下或者各位用自己的 cleint 代码并部署就行了
lesismal
2021-03-10 13:23:12 +08:00
@wslzy007 http 1.x 已经支持,详情请看我上面几楼的回复
lesismal
2021-04-01 11:47:27 +08:00
也不知道楼上有几位都是啥情况,老夫如此热情,连句话都不回,是因为看不懂吗。。。
foam
2021-05-12 23:59:38 +08:00
支持一下
kksco
2021-05-13 16:50:34 +08:00
邮件提醒我 github golang 的一个 issues 有更新,偶然翻到大佬这个仓库,已 star 支持一波
lesismal
2021-05-13 18:16:28 +08:00
@foam 感谢支持!
lesismal
2021-05-13 18:17:04 +08:00
@kksco 感谢支持!太爱 golang 了
towser
2021-08-10 20:29:14 +08:00
两位老哥吵了半天都没有急眼说脏话,君子之争令人愉悦。
shujun
2021-08-24 00:33:04 +08:00
@lesismal 非常棒,支持一下。

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

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

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

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

© 2021 V2EX