Allenxuxu / gev

🚀Gev is a lightweight, fast non-blocking TCP network library / websocket server based on Reactor mode. Support custom protocols to quickly and easily build high-performance servers.
MIT License
1.73k stars 194 forks source link

异步的优势 #4

Open taoso opened 5 years ago

taoso commented 5 years ago

go 使用协程+阻塞的模式来处理并发问题。这样的模式虽然对运行时要求很高,但对程序员却非常友好。这样的代码码也非常容易维护。

异步模式最大的问题就是回调嵌套,项目大了根本没法维护。我就是不想用回调方式写业务代码才转 go 的。

你认为这类 go 语言的异步框架有什么优势,要解决什么问题?

woshihuo12 commented 2 years ago

我有个疑问,net包里的TCPConn本身就是基于epoll进行封装的,作者为何基于原始epoll-go接口来写gev?

net包基于epoll封装了自己的go网络模型,可以同步的方式达到异步的效果。gev是纯异步非阻塞的。在绝大部分场景没啥区别,且go网络模型更好用,但是如果是长连接且活跃的连接不多,异步网络模型只是hold住那些不活跃的连接,而go网络模型会分配goroutine,所以相对内存消耗会多一些,但真的多不了多少,简单算一个goroutine 2k,一百万才2G。。。 一百万连接的应用能有几个啊,哈哈哈

一百万的goroutine会不会给go的调度带来问题?

woshihuo12 commented 2 years ago

我有个疑问,net包里的TCPConn本身就是基于epoll进行封装的,作者为何基于原始epoll-go接口来写gev?

net包基于epoll封装了自己的go网络模型,可以同步的方式达到异步的效果。gev是纯异步非阻塞的。在绝大部分场景没啥区别,且go网络模型更好用,但是如果是长连接且活跃的连接不多,异步网络模型只是hold住那些不活跃的连接,而go网络模型会分配goroutine,所以相对内存消耗会多一些,但真的多不了多少,简单算一个goroutine 2k,一百万才2G。。。 一百万连接的应用能有几个啊,哈哈哈

我不是很能理解作者的意图。goroutine的benchmark我们在1.5版本的时候就做过,那个时候goroutine所占用的内存就已经相当相当少了,使用者几乎不用做任何优化,其实从官方说法上就能知道:goroutine几乎不会有内存瓶颈。

https://colobu.com/2019/02/23/1m-go-tcp-connection/

1m-go-tcp-connection 没有实现streaming-parser,gobwas/ws+netpoll 那个也是,慢连接随便就能把他们的服务搞死,写个简单代理中转数据的时候完整包拆分中间加点延时就能随便复现。我最近几个月给他们解释了好几次了但get到的人很少,作者们竟然还曾提出用SetDeadline之类的来解决,搞得我自己都郁闷了

感觉没必要郁闷,你去攻击他们服务器一次,该郁闷的就是他们了。

lesismal commented 2 years ago

我有个疑问,net包里的TCPConn本身就是基于epoll进行封装的,作者为何基于原始epoll-go接口来写gev?

net包基于epoll封装了自己的go网络模型,可以同步的方式达到异步的效果。gev是纯异步非阻塞的。在绝大部分场景没啥区别,且go网络模型更好用,但是如果是长连接且活跃的连接不多,异步网络模型只是hold住那些不活跃的连接,而go网络模型会分配goroutine,所以相对内存消耗会多一些,但真的多不了多少,简单算一个goroutine 2k,一百万才2G。。。 一百万连接的应用能有几个啊,哈哈哈

一百万的goroutine会不会给go的调度带来问题?

百万goroutine倒也是可以,有些厂是单进程80-100w连接/协程的,但需要很好的硬件配置,并且再高就更难了,交互频率大了不只是调度,内存、gc的压力大、stw会更明显

gukz commented 2 years ago

感谢各位大佬们的精彩讨论!!

szl-926929 commented 2 years ago

@lesismal 大佬,啥时候写点这方面的博客,系统介绍介绍

异步库性能并不是一定强于标准库,影响二者性能方面比较多,比如:

  1. 标准库方案与异步库方案连接数量阈值
  2. 异步解析的复杂度
  3. 如果io和业务协程池分开又涉及跨协程传递的额外调度亲和性降低
  4. 不同业务类型(cpu消耗和io消耗)对于整体调度的成本考量
  5. 如果异步库使用内存池,则又涉及跨协程内存传递与生命周期管理,还有跨协议栈/层数据传递与生命周期管理,很复杂
  6. 还有异步库由于不同协议栈/层与业务层之间的跨协程,导致小对象(比如Conn)难于精确释放,比如poller层面收到需要close的信号时如果释放小对象并归还到Pool,应用层此时可能仍然持有该对象,如果应用层释放前该对象又被其他地方取出,则对象脏了会出问题。并且框架层难于要求业务层去保障这个释放时机,如果要求应用层按照一定的方式使用,既难又又业务不友好,所以这些基础小对象类型比较难用Pool优化 fasthttp那种仍然是一个连接对应一个协程,它不管是小对象还是内存buffer,用池优化都相对容易,异步框架Pool优化的难度大很多

但总归来讲,对于海量并发,至少内存的节约是很客观的,协程数量节约到一个阈值后调度成本的节约也能让异步库性能优势更好

以上都是我最近几个月实现和优化的时候遇到的问题,还有一些优化空间,还有其他一些影响的点

lesismal commented 2 years ago

@szl-926929 去年只做了TCP/TLS/HTTP1.x/Websocket,还想完善更多比如HTTP2.0、HTTP3.0/QUIC,怕现在先整理了内容后面又升级了。HTTP2.0比较渣,做api并不比Websocket强,做文件服务器4层线头阻塞问题还不如1.x。 最近有人提3.0/QUIC的需求,QUIC是基于UDP,我之前没考虑支持UDP,因为标准库的UDP已经足够方便,封装起来很简单,所以没必要在poller库里支持,而且我的库早期定位就是TCP相关,所以提供的接口比如OnOpen/OnClose也是针对TCP,对于UDP本身是不存在这两种接口的。但是QUIC确实挺好,而且目前社区里go实现的QUIC跟TCP也类似,海量并发场景都会消耗巨大数量的协程甚至比TCP消耗得更多。我倒是挺想以后有档期支持下QUIC的,为了与已有的能整合到一起,所以最近也在给自己的库增加UDP的支持,但是不像其他poller框架那样只是支持io、每次读到数据还需要用户自己去判断UDP 4层的映射关系,我默认支持了这种4层映射,实现方式也简单,就是对端发来第一个UDP包时OnOpen,之后每次OnData回调时传递给用户的Conn都是同一个,SetDeadline相关超时或业务层关闭之类的就OnClose,这样对使用者来说更有好些。先把UDP弄上,HTTP3.0/QUIC是个大活,暂时还没排期,以后有档期了撸一波

golang的异步库其实跟c/c++实现类似,但是协程、内存池的策略细节更多些,全写够出本书了,暂时没那么多精力,异步这块内容有兴趣的话欢迎先读读我源码吧

kscooo commented 2 years ago

@szl-926929 去年只做了TCP/TLS/HTTP1.x/Websocket,还想完善更多比如HTTP2.0、HTTP3.0/QUIC,怕现在先整理了内容后面又升级了。HTTP2.0比较渣,做api并不比Websocket强,做文件服务器4层线头阻塞问题还不如1.x。 最近有人提3.0/QUIC的需求,QUIC是基于UDP,我之前没考虑支持UDP,因为标准库的UDP已经足够方便,封装起来很简单,所以没必要在poller库里支持,而且我的库早期定位就是TCP相关,所以提供的接口比如OnOpen/OnClose也是针对TCP,对于UDP本身是不存在这两种接口的。但是QUIC确实挺好,而且目前社区里go实现的QUIC跟TCP也类似,海量并发场景都会消耗巨大数量的协程甚至比TCP消耗得更多。我倒是挺想以后有档期支持下QUIC的,为了与已有的能整合到一起,所以最近也在给自己的库增加UDP的支持,但是不像其他poller框架那样只是支持io、每次读到数据还需要用户自己去判断UDP 4层的映射关系,我默认支持了这种4层映射,实现方式也简单,就是对端发来第一个UDP包时OnOpen,之后每次OnData回调时传递给用户的Conn都是同一个,SetDeadline相关超时或业务层关闭之类的就OnClose,这样对使用者来说更有好些。先把UDP弄上,HTTP3.0/QUIC是个大活,暂时还没排期,以后有档期了撸一波

golang的异步库其实跟c/c++实现类似,但是协程、内存池的策略细节更多些,全写够出本书了,暂时没那么多精力,异步这块内容有兴趣的话欢迎先读读我源码吧

udp 今天刚过一个提案,不知道是否有帮助。大佬感觉可以出一个博客系列讲解下 golang 原生网络方案和你实现的 asyncio + callback(parse) + groutine pool 的架构上的不同,可以讲粗一点,细节可以一笔带过让感兴趣的啃源码。

lesismal commented 2 years ago

@kscooo 我们这些poller框架在架构上与标准库的整体异同其实比较简单,主要就几个部分:

  1. 同步与异步:syscall层面,标准库与poller框架,都是异步的IO
  2. 阻塞与非阻塞:标准库的File与runtime结合、提供给应用层的是阻塞的IO接口,比如TCPConn.Read/Write,而poller框架提供的是非阻塞的接口
  3. 协议解析:标准库是同步读、顺序解析、自上而下,half-packet只要ReadFull等读全了就行,更容易;poller框架需要考虑half-packet缓存、多次解析,更麻烦
  4. 协程数量:标准库每个连接至少1vN协程,poller框架连接数与协程数量可以使MvN,M可以远大于N
  5. 内存优化:标准库就是每个连接的协程里对应需要多少就用多少,优化方式是go的一些基本姿势比如尽量复用buffer以及减少gc的一些策略;poller框架则类似c/c++了,但是也需要结合sync.Pool或者其他,会更麻烦一点,这个不同业务可以有不同的策略来优化

但是每个部分涉及到的细节真要展开说、讲清楚,其实也确实有点难,我不太擅长,所以好多次提笔写了些然后又停下了

其实如果有一定网络基础的兄弟直接读源码可能理解得更快些,如果有疑问随便开issue交流就好了

udp这个提案我也订阅下看看后续

kscooo commented 2 years ago

@lesismal 谢谢,我之前也研究过一阵社区的实现,大概的原理我应该还能了解,但是像 nbio、宇宙条做的一些优化暂时还没看,等有机会把这个坑填了看能不能写个文章系统介绍下。大佬可以开个 tg/slack 或者微信群方便交流,不过这方面可能基于 issues 这种文字讨论更不容易灌水。

lesismal commented 2 years ago

@kscooo slack试试这个,欢迎来撩:https://join.slack.com/t/arpcnbio/shared_invite/zt-1esuz619h-SbKacrYHNdbkNmO9SV~X1A 免费版邀请链接之前好像是30天有效期现在15天了:joy:

宇宙条的优化方向不太一样,他们的netpoll我实测性能没有达到预期,不知道是不是我代码写得不正确: https://github.com/lesismal/go-net-benchmark/issues/1

而且他们netpoll在连接数多、高并发时的占用甚至比标准库高得多,而且对于单个连接,他们好像并不是为了解决海量并发的问题、而是为了尽量提高他们的kitex rpc框架的响应性能,而且交流过程中他们负责人提过kitex也不是针对连接数很多的场景。 整体而言,他们的netpoll并不适合更通用的业务场景比如面向公网(网络质量不稳定)的服务。 之前给他们提过issue也没什么下文我就没继续研究了,有兴趣可以到他们repo里搜下我相关的issue。

所以他们的netpoll其实和其他的poller框架没什么可比性,完全是为了解决不同问题的框架,为了提高而牺牲的东西都不一样。

可能是他们结合kitex做了深度的协议解析的内存复用之类的、针对他们的场景能够提高性能。我跑他们自己的测试时kitex的性能一般、kitex-mux数据不错,但是他们的测试代码也并不是完全的对齐,比如kitex-mux用的连接池size设置为2、和别人不一样,他们只压测少量连接数的响应数据、并没有压测更多连接数时的server响应数据,而实际场景中不可能每个server只处理2个连接。而且目前除了我自己的arpc,其他rpc框架基本都是handler返回即调用结束、所有handler都是异步、不支持简单cpu消耗的handler直接同步返回,这样会浪费一些协程切换和逃逸之类的响应性能,实测这种简单cpu消耗的handler用arpc同步处理也确实比他们数据好得多,arpc这块相关的刚好这两天有人问到:https://github.com/lesismal/arpc/issues/41#issuecomment-1236634004, 而且推送之类的都支持、比普通rpc框架支持的业务场景丰富得多。

lxzan commented 1 year ago

@lvht rawepoll 还是很有必要的. 你不认同rawepoll, 很大原因是你没遇到高并发和大量长连接的场景。 社区中为啥uber, mosn和字节跳动都有使用 rawepoll 框架? 都是为了解决性能问题。

@rfyiamcool 没有不认同 epoll。我只是认为如果 rawepoll 是强需求,那可能 go 就不是最好的选择。

对于没有 c++/rust 程序员的公司, 纯go方案就是最好的选择