trpc.group/trpc-go/trpc-go@v1.0.3/docs/user_guide/client/connection_mode.zh_CN.md (about) 1 [English](connection_mode.md) | 中文 2 3 # tRPC-Go 客户端连接模式 4 5 6 # 前言 7 8 目前 tRPC-Go client,也就是请求发起的一方支持多种连接模式,包括短连接,连接池以及连接多路复用。client 默认使用连接池模式,用户可以根据自己的需要选择不同的连接模式。 9 `注意:这里的连接池指的是 tRPC-Go 自己实现的 transport 里面的连接池,database 以及 http 都是使用插件模式将 transport 替换成开源库,不是使用这里的连接池。` 10 11 # 原理和实现 12 13 ### 短连接 14 15 client 每次请求都会新建一个连接,请求完成后连接会被销毁。请求量很大的情况下,服务的吞吐量会受到很大的影响,性能损耗也很大。 16 17 使用场景:一次性请求或者请求的被调服务是老服务,不支持在一个连接上接受多个请求的情况下使用。 18 19 ### 连接池 20 21 client 针对每个下游 ip 都会维护一个连接池,每次请求先从名字服务获取一个 ip,根据 ip 获取对应连接池,再从连接池中获取一个连接,请求完成后连接会被放回连接池,在请求过程中,这个连接是独占的,不可复用的。连接池内的连接按照策略进行销毁和新建。一次调用绑定一个连接,当上下游规模很大的情况下,网络中存在的连接数以 MxN 的速度扩张,带来巨大的调度压力和计算开销。 22 23 使用场景:基本所有的场景都可以使用。 24 注意:因为连接池队列的策略是先进后出,如果后端是 vip 寻址方式,有可能会导致后端不同实例连接数不均衡。此时应该尽可能基于名字服务进行寻址。 25 26 ### 连接多路复用 27 28 client 在同一个连接上同时发送多个请求,每个请求通过序列号 ID 进行区分,client 与每个下游服务的节点都会建立一个长连接,默认所有的请求都是通过这个长连接来发送给服务端,需要服务端支持连接复用模式。IO 复用能够极大的减少服务之间的连接数量,但是由于 TCP 的头部阻塞,当同一个连接上并发的请求的数量过多时,会带来一定的延时(几毫秒级别),可以通过增加连接多路复用的连接数量(IO 复用默认一个 ip 建立两个连接)来一定程度上减轻这个问题。 29 30 使用场景:对稳定性和吞吐量有极致要求的场景。需要服务端支持单连接异步并发处理,和通过序列号 ID 来区分请求的能力,对 server 能力和协议字段都有一定的要求。 31 注意: 32 33 - 因为连接多路复用对每个后端节点只会建立 1 个连接,如果后端是 vip 寻址方式(从 client 角度看只有一个实例),不可使用连接多路复用,必须使用连接池模式。 34 - 被调 server(注意不是你当前这个服务,是被你调用的服务)必须支持连接多路复用,即在一个连接上对每个请求异步处理,多发多收,否则,client 这边会出现大量超时失败。 35 36 # 示例 37 38 ### 短连接 39 40 ```go 41 opts := []client.Option{ 42 client.WithNamespace("Development"), 43 client.WithServiceName("trpc.app.server.service"), 44 // 禁用默认的连接池,则会采用短连接模式 45 client.WithDisableConnectionPool(), 46 } 47 48 clientProxy := pb.NewGreeterClientProxy(opts...) 49 req := &pb.HelloRequest{ 50 Msg: "hello", 51 } 52 53 rsp, err := clientProxy.SayHello(ctx, req) 54 if err != nil { 55 log.Error(err.Error()) 56 return 57 } 58 59 log.Info("req:%v, rsp:%v, err:%v", req, rsp, err) 60 ``` 61 62 ### 连接池 63 64 ```go 65 // 默认采用连接池模式,不需要任何配置 66 opts := []client.Option{ 67 client.WithNamespace("Development"), 68 client.WithServiceName("trpc.app.server.service"), 69 } 70 71 clientProxy := pb.NewGreeterClientProxy(opts...) 72 req := &pb.HelloRequest{ 73 Msg: "hello", 74 } 75 76 rsp, err := clientProxy.SayHello(ctx, req) 77 if err != nil { 78 log.Error(err.Error()) 79 return 80 } 81 82 log.Info("req:%v, rsp:%v, err:%v", req, rsp, err) 83 ``` 84 85 自定义连接池 86 87 ```go 88 import "trpc.group/trpc-go/trpc-go/pool/connpool" 89 90 /* 91 连接池参数 92 type Options struct { 93 MinIdle int // 最小空闲连接数量,由连接池后台周期性补充,0 代表不做补充 94 MaxIdle int // 最大空闲连接数量,0 代表不做限制,框架默认值 65535 95 MaxActive int // 用户可用连接的最大并发数,0 代表不做限制 96 Wait bool // 可用连接达到最大并发数时,是否等待,默认为 false, 不等待 97 IdleTimeout time.Duration // 空闲连接超时时间,0 代表不做限制,框架默认值 50s 98 MaxConnLifetime time.Duration // 连接的最大生命周期,0 代表不做限制 99 DialTimeout time.Duration // 建立连接超时时间,框架默认值 200ms 100 ForceClose bool // 用户使用连接后是否强制关闭,默认为 false, 放回连接池 101 PushIdleConnToTail bool // 放回连接池时的方式,默认为 false, 采用 LIFO 获取空闲连接 102 } 103 */ 104 105 // 连接池参数可以通过 option 设置,具体请查看 trpc-go 的文档,连接池需要设置成都是全局变量。 106 var pool = connpool.NewConnectionPool(connpool.WithMaxIdle(65535)) 107 // 默认采用连接池模式,不需要任何配置 108 opts := []client.Option{ 109 client.WithNamespace("Development"), 110 client.WithServiceName("trpc.app.server.service"), 111 // 设置自定义连接池 112 client.WithPool(pool), 113 } 114 115 clientProxy := pb.NewGreeterClientProxy(opts...) 116 req := &pb.HelloRequest{ 117 Msg: "hello", 118 } 119 120 rsp, err := clientProxy.SayHello(ctx, req) 121 if err != nil { 122 log.Error(err.Error()) 123 return 124 } 125 126 log.Info("req:%v, rsp:%v, err:%v", req, rsp, err) 127 ``` 128 129 #### 设置空闲连接超时 130 131 在客户端的连接池模式中,框架默认会设置一个 50 秒的空闲超时时间。 132 133 * 对于 `go-net` 来说,连接池会维护一个空闲连接列表。空闲超时时间仅对列表中的空闲连接有效,并且只有在下一次尝试获取连接时,才会触发检查并关闭超时的空闲连接。 134 * 对于 `tnet`,则是通过在每个连接上设置定时器来实现空闲超时。即便连接正在被用于客户端的调用,如果下游服务在空闲超时时间内没有返回结果,该连接仍然会因为空闲超时而被强制关闭。 135 136 可以按照以下方式更改空闲超时时间: 137 138 * `go-net` 139 140 ```go 141 import "trpc.group/trpc-go/trpc-go/pool/connpool" 142 143 func init() { 144 connpool.DefaultConnectionPool = connpool.NewConnectionPool( 145 connpool.WithIdleTimeout(0), // 设置为 0 以禁用空闲超时 146 ) 147 } 148 ``` 149 150 * `tnet` 151 152 ```go 153 import ( 154 "trpc.group/trpc-go/trpc-go/pool/connpool" 155 tnettrans "trpc.group/trpc-go/trpc-go/transport/tnet" 156 ) 157 158 func init() { 159 tnettrans.DefaultConnPool = connpool.NewConnectionPool( 160 connpool.WithDialFunc(tnettrans.Dial), 161 connpool.WithIdleTimeout(0), // 设置为 0 以禁用空闲超时 162 connpool.WithHealthChecker(tnettrans.HealthChecker), 163 ) 164 } 165 ``` 166 167 **注**:服务端默认也设置了一个空闲超时时间,为 60 秒。这个时间比客户端的默认时间长,以确保在大多数情况下,是客户端主动触发空闲超时并关闭连接,而不是服务端强制进行清理。服务端空闲超时时间的修改方法,请参见服务端使用文档。 168 ### 连接多路复用 169 170 ```go 171 opts := []client.Option{ 172 client.WithNamespace("Development"), 173 client.WithServiceName("trpc.app.server.service"), 174 // 开启连接多路复用 175 client.WithMultiplexed(true), 176 } 177 178 clientProxy := pb.NewGreeterClientProxy(opts...) 179 req := &pb.HelloRequest{ 180 Msg: "hello", 181 } 182 183 rsp, err := clientProxy.SayHello(ctx, req) 184 if err != nil { 185 log.Error(err.Error()) 186 return 187 } 188 189 log.Info("req:%v, rsp:%v, err:%v", req, rsp, err) 190 ``` 191 192 设置自定义连接多路复用 193 194 ```go 195 /* 196 type PoolOptions struct { 197 connectNumber int // 设置每个地址的连接数 198 queueSize int // 设置每个连接请求队列长度 199 dropFull bool // 队列满是否丢弃 200 } 201 */ 202 // 连接多路复用参数可以通过 option 设置,具体请查看 trpc-go 的文档,需要设置成都是全局变量。 203 var m = multiplexed.New(multiplexed.WithConnectNumber(16)) 204 205 opts := []client.Option{ 206 client.WithNamespace("Development"), 207 client.WithServiceName("trpc.app.server.service"), 208 // 开启连接多路复用 209 client.WithMultiplexed(true), 210 client.WithMultiplexedPool(m), 211 } 212 213 clientProxy := pb.NewGreeterClientProxy(opts...) 214 req := &pb.HelloRequest{ 215 Msg: "hello", 216 } 217 218 rsp, err := clientProxy.SayHello(ctx, req) 219 if err != nil { 220 log.Error(err.Error()) 221 return 222 } 223 224 log.Info("req:%v, rsp:%v, err:%v", req, rsp, err) 225 ```