解读 | TARS开源项目发布Go语言版本
Server 使用 package net 的 listener 来管理 TCP 和 UDP 连接,使用多个 Goroutine 进行 accept,并将 accept 之后的 net.Conn 经过 SendQueue chan 交给后端的 Handler 进行处理。Handler 由一堆 woker Goroutine 组成,每个 Goroutine 基于 net.Conn 进行收发包、Tars 协议解码,并经过 dispatcher(由 tars2go 生成) 来调用用户的代码实现,然后将结果编码成二进制流返回给 Client。Server 也包含一些 Goroutine 实现远程日志异步上报等功能,防止同步调用阻塞请求。 编者了解到,Tars 开源团队在研发 Tars-Go 的过程中经历的对其各个方面的性能调优改造,Tars-Go 在早先的版本注重于功能的开发和完善,没有体系化地进行压测和性能分析。在业务使用一段时间后,开始注重性能优化。Tars 开源团队对 tars2go 工具先进行了一轮优化,在生成语树的时候生成好了类型信息,避免使用反射进行类型判断,编解码的效率提升了 2 倍,然后对再对整体 servant 进行了一轮轮压测,并进行 CPU profile 性能分析。 下面是性能提升优化的几个实例: Timer 性能问题 每个请求进来,Tars-Go 会创建一个协程进行处理,因为要处理调用超时,会创建一个 timer,在结束的时候会删掉 timer,当并发量一上来,就会频繁创建和删除 timer,占用服务大量 CPU 时间。 研发团队在一个 issue 中发现 ,在多 CPU 的场景下,如果存在大量的 timer,性能就会大量损耗,优化方式是每个 p 有自己的 timer,这样可以大幅提升整体并发性能。于是 Tars-Go 将编译环境升级至 1.10.3,从 profile 来看,性能得到了很大的提升,并且基于时间轮询算法实现了自己的 timer,以精度换取性能和效率。 net 包的 SetDeadline 调用性能问题 为了设置网络连接的读写超时,Tars-Go 使用了 net 包的 SetReadDeadline/SetWriteDeadline 等相关调用,但从 profile 发现,当并发非常大的时候,会导致这两个调用占用了大量的 CPU 时间。为了绕开这两个相关调用,使用了 Sysfd 进行 Socket 读写超时的设置。 bytes 的 Buffer 带来的性能问题 从下图可以看出,有相当大的一部分时间耗在了 slice 相关的操作上,原来在包的编解码过程中,使用 bytes.Buffer 进行临时存放,当 bytes.Buffer 底层用的 byte slice 大小不够的时候,就会分配一定的内存空间,频繁地分配效率很低,所以导致大包情况下性能下降比较明显。 联想到了 Redis 的内存模型和 Linux 的 slab 机制,对于频繁创建销毁的对象,采用预先创建和重复利用的方式。而 Go 本身提供了一种 sync.Pool 机制,供临时对象的复用,以减少 GC,Tars-Go 在此基础上,实现了类似 Linux slab 机制分配的 buffer 管理方案,通过这种方案,性能大幅提高。 其他方面的优化 (编辑:衢州站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |