背景交代:
公司某个项目,生产环境用的自建在服务器内的redis ,由于访问量不大,redis用的是单机的
针对自建的redis ,我一直是持有不看好意见的。自建redis 容易宕机,宕机后数据全无,且监控难做,虽然有普米修斯这种监控工具面板,但粒度、报警等还是达不到实际需求。当然 如果你们公司有能力,有专门的团队开发运维监控工具当我没说。
线上大促期间发生了几次,redis 报错问题,排查日志后,原因大多为报错:JedisConnectionException: Could not get a resource from the pool
因为自建redis,无法细粒度看到过去时间的连接数、连接池资源,排查问题难上加南。
一、迁移前
redis 连接池 逻辑代码
@Data
@Configuration
public class JedisConfig {
@Value("${spring.jedis.config.db}")
private Integer db;
@Value("${spring.jedis.config.maxTotal}")
private Integer maxTotal;
@Value("${spring.jedis.config.maxIdle}")
private Integer maxIdle;
@Value("${spring.jedis.config.minIdle}")
private Integer minIdle;
@Value("${spring.jedis.config.maxWaitMillis}")
private Long maxWaitMillis;
@Value("${spring.jedis.config.testOnBorrow}")
private Boolean testOnBorrow;
@Value("${spring.jedis.host}")
private String host;
@Value("${spring.jedis.port}")
private Integer port;
@Value("${spring.jedis.password}")
private String password;
@Bean
public JedisPoolConfig getJedisPoolConfig() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setTestOnBorrow(testOnBorrow);
return config;
}
@Bean
public JedisPool getJedisPool(@Autowired JedisPoolConfig config) {
JedisPool pool = null;
if (StringUtils.isNotBlank(password)) {
pool = new JedisPool(config, host, port, 1000, password, db);
} else {
// 该模式一般用户测试,没有选择数据库
pool = new JedisPool(config, host, port, 1000);
}
return pool;
}
}
配置文件application.properties
spring.jedis.host=127.0.0.1
spring.jedis.port=6379
spring.jedis.password=xxxxxxxxx
spring.jedis.config.db=0
spring.jedis.config.maxTotal=500
spring.jedis.config.maxIdle=500
spring.jedis.config.minIdle=50
spring.jedis.config.maxWaitMillis=15000
spring.jedis.config.testOnBorrow=true
spring.jedis.timeout.default=1800000
spring.jedis.timeout.user=172800000
似乎没什么问题
既然是redis 连接池报资源不足,那就往这个方向排查
看到具体参数:
maxTotal:资源池中的最大连接数
关键参数设置建议 maxTotal(最大连接数)
想合理设置maxTotal(最大连接数)需要考虑的因素较多,如:
业务希望的Redis并发量;
客户端执行命令时间;
Redis资源,例如nodes (如应用ECS个数等) * maxTotal不能超过Redis的最大连接数(可在实例详情页面查看);
资源开销,例如虽然希望控制空闲连接,但又不希望因为连接池中频繁地释放和创建连接造成不必要的开销。
假设一次命令时间,即borrow|return resource加上Jedis执行命令 (
含网络耗时)的平均耗时约为1ms,一个连接的QPS大约是1s/1ms =
1000,而业务期望的单个Redis的QPS是50000(业务总的QPS/Redis分片个数),那么理论上需要的资源池大小(即MaxTotal)是50000
/ 1000 = 50。但事实上这只是个理论值,除此之外还要预留一些资源,所以maxTotal可以比理论值大一些。这个值不是越大越好,一方面连接太多会占用客户端和服务端资源,另一方面对于Redis这种高QPS的服务器,如果出现大命令的阻塞,即使设置再大的资源池也无济于事。
maxIdle与minIdle
maxIdle实际上才是业务需要的最大连接数,maxTotal 是为了给出余量,所以 maxIdle 不要设置得过小,否则会有new
Jedis(新连接)开销,而minIdle是为了控制空闲资源检测。连接池的最佳性能是maxTotal=maxIdle,这样就避免了连接池伸缩带来的性能干扰。如果您的业务存在突峰访问,建议设置这两个参数的值相等;如果并发量不大或者maxIdle设置过高,则会导致不必要的连接资源浪费。
您可以根据实际总QPS和调用Redis的客户端规模整体评估每个节点所使用的连接池大小。
使用监控获取合理值
在实际环境中,比较可靠的方法是通过监控来尝试获取参数的最佳值。可以考虑通过JMX等方式实现监控,从而找到合理值
maxIdle:资源池允许的最大空闲连接数
minIdle:资源池确保的最少空闲连接数
maxIdle实际上才是业务需要的最大连接数,maxTotal 是为了给出余量,所以 maxIdle 不要设置得过小,否则会有new
Jedis(新连接)开销,而minIdle是为了控制空闲资源检测。
连接池的最佳性能是maxTotal=maxIdle,这样就避免了连接池伸缩带来的性能干扰。如果您的业务存在突峰访问,建议设置这两个参数的值相等;如果并发量不大或者maxIdle设置过高,则会导致不必要的连接资源浪费。
您可以根据实际总QPS和调用Redis的客户端规模整体评估每个节点所使用的连接池大小。
二、迁移后
利用阿里云DTS 迁移工具,将旧数据实施同步至线上TairDB。
有了强大的、细粒度监控视图。
调优了redis配置参数:
# Redis服务器地址
spring.jedis.host=r-wz9yl4yc6d6lij33zq.redis.rds.aliyuncs.com
# Redis服务器连接端口
spring.jedis.port=6379
# Redis服务器连接密码(默认为空)
spring.jedis.password=xxxxxxx
# Redis数据库索引(默认为0)
spring.jedis.config.db=0
#资源池中的最大连接数
spring.jedis.config.maxTotal=2000
#资源池允许的最大空闲连接数
spring.jedis.config.maxIdle=2000
#资源池确保的最少空闲连接数
spring.jedis.config.minIdle=100
#当资源池连接用尽后,调用者的最大等待时间(单位为毫秒)。-1(表示永不超时)不建议使用默认值。
spring.jedis.config.maxWaitMillis=15000
#向资源池归还连接时是否做连接有效性检测(ping)。检测到无效连接将会被移除。默认:false 业务量很大时候建议设置为false,减少一次ping的开销。
spring.jedis.config.testOnBorrow=true
# Redis服务器连接超时时间(毫秒)
spring.jedis.timeout=10000
spring.jedis.timeout.default=1800000
spring.jedis.timeout.user=172800000
#建议开启,请注意应用本身也需要开启。
spring.jedis.config.jmxEnabled=true
增加了资源池中的最大连接数、将最大连接数与最大空闲连接数调大,并设置成一致,减少因为伸缩扩容,浪费资源。
增加调用者最大等待时间maxWaitMillis=15000。
将这些配置设置后,没有出现过,资源不足报错情况。
记录此文。
2024.05.25 于广州