发布个 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 条回复
byte10
2021-02-25 15:54:40 +08:00
@guotie 没有啥大价值,netty 那么强,直接搞 netty 就可以了。golang 和 C 一样都是喜欢造轮子,每个人都在造轮子,这就是这种语言的特性,低级语言的特性(相对 java,python 这些)。

@lairdnote 他是要搞底层优化,上层还是会用协程的。他是底层 NIO 作为网络并发编程,别人的大部分框架是底层 epoll+协程来实现网络并发编程。本质没多大的区别,他把编程开发是否异步还是同步交给上层业务来选择,但是业务层一定是要用协程的,不然就是大菜鸟了。所以结论是,还不如直接实现一份同步的网络并发框架。另外要搞 nio 的话,直接用 netty 就很好了。
FrankAdler
2021-02-25 17:45:36 +08:00
@byte10 好奇什么是“餐桌鸡 架构师”,搜了下没找到,应该不是个啥梗?
byte10
2021-02-25 17:49:09 +08:00
@FrankAdler 哈哈我自己创作的非主流 梗,放在餐桌上吃的鸡就是一道菜,菜鸡。
king888
2021-02-25 21:25:53 +08:00
@byte10 #20 '百万连接的测试我也做过,netty 占用了 2G,nodejs 占用了 5g,golang 也是 13 个 G'
能否说下测试详情?
byte10
2021-02-26 09:30:51 +08:00
@king888 一台机器运行 30 个 docker 容器作为客户端(用的是 nodejs 作为 websocket 客户端)每个容器跑 3.5 万个连接,每个连接随机 10 秒请求一次后台,算下来就是平均每秒 10w 并发。然后服务端是 8 核的 AMD-4800U,设置后文件打开数量限制,正常跑 netty 回复信息。实际下来每秒处理 10w 请求还可以。如果并发 20w 的话,客户端收到消息回复就会平均在 2 秒多。nodejs 在单线程情况下,表现挺不错的,如果部署多个进程完全利用好 8 核,吞吐量可以大大超过 netty,当然内存也上去了,也是内存换出来的吞吐量。
lesismal
2021-02-26 20:31:14 +08:00
@byte10 linux 下 nbio 普通压测,客户端服务器共用单机 localhost 2w 连接数,2w 个 client echo 压测不停收发,payload 比较小只用了 64 字节,4c8t/4g 的虚拟机,ps -aux 查看内存占用 0.4%或者 20M 左右,qps 大概 25-30w,如果只是连接数较高、qps 不高,除了 nbio.Conn 的结构体按比例扩大下内存占用(估计百字节左右乘以个连接数),处理请求使用的内存也不会太大

测试代码在这里,有兴趣的话可以来试试:
https://github.com/lesismal/go_network_benchmark/blob/master/nbio/nbio.go

ps:linux 下每个 Conn 是挂到对应的 poller 上,poller 每次 loop 的协程执行读,所以这个 poller 上的所有 Conn 可以共用这个 readbuffer,读到一个处理一个,当然如果需要解析、粘包 save 下次继续解析之类的,内存占用会更高,但是由于没有使用标准库的协程模型,已经把协程的资源消耗降到最低了,否则单个协程,go 不同版本有 2k 、4k 、8k,不记得现在是不是 16k 了,反正百万连接,较新版本的 go 单单用于创建协程就已经 8G 甚至十几 G 了
但是 nbio 或者其他的异步框架,不存在协程消耗巨大内存的瓶颈
lesismal
2021-02-26 20:35:49 +08:00
@king888 请看我上一楼回复的测试数据,有兴趣的话可以尝试下测试代码的链接。不是做 http 测试,因为暂时没有支持异步框架的完整功能的纯 go http parser,我打算闲余时间愚公移山搞一套
nbio 或者其他的 golang 异步框架,正是为了解决类似这种 13G 的问题
lesismal
2021-02-26 20:37:12 +08:00
@byte10 如果觉得搞这种异步框架也没用,那是因为社区还没有足够多的支持 golang 异步网络库的业务层框架比如异步网络库之上的 web 框架
别着急,老夫抽空慢慢搞,或者其他哪位大神大仙,早晚会有人搞出来
lesismal
2021-02-26 20:43:05 +08:00
至于 netty,确实挺不错的,但是要真说强,咋都当 c/cpper 灭绝了?比性能都不带上他们,这太不讲武德了

另外,我无意挑起语言之争,只是个人审美,喜欢简洁,java 本身强于社区而非语言本身,但是性能也并不足够强,否则 google 当年引进 python 失败就不至于搞 go 了( google 被 cpp 虐惨了,看 py 牛逼,想引进 py 解决工程问题,也正是那个时段,py 之父去了 google,但是后来 google 醒了,发现 py 的性能实在坑,所以又放弃了,但是没有选择 java,而是搞了 go,也正是 go 开始逐渐成型后,py 之父离开了 google,虽然 py 之父的来去未必都是直接联系,但也一定程度上是 google 内部技术升级的时代使然)

还是那句话,如果 java 真的足够好,天下一统,全都 java 不就完事了?为啥今年独角兽、明星企业大量搞 go 为啥 java 份额下降了?

但是,如果新手选技术路线,我还是建议 java,因为好岗位多、待遇好,赚钱第一嘛~
lesismal
2021-02-26 20:44:58 +08:00
上一楼错别字:近年独角兽

不能编辑,这个真的有点难受
wslzy007
2021-02-27 23:36:22 +08:00
用实测数据说话吧: [golang 主流高性能 web 框架性能测试] https://yq.aliyun.com/articles/721540
fucUup
2021-02-28 15:39:19 +08:00
我是谷狗的,虽然我们闭源 stubby 库是 c++的不是 go,为啥你网络库用水平触发,谷狗是边缘触发的 。。不要问我然后商业机密源码
lesismal
2021-02-28 17:30:36 +08:00
@wslzy007 不要用不一样的东西进行对比。nbio 目前只是网络库,不是 http 框架,所以没有意义。
另外,性能对比涉及很多方面,连接数、并发请求频率、payload 、应用层框架功能完整度、针对性优化等,每个点的差别都能带来较大的性能差异。

golang 的异步网络库主要优势是面对大量连接数场景下,节约大量的协程数量,从而减少对应的巨大内存消耗和协程调度成本。当连接数较少时比如几百几千个连接数(系统硬件资源不同这个阈值不同),异步网络库不会有明显优势,但是连接数上升到数万甚至数十万、上百万时,标准库的绝大资源消耗和性能下降会非常明显
lesismal
2021-02-28 17:47:47 +08:00
@fucUup 目前没有测试表明 ET 更快,实际上只作为网络库压测的情况,ET 和 LT 能达到基本相同的吞吐量,很多选择 ET 的可能是出于对 epoll_ctl 说明以及对 LT 用法不当导致的误解:
https://man7.org/linux/man-pages/man7/epoll.7.html

When used as an edge-triggered interface, for performance
reasons, it is possible to add the file descriptor inside the
epoll interface (EPOLL_CTL_ADD) once by specifying
(EPOLLIN|EPOLLOUT). This allows you to avoid continuously
switching between EPOLLIN and EPOLLOUT calling epoll_ctl(2) with
EPOLL_CTL_MOD.

但实际上 LT 也可以避免不必要的 syscall,可以参考 nbio 的实现

ET 和 LT 各有利弊,比如 ET,需要谨慎处理读写、避免 bug 导致 hang up,而 LT 处理逻辑更简单( nbio 的方式,其实可能比 ET 更省 syscall,因为不需要每次读完再去重新 epoll_ctl )

ET 要求单次 event loop 读完单个 fd 的所有,但其实,如果这单个 fd 上面数据非常大,比如多个应用层协议,反倒可能造成这单次 loop 内处理它耗时长、其他 fd 的等待,而 LT 单次 event loop 内单个 fd 可以不全读完,可以控制数量避免其他 fd 等待太久,反而能更公平

另外,redis 是用的默认 LT,muduo 陈老板也是默认 LT
nginx 年代较早,man 手册的建议以及 ET 也并不比 LT 性能差,所以即使 LT 实现逻辑更 easy 也没必要改
lesismal
2021-02-28 17:49:09 +08:00
@fucUup 还有云风的 skynet,印象中也是 LT 的
fucUup
2021-02-28 18:08:42 +08:00
@lesismal 原理都是公开的,发明了几十年的,我们不要谈原理,但是工程质量往往决定了成败,比如开源的 MapReduce 实在太弱鸡了
fucUup
2021-02-28 18:16:29 +08:00
rpc 请求一般限制 4mb 以内,公网条件还会更少,你框架有无做限制
lesismal
2021-02-28 18:51:20 +08:00
@fucUup 兄弟,你上面问的是为什么你们谷狗用 ET 而我的用 LT,那我不给你讲原理和实现原因,该怎么回答你?难道因为你们某个项目用了 ET 所以就应该用 ET ?那我是不是可以回答因为 redis 用了 LT 所以用 LT ?

工程质量,谷狗项目用啥并不是工程质量的唯一标准。另外,这里说网络库,网络库跟 MapReduce 这两者本来就是处于基础设施的不同层次,网络仓库作为数据传输的基础设施,MapReduce 是在网络或者其他整合生产消费的数据层之上的层,更多的是需要对数据生产消费的整合和加工,跟网络库本身没有可比性
lesismal
2021-02-28 18:55:54 +08:00
@fucUup 网络框架这一层处理数据收发。rpc 请求的限制,你在 rpc 框架收发数据的层完全可以自己限制。
兄弟,分层的理念你可以再加深下理解,不同的层处理自己的事情,从内核到应用,或者网络协议栈不同的层,都是各自处理自己层内的事情,该留给更高层去处理的留给更高层去处理,否则就拿你 rpc 4M 的来举例,如果网络库这一层写死了限制 4 M,你在其基础之上实现的 rpc 框架舒服了,但是其他人在其基础之上实现的其他业务可能需要大于 4M,那怎么办?所以,下层在自己层内资源可控的前提下提供上层所需的扩展机制、接口,而不是下层直接做这些限制
wslzy007
2021-02-28 19:44:07 +08:00
@lesismal 网络库最终还是要加载协议的,哪怕是简化版协议也行啊,等你的 http 协议对接后我测测。用数据说话最直接

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

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

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

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

© 2021 V2EX