- 后端
- 运维
【免费下载链接】cachecloud
搜狐视频(sohu tv)Redis私有云平台 :支持Redis多种架构(Standalone、Sentinel、Cluster)高效管理、有效降低大规模redis运维成本,提升资源管控能力和利用率。平台提供快速搭建/迁移,运维管理,弹性伸缩,统计监控,客户端整合接入等功能。(CacheCloud is a Redis cloud management platform. It supports Standalone, Sentinel, and Cluster architectures for Redis, effectively reducing large-scale Redis operation and maintenance costs, and improving resource management and utilization. The platform provides rapid construction/migration, operation and maintenance management, elastic scaling, statistical monitoring, client integration and access and other functions)
导读:本文基于 CacheCloud 官方 Wiki 中的《JedisPool资源池优化》文档展开,系统讲解 JedisPool 的使用方法、
GenericObjectPoolConfig全部核心参数的含义与默认值,并结合真实业务给出maxTotal / maxIdle / minIdle的估算方法,最后给出可直接落地的"最合理"配置。读完本文,你将掌握连接池参数的量化评估思路、空闲资源监测机制,以及如何借助 CacheCloud 平台(其自身源码同样基于 Jedis 连接池构建)把连接池调到既稳又省的理想状态。
CacheCloud 是搜狐视频开源的 Redis 私有云平台,支持 Standalone、Sentinel、Cluster 三种架构的统一管理。无论是业务方直连 Redis,还是通过 CacheCloud 的接入层访问,客户端侧的JedisPool资源池参数都直接决定了连接可用性、响应延迟与资源开销——参数设小了,高并发下会频繁抛Pool exhausted;设大了,又会白白占用客户端与服务端的内存和连接资源。本文就围绕 JedisPool 资源池优化这一主题,给出完整、可验证的配置方案。
一、背景:为什么需要合理的资源池配置
Jedis 是 Java 生态中使用最广泛的 Redis 客户端,它本身不是线程安全的,因此官方推荐通过连接池(JedisPool)来复用连接、规避多线程并发复用同一连接带来的风险。JedisPool内部基于 Apache Commons Pool 2 实现,连接的借出(borrow)、归还(return)、空闲回收、有效性检测全部由对象池托管。
合理的GenericObjectPoolConfig配置能为业务使用 Redis 保驾护航:既能保证高并发下有足够的连接可用,又能避免连接过度创建导致的资源浪费,还能通过空闲监测及时剔除失效连接。这一点在 CacheCloud 自身的实现中同样有所体现——例如平台监控模块在维护到各 Redis 实例的连接时,就是通过 maintainJedisPool 以host:port为键缓存并复用JedisPool实例(双重检查加锁),其中同样使用了GenericObjectPoolConfig作为默认配置;AssistRedisServiceImpl 则直接以new GenericObjectPoolConfig()创建连接池。由此可见,连接池配置是整个 Redis 客户端链路中绕不开的关键环节。
二、使用方法:从 Maven 依赖到命令执行
以官方 2.9.0(Jedis Release)为示例,Maven 依赖如下:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> <scope>compile</scope> </dependency>说明:当前 CacheCloud 仓库根 pom.xml 中声明的
jedis.version为3.7.0(由 cachecloud-web/pom.xml 引用),新项目建议直接使用较新的 3.x 版本;2.9.0 的 API 用法与本文一致,仅部分构造签名有所演进。
Jedis 使用 Apache Commons Pool 2 对连接资源池进行管理,因此定义JedisPool时最重要的参数就是资源池配置对象GenericObjectPoolConfig:
GenericObjectPoolConfig jedisPoolConfig = new GenericObjectPoolConfig(); jedisPoolConfig.setMaxTotal(..); jedisPoolConfig.setMaxIdle(..); jedisPoolConfig.setMinIdle(..); jedisPoolConfig.setMaxWaitMillis(..); ...注意:后面会提到,建议用
JedisPoolConfig代替GenericObjectPoolConfig,它预置了更合理的空闲监测默认值。
JedisPool的初始化如下:
// redisHost和redisPort是实例的IP和端口 // redisPassword是实例的密码 // timeout,这里既是连接超时又是读写超时, // 从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数 JedisPool jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, timeout, redisPassword);执行命令的标准写法(关键在 finally 中的归还语义):
Jedis jedis = null; try { jedis = jedisPool.getResource(); //具体的命令 jedis.executeCommand() } catch (Exception e) { logger.error(e.getMessage(), e); } finally { //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。 if (jedis != null) jedis.close(); }这里的jedis.close()在JedisPool模式下并不会真正关闭 TCP 连接,而是把连接归还给资源池供复用;只有JedisPool.close()/jedisPool.destroy()才会真正释放资源。如果业务代码借了连接却不归还(不调close()),就会造成连接泄露,最终把资源池耗尽——这是下文要重点防范的典型故障。
三、参数说明:一张表看懂资源池全部关键参数
JedisPool 保证连接资源在一个可控范围内,并且提供了线程安全;而合理的GenericObjectPoolConfig配置能为应用使用 Redis 保驾护航。在当前环境下,Jedis 连接就是资源,JedisPool 管理的就是 Jedis 连接。
1. 资源设置和使用
| 序号 | 参数名 | 含义 | 默认值 | 使用建议 |
|---|---|---|---|---|
| 1 | maxTotal | 资源池中最大连接数 | 8 | 设置建议见下节 |
| 2 | maxIdle | 资源池允许最大空闲的连接数 | 8 | 设置建议见下节 |
| 3 | minIdle | 资源池确保最少空闲的连接数 | 0 | 设置建议见下节 |
| 4 | blockWhenExhausted | 当资源池用尽后,调用者是否要等待。只有当为 true 时,下面的maxWaitMillis才会生效 | true | 建议使用默认值 |
| 5 | maxWaitMillis | 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) | -1:表示永不超时 | 不建议使用默认值 |
| 6 | testOnBorrow | 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 业务量很大时候建议设置为 false(多一次 ping 的开销) |
| 7 | testOnReturn | 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 业务量很大时候建议设置为 false(多一次 ping 的开销) |
| 8 | jmxEnabled | 是否开启 JMX 监控,可用于监控 | true | 建议开启,但应用本身也要开启 |
逐一展开说明:
maxTotal:资源池中可同时存在的最大连接数(含正在使用与空闲的)。默认值 8 对绝大多数线上业务都偏小,是Could not get a resource from the pool异常最常见的诱因之一。maxIdle/minIdle:maxIdle是允许保留的最大空闲连接数,minIdle是池要尽力维持的最小空闲连接数。maxIdle太小会导致连接频繁创建销毁;minIdle主要用于配合空闲回收,让池子始终保有一定预热连接。blockWhenExhausted:池用尽后的行为开关。为 true 时调用线程会阻塞等待;为 false 时立即抛NoSuchElementException: Pool exhausted。一般保持默认 true,并配合maxWaitMillis设置合理的等待上限。maxWaitMillis:借用连接的最大阻塞等待时长。默认 -1 表示永不超时,此时若 Redis 抖动或连接被占满,业务线程会无限期挂起,风险很高,因此强烈不建议使用默认值。testOnBorrow/testOnReturn:借用/归还时是否执行ping验证连接有效性。开启能及时剔除死连接,但每次借还都多一次网络往返;高 QPS 场景建议关闭,依靠下面的空闲监测(testWhileIdle)来兜底清理。jmxEnabled:是否向 JMX 注册池的监控指标(如NumActive、NumIdle、NumWaiters等),方便通过监控系统观测连接池水位,是下文"用监控找最佳值"的基础。
2. 空闲资源监测
空闲 Jedis 对象的检测由下面四个参数组合完成,testWhileIdle是整个功能的开关:
| 序号 | 参数名 | 含义 | 默认值 | 使用建议 |
|---|---|---|---|---|
| 1 | testWhileIdle | 是否开启空闲资源监测 | false | true |
| 2 | timeBetweenEvictionRunsMillis | 空闲资源的检测周期(单位为毫秒) | -1:不检测 | 建议设置,周期自行选择,也可以使用下面JedisPoolConfig中的配置 |
| 3 | minEvictableIdleTimeMillis | 资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除 | 1000 × 60 × 30 = 30 分钟 | 可根据自身业务决定,大部分默认值即可,也可以考虑使用下面JedisPoolConfig中的配置 |
| 4 | numTestsPerEvictionRun | 做空闲资源检测时,每次的采样数 | 3 | 可根据自身应用连接数进行微调,如果设置为 -1,就是对所有连接做空闲监测 |
各参数行为说明:
testWhileIdle开启后,空闲监测线程会周期性巡检池中空闲连接;配合timeBetweenEvictionRunsMillis决定巡检频率。minEvictableIdleTimeMillis是"空闲多久才允许被回收"的阈值。注意默认值写法1000 60 30实为1000ms × 60 × 30,即 30 分钟。numTestsPerEvictionRun控制单次巡检抽样的连接数,-1 表示全量检测。连接数较大的应用可以适当增大,避免死连接长期滞留。
为了方便使用,Jedis 提供了JedisPoolConfig,它本身继承了GenericObjectPoolConfig并预置了一套空闲监测设置:
public class JedisPoolConfig extends GenericObjectPoolConfig { public JedisPoolConfig() { // defaults to make your life with connection pool easier :) setTestWhileIdle(true); // setMinEvictableIdleTimeMillis(60000); // setTimeBetweenEvictionRunsMillis(30000); setNumTestsPerEvictionRun(-1); } }可以看到JedisPoolConfig相比原始GenericObjectPoolConfig的默认值做了三点改进:开启testWhileIdle、空闲 60 秒即可回收、每 30 秒巡检一次且全量检测。这也是本文推荐用JedisPoolConfig替代GenericObjectPoolConfig的原因——开箱即用的空闲监测能及时回收并剔除死连接,配合testOnBorrow=false,既省了借还时的 ping 开销,又保证了连接质量。
所有默认值均可在org.apache.commons.pool2.impl.BaseObjectPoolConfig源码中查到,本文表格中的默认值即来源于此。
四、资源池大小(maxTotal)、空闲(maxIdle / minIdle)设置建议
1. maxTotal:最大连接数
实际上这是一个很难回答的问题,考虑的因素比较多:
- 业务希望 Redis 并发量;
- 客户端执行命令时间;
- Redis 资源:例如 nodes(应用个数)× maxTotal 不能超过 Redis 的最大连接数(
maxclients); - 资源开销:例如虽然希望控制空闲连接,但是不希望因为连接池的频繁释放创建连接造成不必要的开销。
以一个例子说明,假设:
- 一次命令时间(borrow | return resource + Jedis 执行命令(含网络))的平均耗时约为 1ms,一个连接的 QPS 大约是 1000;
- 业务期望的 QPS 是 50000。
那么理论上需要的资源池大小是50000 / 1000 = 50个。但事实上这是个理论值,还要考虑到要比理论值预留一些资源,通常来讲maxTotal可以比理论值大一些。
但这个值不是越大越好:一方面连接太多占用客户端和服务端资源,另一方面对于 Redis 这种高 QPS 的服务器,一个大命令的阻塞即使设置再大的资源池仍然会无济于事——连接池解决的是并发连接复用问题,而不是单条命令的慢查询问题。
2. maxIdle / minIdle
maxIdle实际上才是业务需要的最大连接数,maxTotal是为了给出余量,所以maxIdle不要设置过小,否则会有new Jedis(新连接)开销;而minIdle是为了控制空闲资源监测。
连接池的最佳性能是maxTotal = maxIdle,这样就避免了连接池伸缩带来的性能干扰。但是如果并发量不大或者maxTotal设置过高,会导致不必要的连接资源浪费。可以根据实际总 OPS 和调用 Redis 客户端的规模,整体评估每个节点所使用的连接池。
3. 监控:用数据找到"最佳值"
实际上最靠谱的值是通过监控来得到"最佳值"的,可以考虑通过一些手段(例如 JMX)实现监控,找到合理值。具体做法:
- 确保
jmxEnabled=true,并在应用端开启 JMX 暴露(如 Spring Boot Actuator 或-Dcom.sun.management.jmxremote); - 持续观测
NumActive(活跃连接数)、NumIdle(空闲连接数)、NumWaiters(等待线程数)与borrowedCount等指标; - 若长期
NumIdle趋近 0 且NumWaiters持续大于 0,说明maxTotal偏小;若NumIdle长期维持高位,说明maxTotal偏大,可适当下调以省资源; - 结合业务高峰期的 QPS 曲线,取"略高于峰值所需连接数"作为
maxTotal的最终值。
推荐配置速查
综合以上分析,给出一个可直接落地的"最合理"配置模板(以JedisPoolConfig为基础,按需微调):
JedisPoolConfig poolConfig = new JedisPoolConfig(); // 资源池最大连接数:按 峰值QPS / 单连接QPS 估算后上浮,如 50000/1000 ≈ 50 → 取 60~100 poolConfig.setMaxTotal(60); // 业务实际需要的最大空闲连接数,建议与 maxTotal 相同以避免池伸缩 poolConfig.setMaxIdle(60); // 保底空闲连接数,保证低谷期也有预热连接 poolConfig.setMinIdle(10); // 池用尽后等待上限,建议 500~2000ms,避免无限等待 poolConfig.setMaxWaitMillis(2000); // 借用/归还不做 ping 校验,减少额外 RTT,交给空闲监测兜底 poolConfig.setTestOnBorrow(false); poolConfig.setTestOnReturn(false); // 空闲监测由 JedisPoolConfig 默认开启:每 30s 巡检、空闲 60s 回收、全量检测 // (testWhileIdle=true, timeBetweenEvictionRunsMillis=30000, // minEvictableIdleTimeMillis=60000, numTestsPerEvictionRun=-1)提醒:以上数值需结合自身 QPS、单命令耗时、应用节点数与 Redis
maxclients综合评估,本文示例仅用于说明估算方法,不可照搬为所有场景的万能值。
五、与 CacheCloud 平台的结合
1. CacheCloud 自身如何使用连接池
CacheCloud 平台内部同样依赖 Jedis 连接池与各 Redis 实例通信,可作为配置实践的直接参考:
- RedisCenterImpl.maintainJedisPool:按
host:port维护JedisPool实例,使用new GenericObjectPoolConfig()+Protocol.DEFAULT_TIMEOUT构建(有密码时额外传入密码),并通过双重检查加锁保证并发安全。这里采用默认配置即可满足平台自身的低频管理操作需求。 - AssistRedisServiceImpl:辅助服务同样以
GenericObjectPoolConfig+Protocol.DEFAULT_TIMEOUT构建JedisPoolMain,并通过getFromJedisPool()统一借用连接执行命令。
从源码结构看,CacheCloud 平台内部的连接池使用场景均为低频管理操作,因此使用默认配置是合理的;而业务应用属于高频读写场景,就需要按照本文第三节、第四节的方法精细化调参。
2. 业务接入时的配置示例
CacheCloud 提供的客户端接入示例(DemoCodeUtil)中,各架构的业务接入代码都采用了"自定义JedisPoolConfig+ClientBuilder"的模式,例如 Standalone 架构:
JedisPool jedisPool = null; // 使用默认配置 //jedisPool = ClientBuilder.redisStandalone(appId).build(); /** * 自定义配置:setTimeout 连接和操作超时时间,默认2000毫秒。 */ JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxWaitMillis(Protocol.DEFAULT_TIMEOUT); jedisPool = ClientBuilder.redisStandalone(appId) .setTimeout(Protocol.DEFAULT_TIMEOUT) .setPoolConfig(poolConfig) .build(); Jedis jedis = jedisPool.getResource(); jedis.setnx("key2", "5"); assertEquals("10", jedis.incrBy("key2", 5)); jedis.close();Sentinel 与 Cluster 架构的 Spring 接入(RedisSentinelFactory / RedisClusterFactory 示例)也遵循同一模式:new JedisPoolConfig()后通过poolConfig.setMaxWaitMillis(Protocol.DEFAULT_TIMEOUT)设置等待上限,再以ClientBuilder.redisSentinel(appId).setPoolConfig(poolConfig).build()/ClientBuilder.redisCluster(appId).setJedisPoolConfig(poolConfig).build()完成构建。这与本文"用JedisPoolConfig、务必设置maxWaitMillis"的建议完全一致。
3. 参数不合理时的典型故障排查
CacheCloud 官方 Wiki 的 exception.md 给出了连接池配置不当引发的典型异常:
JedisConnectionException: Could not get a resource from the pool(Caused by: NoSuchElementException: Pool exhausted):当blockWhenExhausted=false时,池中没有可用连接会立即抛出该异常。常见原因之一是连接泄露——下面的代码从JedisPool借了 8 次连接(默认maxTotal=8)但未归还,第 9 次jedisPool.getResource()即触发异常:
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379); //向JedisPool借用8次连接,但是没有执行归还操作。 for (int i = 0; i < 8; i++) { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.ping(); } catch (Exception e) { logger.error(e.getMessage(), e); } } jedisPool.getResource().ping();因此务必遵守第二节的代码规范:借用的连接必须在 finally 中close()归还。
- 若出现大量"Could not get a resource"且代码规范无误,则应检查
maxTotal是否过小、maxWaitMillis是否过短,或是否存在 Redis 侧maxclients限制(nodes × maxTotal 超过 Redis 最大连接数)。
结语
JedisPool 资源池的调优,本质是在连接可用性、响应延迟与资源开销三者之间取平衡:maxTotal决定并发上限,maxIdle/minIdle决定空闲水位,blockWhenExhausted/maxWaitMillis决定池耗尽时的行为,testWhileIdle及其配套参数负责空闲连接的巡检回收。建议以JedisPoolConfig为起点(自动开启空闲监测),用QPS ÷ 单连接QPS估算maxTotal并适当上浮,借助 JMX 监控持续校准,最终收敛到"既满足峰值、又不浪费资源"的最优配置。CacheCloud 平台自身的接入示例与故障排查文档(exception.md)与本文建议互为印证,可作为业务落地的直接参考。
- 后端
- 运维
【免费下载链接】cachecloud
搜狐视频(sohu tv)Redis私有云平台 :支持Redis多种架构(Standalone、Sentinel、Cluster)高效管理、有效降低大规模redis运维成本,提升资源管控能力和利用率。平台提供快速搭建/迁移,运维管理,弹性伸缩,统计监控,客户端整合接入等功能。(CacheCloud is a Redis cloud management platform. It supports Standalone, Sentinel, and Cluster architectures for Redis, effectively reducing large-scale Redis operation and maintenance costs, and improving resource management and utilization. The platform provides rapid construction/migration, operation and maintenance management, elastic scaling, statistical monitoring, client integration and access and other functions)
相关推荐
Wand-Enhancer 指南:一次本地补丁免费解锁 WeMod 专业版
Wand Enhancer 指南:一次本地补丁免费解锁 WeMod 专业版 Wand Enhancer 是一个开源的本地补丁工具,专门处理 WeMod 客户端的
后端运维Kaleidoscope架构解析:深入理解事件驱动键盘固件设计
Kaleidoscope架构解析:深入理解事件驱动键盘固件设计 Kaleidoscope是一款功能强大的开源键盘固件,专为Keyboardio键盘及其他采用AV
嵌入式Masto.js贡献指南:如何为这个开源Mastodon客户端库贡献力量
Masto.js贡献指南:如何为这个开源Mastodon客户端库贡献力量 想要为开源项目贡献代码但不知从何入手?Masto.js作为一款优秀的JavaScrip
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考