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的另两个手段,是内存池和对象池,不过最好做仔细评估和测试,内存池、对象池使用,也需要对于代码可读性与整体效率进行权衡 。
这种程序一定情况下会降低并行度,因为用池内资源一定要加互斥锁或者原子操作做CAS,通常原子操作实测要更快一些 。CAS可以理解为可操作的更细行为粒度的锁(可以做更多CAS策略,放弃运行,防止忙等) 。这种方式带来的问题是,程序的可读性会越来越像C语言,每次要malloc,各地方用完后要free,对于对象池free之前要reset,我曾经在应用层尝试做了一个分层次结构的“无锁队列”
- 将数据保存到文件中c语言 将数据保存到mongodb
- 罗布人村天气 mysql语言具有的功能
- redis编程语言 redis对应c语言
- redis一般和什么语言一起开发 和redis类似的编程
- mongodb删除数据不释放空间 mongodb如何彻底卸载
- mongodb采用什么语言 mongodb的设计采用什么
- c连接mysql数据库 c连接mysql报错
- redis zset 延迟队列 redis延迟执行
- mongodb底层数据结构 mongodb底层语言
- redis主主同步延迟
