发现的问题
在微服务开发中,gRPC 的应用绝对少不了,一般情况下,内部微服务交互,通常是使用 RPC 进行通信,如果是外部通信的话,会提供 https 接口文档
对于 gRPC 的基本使用可以查看文章 gRPC介绍
对于 gRPC ,我们需要基本知道如下的一些知识点:
-
gRPC 的基本四种模式的应用场景
- 请求响应模式
- 客户端数据流模式
- 服务端数据流模式
- 双向流模式
- Proto 文件的定义和使用
-
gRPC 拦截器的应用 , 基本的可以查看这篇 gRPC 拦截器
- 实际上有客户端拦截器 和 服务端拦截器,具体详细的可以自行学习
- gRPC 的设计原理细节
- Go-Kit 的使用
当然今天并不是要聊 gRPC 的应用或者原理,而是想聊我们在开发过程中很容易遇到的问题:
- 未复用 gRPC 客户端连接,影响性能
最近审查各个服务代码中,发现整个部门使用 gRPC 客户端请求服务端接口的时候,都是会新建一个连接,然后调用服务端接口,使用完毕之后就 close 掉, 例如这样
这会有什么问题呢?
正常简单的使用不会有啥问题,但如果是面临高并发的情况,性能问题很容易就会出现,例如我们在做性能测试的时候,就会发现,打一会性能测试,客户端请求服务端的时候就会报错:
rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: Error while dialing dial tcp xxx:xxx: connect: connection refused
实际去查看问题的时候,很明显,这是 gRPC 的连接数被打满了,很多连接都还未完全释放
那这个时候,简单思考一下,我们是没有必要对于每一次客户端请求服务端接口的时候,都新建立一次连接,并且调用完毕之后就马上关闭连接
我们知道,gRPC 的通信本质上也是 TCP 的连接,那么一次连接就需要三次握手,和四次挥手,每一次建立连接和释放连接的时候,都需要走这么一个过程,如果我们频繁的建立和释放连接,这对于资源和性能其实都是一个大大的浪费
我们还知道 gRPC 是一个高性能、开源和拥有统一规定的 RPC框架,面向对象的 http/2 通信协议,能够能节省空间和 IO 密集度的开销 ,但是我们并没有很好的将他运用起来,gRPC 服务端的连接管理不用我们操心,但是我们对于 gRPC 客户端的连续非常有必要关心,咱们要想办法复用客户端的连接
gRPC 连接池
复用连接,我们可以使用连接池的方式
对于这种复用资源,我们其实也接触了不少,例如复用线程 worker 的线程池,go 中的协程池 …
简单来说,连接池 ,就是提前创建好一定数量的 tcp 连接句柄放在池子中,咱们需要和外部通信的时候,就去池子中取一个连接来用,用完了之后,咱们就放回去
连接池解决了什么问题
很明显,连接池解决了上述咱们频繁创建连接和释放连接带来的资源和性能上的损耗,咱们节省了这部分开销后,自然就提高了咱们的性能
可是我们再次思考一下,如