连接池是各种服务绕不过去的模块,它在调用链路的上下游之间建立了一个缓冲区。客户端可以从连接池中获取连接来执行数据库操作,完成后将连接返回给连接池,而不是每次都建立新的连接。
连接池的作用显而易见:
- 提高性能:连接池可以减少连接的创建和销毁过程,避免了频繁地与服务端建立物理连接的开销,从而提高了客户端的性能和响应速度。
- 节省资源:服务端连接是一种有限的资源,每个连接都占用着内存等系统资源。连接池可以限制连接的数量,避免过多的连接导致资源的浪费,提高了系统的资源利用率。
- 连接的复用:连接池可以重复利用已经建立的连接,避免了频繁地创建和销毁连接的开销,提高了服务端的并发性能。
但是,连接池配置众多,根据业务特征调整好连接池并不容易。
go-redis 连接池的配置参数包括:
- DialTimeout # Dial timeout for establishing new connections. - ReadTimeout # Timeout for socket reads. If reached, commands will fail with a timeout instead of blocking. - WriteTimeout # Timeout for socket writes. If reached, commands will fail with a timeout instead of blocking. - PoolFIFO # Type of connection pool. true for FIFO pool, false for LIFO pool. - PoolSize # Maximum number of socket connections. - PoolTimeout # Amount of time client waits for connection if all connections are busy before returning an error. - MinIdleConns # Minimum number of idle connections which is useful when establishing new connection is slow. - MaxIdleConns # Maximum number of idle connections. - ConnMaxIdleTime # ConnMaxIdleTime is the maximum amount of time a connection may be idle. - ConnMaxLifetime # Expired connections may be closed lazily before reuse.
误区一: DialTimeout 设置过小
DialTimeout
(拨号超时)用于指定建立网络连接的超时时间。当客户端尝试连接到服务端时,如果在 DialTimeout
指定的时间内无法建立连接,连接操作将超时失败。它通常包括域名解析、建立 TCP 连接等步骤的超时时间。
DialTimeout
设置过小,可能会导致服务由于无法成功建立连接,启动失败。尤其是使用 DNS 作为服务发现以及跨 IDC 调用的场景下。
go-redis 默认是 5 s。3~5 s 是比较合适的,可以直接使用默认值。
误区二: PoolSize 设置不合理
如果连接池的大小设置过小,无法满足应用程序的并发需求,可能会导致连接不足的问题,影响应用程序的性能和响应速度。
如果连接池的大小设置过大,最大连接总数超过服务端最大连接数。在业务请求峰值时,会出现新建连接失败导致的请求失败。
那怎么评估连接池大小呢?
假如请求服务端的平均延迟是 duration ms,客户端进程的峰值 QPS 是 qps。单个连接 1 秒(1000)能否处理的请求总数是 1000 / duration;同时,预留一定的 Buffer 连接数 buffer 给请求变慢或请求量因为需求变化增加等场景。那么合适的连接池大小为:
PoolSize = qps / (1000 / duration) + buffer
误区三:ConnMaxLifetime 设置不当
如果连接生存时间设置得过短,则可能频繁地创建和销毁连接,影响性能。此问题比较容易理解。
如果连接生存时间设置得过长,可能会导致连接过期或失效。举个极端的例子,不设置连接生存时间。
考虑以下场景
场景一:
服务端新版本发布。假如该服务有两个实例 A、B。考虑发布过程,首先,A 升级重启,连接全部请求到 B。然后,B 升级重启,连接全部回到 A。因为没有设置连接生存时间,调用 A 不出现错误的前提下,连接永远不均匀。
场景二:
客户端到服务端短暂网络异常。假如该服务有两个实例 A、B,新建连接的机制是 Round Robin。考虑到 B 的网络异常,导致请求全部断开。然后客户端开始新建连接,到 B 的连接全部失败,最终连接池的中的连接全部连接到 A。因为没有设置连接生存时间,调用 A 不出现错误的前提下,连接永远不均匀。
go-redis 该设置默认关闭。为避免类似问题,连接生存时间一般建议配置为小时级,既避免频繁地创建和销毁连接,影响性能;同时也避免连接不均匀。
误区四:PoolFIFO 设置不当
在连接池中连接到服务端每个实例的连接数大致均匀的前提下。客户端从连接池获取连接发起请求,本质来说是一个负载均衡的问题。常见的负载均衡算法包括:
- Round-Robin(FIFO)
- Random
- Weighted Round Robin
- Weighted Random
- Hashing
很显然,go-redis 默认使用的 LIFO 并不在列。
LIFO 并不适合作为负载均衡算法的选择。因为 LIFO 会优先处理最近使用过的连接,这可能会导致某些服务实例负载过重,而其他的服务实例却得不到充分的利用。这种不均衡的分配会影响系统的可用性、性能和容错能力。
因此,在使用 go-redis 时,PoolFIFO 应永远设置为 true。
附:连接池图例
连接使用:获取/释放流程图
连接管理:连接状态机
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/06-05-2023/go-redis-connection-pool.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!