并发超时控制模型如上图所示,在模型中引入了“Concurrent Ctrl”模块,这个模块属于微服务熔断功能的一部分,用于控制客户端能够发起的最大并发请求数。并发超时控制整体流程是这样的 首先,客户端发起 RPC 请求,经过“Concurrent Ctrl”模块判断是否允许当前请求发起。如果被允许发起 RPC 请求,此时启动一个协程并执行 RPC 调用,同时初始化一个超时定时器。然后在主协程中同时监听 RPC 完成事件信号以及定时器信号。如果 RPC 完成事件先到达,则表示本次 RPC 成功,否则,当定时器事件发生,表明本次 RPC 调用超时。这种模型确保了无论何种情况下,一次 RPC 都不会超过预定义的时间,实现精准控制超时。 Go 语言在1.7版本的标准库引入了“context”,这个库几乎成为了并发控制和超时控制的标准做法,随后1.8版本中在多个旧的标准库中增加对“context”的支持,其中包括“database/sql”包。 性能 Go 相对于传统 Web 服务端编程语言已经具备非常大的性能优势。但是很多时候因为使用方式不对,或者服务对延迟要求很高,不得不使用一些性能分析工具去追查问题以及优化服务性能。在 Go 语言工具链中自带了多种性能分析工具,供开发者分析问题。 CPU 使用分析 内部使用分析 查看协程栈 查看 GC 日志 Trace 分析工具 下图是各种分析方法截图 在使用 Go 语言开发的过程中,我们总结了一些写出高性能 Go 服务的方法 注重锁的使用,尽量做到锁变量而不要锁过程 可以使用 CAS,则使用 CAS 操作 针对热点代码要做针对性优化 不要忽略 GC 的影响,尤其是高性能低延迟的服务 合理的对象复用可以取得非常好的优化效果 尽量避免反射,在高性能服务中杜绝反射的使用 有些情况下可以尝试调优“GOGC”参数 新版本稳定的前提下,尽量升级新的 Go 版本,因为旧版本永远不会变得更好 下面描述一个真实的线上服务性能优化例子。 这是一个基础存储服务,提供 SetData 和 GetDataByRange 两个方法,分别实现批量存储数据和按照时间区间批量获取数据的功能。为了提高性能,存储的方式是以用户 ID 和一段时间作为 key,时间区间内的所有数据作为 value 存储到 KV 数据库中。因此,当需要增加新的存储数据时候就需要先从数据库中读取数据,拼接到对应的时间区间内再存到数据库中。 对于读取数据的请求,则会根据请求的时间区间计算对应的 key 列表,然后循环从数据库中读取数据。 这种情况下,高峰期服务的接口响应时间比较高,严重影响服务的整体性能。通过上述性能分析方法对于高峰期服务进行分析之后,得出如下结论: 问题点: GC 压力大,占用 CPU 资源高 反序列化过程占用 CPU 较高 优化思路: GC 压力主要是内存的频繁申请和释放,因此决定减少内存和对象的申请 序列化当时使用的是 Thrift 序列化方式,通过 Benchmark,我们找到相对高效的 Msgpack 序列化方式。 分析服务接口功能可以发现,数据解压缩,反序列化这个过程是最频繁的,这也符合性能分析得出来的结论。仔细分析解压缩和反序列化的过程,发现对于反序列化操作而言,需要一个”io.Reader”的接口,而对于解压缩,其本身就实现了”io.Reader“接口。在 Go 语言中,“io.Reader”的接口定义如下: 这个接口定义了 Read 方法,任何实现该接口的对象都可以从中读取一定数量的字节数据。因此只需要一段比较小的内存 Buffer 就可以实现从解压缩到反序列化的过程,而不需要将所有数据解压缩之后再进行反序列化,大量节省了内存的使用。 为了避免频繁的 Buffer 申请和释放,使用“sync.Pool”实现了一个对象池,达到对象复用的目的。 (责任编辑:本港台直播) |