go语言http长连接的简单介绍( 五 )


2.网络环境不好引起激增
go协程相比较以往高并发程序,如果做不好流控,会引起协程数量激增 。早期的时候也会发现,时不时有部分主机内存会远远大于其他服务器,但发现时候,所有主要profiling参数都正常了 。
后来发现 , 通信较多系统中,网络抖动阻塞是不可免的(即使是内网),对外不停accept接受新请求,但执行过程中 , 由于对内通信阻塞,大量协程被 创建,业务协程等待通信结果没有释放 , 往往瞬时会迎来协程暴涨 。但这些内存在系统稳定后,virt和res都并没能彻底释放,下降后,维持高位 。
处理这种情况 , 需要增加一些流控策略,流控策略可以选择在rpc库来做,或者上面说的任务池来做,其实我感觉放在任务池里做更合理些,毕竟rpc通信库可以做读写数据的限流,但它并不清楚具体的限流策略,到底是重试还是日志还是缓存到指定队列 。任务池本身就是业务逻辑相关的,它清楚针对不同的接口需要的流控限制策略 。
3.低效和开销大的rpc框架
早期rpc通信框架比较简单,对内通信时候使用的也是短连接 。这本来短连接开销和性能瓶颈超出我们预期,短连接io效率是低一些 , 但端口资源够,本身吞吐可以满足需要 , 用是没问题的,很多分层的系统 , 也有http短连接对内进行请求的
但早期go版本 , 这样写程序,在一定量级情况 , 是支撑不住的 。短连接大量临时对象和临时buffer创建,在本已经百万协程的程序中,是无法承受的 。所以后续我们对我们的rpc框架作了两次调整 。
第二版的rpc框架,使用了连接池,通过长连接对内进行通信(复用的资源包括client和server的:编解码Buffer、Request/response),大大改善了性能 。
但这种在一次request和response还是占用连接的,如果网络状况ok情况下,这不是问题,足够满足需要了 , 但试想一个room实例要与后面的数百个的register,coordinator , saver , center , keeper实例进行通信,需要建立大量的常驻连接,每个目标机几十个连接,也有数千个连接被占用 。
非持续抖动时候(持续逗开多少无解),或者有延迟较高的请求时候,如果针对目标ip连接开少了,会有瞬时大量请求阻塞 , 连接无法得到充分利用 。第三版增加了Pipeline操作,Pipeline会带来一些额外的开销,利用tcp的全双特性,以尽量少的连接完成对各个服务集群的rpc调用 。
4.Gc时间过长
Go的Gc仍旧在持续改善中,大量对象和buffer创建 , 仍旧会给gc带来很大负担,尤其一个占用了25G左右的程序 。之前go team的大咖邮件也告知我们,未来会让使用协程的成本更低,理论上不需要在应用层做更多的策略来缓解gc.
改善方式 , 一种是多实例的拆分 , 如果公司没有端口限制,可以很快部署大量实例,减少gc时长,最直接方法 。不过对于360来说,外网通常只能使用80和433 。因此常规上只能开启两个实例 。当然很多人给我建议能否使用SO_REUSEPORT,不过我们内核版本确实比较低,并没有实践过 。
另外能否模仿nginx,fork多个进程监控同样端口,至少我们目前没有这样做,主要对于我们目前进程管理上,还是独立的运行的,对外监听不同端口程序,还有配套的内部通信和管理端口,实例管理和升级上要做调整 。
解决gc的另两个手段,是内存池和对象池,不过最好做仔细评估和测试,内存池、对象池使用 , 也需要对于代码可读性与整体效率进行权衡 。

推荐阅读