-
Notifications
You must be signed in to change notification settings - Fork 95
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
non-blocking write性能、消耗、时序 #3
Comments
基于标准库阻塞io的websocket,melody虽然也有些浪费的地方,但它在gorilla基础上的封装方式是做得比较不错的,可以参考下: |
async read/write都是可选的,对时序有要求可以采用同步模式. 它们都通过了autobahn的测试,相比同步模式多了一些not strict. |
事实上,我们跑benchmark才会出现单连接高并发的情况,实际业务锁竞争不会特别激烈. |
下午去看看借鉴下 |
这两条的前提都是基于运气好。业务量再小,只要时间足够,也会遇到问题——墨菲定律了解一下。 如果基础设施的代码基于运气,谁敢用啊兄弟,:joy: |
时序的问题,如果你想节省协程也可以做到,消息入队列如果是队首则启动go后for循环去发送队列里的数据,这样也能做到没有待发送的数据时不用占用协程,类似我这里: 你现在的workerqueue size设置为1、do的时候改造下判断是否队首应该就可以。目前没有判断队首直接改size为1应该是不行的,并发AddJob会乱 |
并不是运气. 对于线上环境, 单连接内出现高并发, 十有八九是DDOS. 而且, 我这套基于任务队列的IO模型是有改进空间的. channel并发写之所以高效, 是因为并行写入内存(加锁解锁时间很短), 串行写入net.Conn. |
被你发现了, 我就是不想增加常驻协程. |
1.14后,go的调度是抢占式的了,即使纯cpu消耗的代码也可能被中途打断的。所以这并不是你的并发量有多大的问题,只要发送队列大于等于2,基于墨菲定律,就肯定会发生。 |
就墨菲定律来说, 你无法100%保证所有用户的QoS, 就像机械效率无法达到100%. |
nbio里使用的默认协程池没任务的时候就是退出的不占用资源的,但是基于自然均衡的需求,留了一个带size chan当队列的常驻协程,一个协程成本无所谓。 |
计算机科学的分层思想很有道理。就可靠性来讲,我觉得:
而且这个问题,完全可以做到更好的可靠性代码+小于等于当前方案的消耗+更低的理论消耗上限 |
这个workerqueue设计得挺好,但更适合用于通用协程池的场景。单个conn这种有时序要求的,再特化一下会更好 |
字节的那个workerpool本质也是类似的,细节差异罢了 |
再加一个有锁队列(RWMutex),可以达到channel的效果 |
从我的concurrency库复制过来的. 看过字节和ants两个库,都没看明白原理😂 |
字节的主要是自己的简单链表+pool略有优化,用的for循环,你的是数组+递归,本质上你俩这个是相同的。 ants写的太复杂,我没深入读过它代码,简单扫了下应该是用的条件变量,类似c/cpp那套。因为写的太复杂,而且我看issue里有人遇到一些莫名其妙的bug,而且较高版本go里我benchmark了下它更慢,所以就更没打算看它源码了。 我那个协程池为了减少一个协程池只有单个数组或者链表时锁操作的竞争浪费,保留了一个常驻协程用chan做队列,当前并发度没有达到协程数量限制时则只要一个原子操作,比无竞争锁需要加锁解锁省掉一些原子操作,比竞争时try lock部分自旋节省更多,以一个常驻协程代价换更均衡的。对于单个conn做了上面说的那个时序保证、但这是conn自己的部分、并不是协程池本身的功能。我这里单独实现了一个有序化的,相当于把conn上那个拆出来了,可以配合任何协程池使用: |
应该是不需要用RWMutex的,这种队列+线程/协程池,通常是需要push/pop的原子性保障,读锁并发时没法保证push/pop的原子性,所以可能会带来bug。 |
一个常驻协程无所谓, 连接上面的就可观了 |
回到问题本身,不管是否优化协程占用,应该严格保证发送顺序、而不是基于运气默认认为不会出现不一致。 |
我说的一个常驻协程是一个协程池只有一个常驻,一个nbio Engine上的所有conn共用同一个或几个这种协程池,并不是像gws这种一个conn上一个workerpool,所以不存在协程数量可观的问题。 |
对于传统的c/cpp那些框架,我这个时序的方案也是适用的,这样至少可以让多逻辑线程被均衡利用起来。不只是单个conn时序保证,还可以根据模块,每个模块继承/集成一个这种队列+线程池,就能做到逻辑多线程+各种时序保证了 |
时序问题在v1.3.1已经解决了. 肝了一天, 累死了, 异步IO真是让人头大. |
这。。。我简单review了下,应该是没解决的。我展开了说,你看一下是不是这个道理: 1.3.1是每次WriteAsync都先入队列,然后AddJob,每个job执行时是doWriteAsync,但是doWriteAsync是每次先取出所有再循环发送。 今天头大了可以先停一停,休息下散散步喝喝茶,等思路清晰了再改,否则一下子卡住可能越改越乱,我以前也是,好几次肝到凌晨5点甚至早上8点,整个人状态都不好了。。。 |
这里的85-88行取出所有后解锁了,下面循环发送的过程中,别的地方可能又WriteAsync并触发了新的job异步执行: 这里取所有如果只加锁、等所有发送完再解锁,那其他地方WriteAsync又可能阻塞,所以这里取所有message不管加不加锁都不是正解。我前面说不要使用RWMutex、保证不了原子性也是类似的原因。 |
写入顺序就是入队顺序, 已经跑通了Autobahn测试, 没有再出现额外的几个Non-Strict了. 暂未发现异常, 跑Benchmark性能更强了 😂. |
还有一点,nbio的阻塞io的websocket conn的写,是按配置项决定是否开启单独协程负责写的。用户如果不需要广播,不开启就能省点协程。但是如果需要广播、用户配置了异步写,我是用单独的常驻协程处理异步写的,没有做成这种动态协程,因为动态协程性能要差一些,跨协程变量逃逸、调度亲和性都要差。 |
一些问题压测是测不出来的,autobahn也跑通也不代表就没有问题啊。。。 |
改了再看吧, 今天改休息了 |
实际业务场景会加限流来避免这种情况. 但我还是会加容量限制, 不然心里不舒服 |
用chan的话,就可以自然限流了,非常均衡圆满的感觉,哈哈哈 |
不喜欢使用channel, 感觉太重了 |
其实chan不重。rpc里每次call都会创建一个,用完就不管了,不close也没什么副作用 |
经常需要搭配go for select一起使用, 如果还有多个case, 开销就上来了, 而且代码很丑... |
这可是比手撸cond_t+mux轻松多了。。。 |
话说我还没试过手撸cond_t+mux方式的协程池,不知道能不能做到比现在这个更快,按道理应该是不能更快,我之前测试ants的性能也没感觉除强来,所以不知道ants为啥那么做:joy: |
gws wq优化之后, ants变成性能最差的了, nbio任务队列性能一骑绝尘 |
这玩意根本就不是和线程池类似的概念 |
是啊,线程池还是得各种异步回调,callback hell,而且如果是c/cpp还时刻考虑着内存管理怕泄露,太虐心 |
这里加个go关键字就不会栈溢出了 |
每次新开一个协程,如果真的排队大于1切新协程是性能不划算的,你这么想递归是讳疾忌医:joy: |
两种方式性能相差不大吧, 开了新协程:
|
升级版workQueue表现挺不错的, 而且实现方式最简单
|
多换一些参数试试。系统资源、入队和并发度会有阈值。比如poolsize比较大多数时候都新协程,适合实际业务中带有io的任务,因为io阻塞的时候新协程可以继续,避免像我复现的字节那个排队问题。但纯cpu消耗的,并发度再高也没办法提升核心数量对应的计算能力,所以反倒可能是并发度设置的低、多数时候入队等旧的协程执行,避免了新协程的切换消耗。 |
不同硬件和测试参数跑出来的数据都不太一样,我自己机器上测,nbio并不是每种参数都最好,但是整体稳定并且适合nbio自带的http和websocket,尤其是http因为肯定会有response的io |
CPU密集型任务测试结果还算稳定, IO不好测误差大 |
我笔记本上一些测试参数+低消耗demofunc,字节的那个跑出来的数据快极了,比直接go还快两倍之类的,我分析可能就是协程切换少,这玩意压测的实际并发竞争调度比较混沌而且测试参数太多,跑profile又是很累,所以我也没profile分析,就是结合源码想象,所以也不知道准确不。要是有时间可以多参数每个框架一轮烤机profile看下:joy: |
感觉任务队列这一块IO优化的空间很小, redis get测试:
|
不是IO优化,是并发度与协程复用率,你多试试不同的poolsize |
从测试结果来看, 很多时候不复用也没什么问题, 相信runtime的调度能力 |
不是复用或者不复用有问题,我再解释细点。
实际场景里2的这种纯cpu消耗类任务比较少见。 不同场景要具体分析,并不是哪种好不好,还得结合实际测试数据针对性调优。 |
很细微的调优了 |
感觉我有毒, 老是把API改来改去的. 下次更新变化蛮大的
|
希望是最后一次大改吧,感觉是越来越成熟了,提供的配置项越来越多了... |
趁着用户少赶紧改。nbio我总是考虑旧版本兼容性所以还保留着一些初版的内容看着别扭:joy: |
大家都有相同的烦恼 |
跑了下pprof, 主要都是IO开销, 感觉可优化的空间不大了 |
也对,加io的话,其他消耗相比之下就太小了,难分析 |
简单看了下,每个conn上自带一个size为8的workerQueue,但是这样似乎有几个问题:
而单个conn的消息,很多时候是需要时序保证的。比如流媒体推流需要广播不能允许被单个conn卡其他conn,所以就得WriteAsync,但是推送的数据顺序错乱了音视频就乱码了丢帧了。比如游戏一些玩家操作顺序广播出去,本来是ABC,用户收到变成了CAB。。。
兄弟我建议这块就按常规简单的方式来,单个协程+limited size chan+select default/timeout循环发送就可以了,继续这样下去好像越走越远了。。。
The text was updated successfully, but these errors were encountered: