-
Notifications
You must be signed in to change notification settings - Fork 193
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
异步的优势 #4
Comments
@lvht 我开发 gev 的初衷也并不是为了来写业务逻辑,这样的异步框架更适合用来构建更为底层的基础设施,比如反向代理程序、消息队列等。 个人认为,这类 go 异步框架,使用更少的内存,速度更多,适用于一些有特殊需求的场景。 https://www.freecodecamp.org/news/million-websockets-and-go-cc58418460bb/ |
这一类场景使用 go 并无优势呀。c/c++/rust 都是更好的选择。 |
开发效率高,代码维护性好,rust我不了解,但开发效率,可维护性远比c/c++好。 |
go 本来就是牺牲性能换取可维护性的。对于你提到的场景,都是足够简单,用 c/cxx 开发是没有问题。典型的是 Nginx 和 Envoy。我不赞成一种语言打天下,本来设计哲学就不一样。 |
go 相对于c/c++性能稍差,但是可维护性高。我尝试在特殊场景平衡两者,而且我并不认为回调会大幅降低可维护性。 |
这种网络库稳定之后,应该不需要怎么维护吧 |
异步对于1M或者以上的长连接是很有必要的 |
这个issue可以开着做长期讨论 |
可以,听舒畅的👀 |
我觉得网络库很大的作用在于把业务和底层的处理隔离开来,在远古时期使用c/c++没有网络库的情况下还需要手动的listen,bind巴拉巴拉等等反智的行为,并且有太多的tcp细节需要处理。即使是到了go的时代,如果一个server还是从listen,accept开始写,这种行为依然是反智的。重复的底层劳动没有意义,把这些操作封装起来反复使用并且及时维护,这就是网络库存在的价值。 |
我有个疑问,net包里的TCPConn本身就是基于epoll进行封装的,作者为何基于原始epoll-go接口来写gev? |
@MrChang0 这种反智代码能占不了多少行吧 |
net包基于epoll封装了自己的go网络模型,可以同步的方式达到异步的效果。gev是纯异步非阻塞的。在绝大部分场景没啥区别,且go网络模型更好用,但是如果是长连接且活跃的连接不多,异步网络模型只是hold住那些不活跃的连接,而go网络模型会分配goroutine,所以相对内存消耗会多一些,但真的多不了多少,简单算一个goroutine 2k,一百万才2G。。。 一百万连接的应用能有几个啊,哈哈哈 |
Go 语言就是用来解决反智的异步回调问题的。本项目又用 go 重新撸了一个异步回调框架…… |
我不是很能理解作者的意图。goroutine的benchmark我们在1.5版本的时候就做过,那个时候goroutine所占用的内存就已经相当相当少了,使用者几乎不用做任何优化,其实从官方说法上就能知道:goroutine几乎不会有内存瓶颈。 |
作者的意图很明显——撸轮子 |
|
协成多了cpu在调度上花费很多,目前正在做一个长连接接入服务,一条连接有两个协成分别进行读写,没请求的话单机能抗400w连接,但是实际场景是一条连接维持20s左右,一条连接平均发1次请求,结果一台机子只能抗住30w连接,qps也才1w多,pprof跑了下,调度占了快一半了。我准备换成这个框架看下效果 |
😄对框架有疑惑的地方,欢迎提出👏 |
websocket升级的时候需要把http header保存下来的,里面的数据后面conn处理请求要用到的,现在看起来没法做到,需要修改框架支持, |
connection应该有个唯一ID,可以提供在这条连接上保存和读取自定义数据的能力,否则需要全局维护连接和数据的对应关系,目前像客户端版本号,平台这些数据还是很需要的,统计用,push的时候也能做到根据自定义条件push |
用websocekt,调用conn send发送消息失败,看了下是因为websocket消息的封装没在protocol的pack里面做,是在on message里面做的,这么做可能是因为websocket的发送是有messageType和data两部分,这个可以通过修改 protocal pack 和 unpack函数的实现来做吧 |
websocket 相关的,我们在另一个 issue 下讨论,这个 issue 保留讨论异步相关的。 |
服务替换成这个后,同时并发连接数由30w提高的41w,qps 1.5w提高到2w,提升挺大的.缺点就是目前不够完善,需要二次开发才能使用,目前线上服务流量挺大的,也不敢用于生产环境直接测试. |
其实最大的好处就是,goroutine 虽然lightweight 但是架不住多,如果成千上万没有问题,但是一百万呢?所以说原生Epoll主要是为了省内存,但是头条的实践已经告诉我们,这个东西毕竟还是goroutine 驱动的,所以会有可能变成串行的 |
如果看了这些例子,楼主还是坚持认为go由于为了同步优势牺牲了异步姿势导致海量并发的性能损失而无法处理该类业务场景,或者异步库必须得异步,那我不知道该怎么解释了,我只能建议,talk is cheap,还是上代码来对比吧,书上或者日常积累到的观点有很多正确的沉淀,但那并不是全部,尽信书不如无书,在处理具体问题的时候,还是要从实际出发 |
就技术论技术,说话可能不懂礼貌,如有冒犯,再请见谅! |
你说的应该就是 reactor 模式。本质上是把网络IO/协议编解码跟业务逻辑处理分开。两部分都可以用并发的方式实现,但本质都是池子。如果业务逻辑用同步的方式,还是有阻塞的问题。以前是用线程池实现,现在用协程实现,本质都是池子。业务处理的吞吐量一定小于网络处理的吞吐量,主要矛盾在业务处理这一端。另一端用不用异步根本没有太大区别。 如果真是要处理你所谓的海量并发,那一定是协议处理跟业务处理分开。有单独的长连接集群只负责处理网络IO和协议解析,最终把业务数据转发给后台业务逻辑集群来处理。但这么一来,长连接相关的服务就变得相对简单(因为只有IO和编解码),就不一定非得用 go 来实现了。Nodejs,甚至 c/c++ 都可以胜任。 |
c/c++做这些还是要回调,nodejs有了Promise、async/await虽然看上去舒服了一点,但是仍然也是设计回调的时序问题,比如一个Promise: function callback() {
...
}
Promise(callback)
print("log before callback") 写法上看上去是先调用callback了?但实际上是先执行的是print,callback应该是在nodejs/libuv的下一次event loop中执行
所以说,还请再看下我昨晚上的内容:
造成阻塞的,是那些真正的公共资源比如数据库、缓存、rpc service,这些涉及io的基础设施。而对于golang,不管是标准库一连接一协程,还是异步网络库+协程池(size可以自定制),业务协程数量都可以合理高于基础设施的可并发量,比如sql连接池100,连接数100k,业务协程池5000,这是足够用的 c/c++,nodejs,同样也是会在公共资源这里受限,在受限的公共资源基础之上,更加高效地利用软硬件资源的角度,golang异步库+业务池是最平衡的一种方案。 |
前面聊的基本都是基于web方面的,再举个例子,比如游戏,即使用golang做游戏,也不是用全同步的,因为游戏的交互复杂,并不全是“请求-响应”这种交互,还有很多主动推送。 for _, c := allConnections {
c.Write(...)
} 如果是直接对每个连接这样Write,其中某些连接可能网络状况不好、拥塞导致tcp窗口满了之类的,c.Write阻塞了,阻塞1s,则这个广播,其他的连接数都被阻塞,这是一次广播阻塞一轮,而且实际生产环境有的连接可能不只阻塞1s,我遇到过阻塞15s然后因为Deadline而close的。 SetDeadline也解决不了广播的问题 for _, c := allConnections {
// 这里即使加上deadline也不能解决,因为对于高实时性的游戏业务,即使设置100ms的deadline也是不合理的
c.SetWriteDeadline(...)
c.Write(...)
} 所以,游戏服务如果使用标准库的方案,通常每个连接需要开两个协程,一个读,另一个select {<-chan default} 接收message然后再发送,这样才能确保广播不被阻塞 |
所以,楼主来聊异步的优势的观点其实是有点搞反了:因为看到了gev这些异步库,觉得异步反倒不如标准库方案 |
再补充一些。 |
所以你看阿里有搞openresty,但是呢?他们现在中间件团队又大量用go在搞。c/c++ bind lua我也搞了很多,直到遇到了golang,再也没回头过,有了golang,其他语言不值得。 |
说了这么多,刚才review了下gev的websocket,是异步的stream parser,没有gobwas+netpoll那种可能导致服务慢、服务不可用的缺陷,挺好,已star |
七周七并发这种书,适合用来了解大概,但太浅了,真的搞,就还是CSAPP APUE UNP linux内核那些老书,“啃老”啃明白点,就不会被那些除了独孤九剑的各大派花架子剑谱忽悠了 |
1m-go-tcp-connection 没有实现streaming-parser,gobwas/ws+netpoll 那个也是,慢连接随便就能把他们的服务搞死,写个简单代理中转数据的时候完整包拆分中间加点延时就能随便复现。我最近几个月给他们解释了好几次了但get到的人很少,作者们竟然还曾提出用SetDeadline之类的来解决,搞得我自己都郁闷了 |
@lvht rawepoll 还是很有必要的. 你不认同rawepoll, 很大原因是你没遇到高并发和大量长连接的场景。 社区中为啥uber, mosn和字节跳动都有使用 rawepoll 框架? 都是为了解决性能问题。 |
@rfyiamcool 没有不认同 epoll。我只是认为如果 rawepoll 是强需求,那可能 go 就不是最好的选择。 |
mosn那个rawpoll好像还没覆盖全业务,可能还是缺少很多异步parser吧,他们使用的easygo性能比较一般,而且使用起来也是比较麻烦: |
应该可以干翻java netty、nodejs那些了,go的方案能达到性能和开发效率各方面都很平衡,极致性能是比c/c++/rust要差一些,但已经非常好了 |
异步库性能并不是一定强于标准库,影响二者性能方面比较多,比如:
但总归来讲,对于海量并发,至少内存的节约是很客观的,协程数量节约到一个阈值后调度成本的节约也能让异步库性能优势更好 以上都是我最近几个月实现和优化的时候遇到的问题,还有一些优化空间,还有其他一些影响的点 |
u1s1,是不是最好的选择应该由使用者决定,而不是由语言决定,使用者要经过多方面的考虑:团队能力、规模、业务变动、拓展维护...... |
评论精彩! |
一百万的goroutine会不会给go的调度带来问题? |
感觉没必要郁闷,你去攻击他们服务器一次,该郁闷的就是他们了。 |
百万goroutine倒也是可以,有些厂是单进程80-100w连接/协程的,但需要很好的硬件配置,并且再高就更难了,交互频率大了不只是调度,内存、gc的压力大、stw会更明显 |
感谢各位大佬们的精彩讨论!! |
@lesismal 大佬,啥时候写点这方面的博客,系统介绍介绍
|
@szl-926929 golang的异步库其实跟c/c++实现类似,但是协程、内存池的策略细节更多些,全写够出本书了,暂时没那么多精力,异步这块内容有兴趣的话欢迎先读读我源码吧 |
udp 今天刚过一个提案,不知道是否有帮助。大佬感觉可以出一个博客系列讲解下 golang 原生网络方案和你实现的 asyncio + callback(parse) + groutine pool 的架构上的不同,可以讲粗一点,细节可以一笔带过让感兴趣的啃源码。 |
@kscooo
但是每个部分涉及到的细节真要展开说、讲清楚,其实也确实有点难,我不太擅长,所以好多次提笔写了些然后又停下了 其实如果有一定网络基础的兄弟直接读源码可能理解得更快些,如果有疑问随便开issue交流就好了 udp这个提案我也订阅下看看后续 |
@lesismal 谢谢,我之前也研究过一阵社区的实现,大概的原理我应该还能了解,但是像 nbio、宇宙条做的一些优化暂时还没看,等有机会把这个坑填了看能不能写个文章系统介绍下。大佬可以开个 tg/slack 或者微信群方便交流,不过这方面可能基于 issues 这种文字讨论更不容易灌水。 |
@kscooo 宇宙条的优化方向不太一样,他们的netpoll我实测性能没有达到预期,不知道是不是我代码写得不正确: 而且他们netpoll在连接数多、高并发时的占用甚至比标准库高得多,而且对于单个连接,他们好像并不是为了解决海量并发的问题、而是为了尽量提高他们的kitex rpc框架的响应性能,而且交流过程中他们负责人提过kitex也不是针对连接数很多的场景。 所以他们的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框架支持的业务场景丰富得多。 |
对于没有 |
go 使用协程+阻塞的模式来处理并发问题。这样的模式虽然对运行时要求很高,但对程序员却非常友好。这样的代码码也非常容易维护。
异步模式最大的问题就是回调嵌套,项目大了根本没法维护。我就是不想用回调方式写业务代码才转 go 的。
你认为这类 go 语言的异步框架有什么优势,要解决什么问题?
The text was updated successfully, but these errors were encountered: