背景:
最近使用jedis(redis)开发一项功能,查阅日志发现,服务运行一段时间之后,就会出现
redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool 的异常,
重启就好了,但是过一段时间又出现这种情况;
我这里问题比较简单,释放连接的方法忘记调用了,应该是没有及时释放连接造成的连接池内无连接可用。
但是关于这个问题,发现网上有种种说法,主要是连接池配置和释放连接,关于这个问题,我们不妨深入思考下:
一、连接池配置参数怎么设置?这些参数跟什么有关系?
1、最大连接数:MAX_ACTIVE,指的是支持同时在使用的最大的连接数量:
参数的设置并不是越大越好,而是要看自己的实际使用情况,也就是需要的并发数是多少?
比如,系统并发量一般100,但是你的设置是50,显然这个时候加大设置是很有用的。
但是,又比如这个业务最大可能并发100个链接,那么就没必要设置成2000,设置太大也可能会浪费系统性能;
如果这时候设置远远高于实际了,还是出现连接不够用的情况,那么基本上就是你使用后忘记释放了。
2、等待可用连接的最大时间:MAX_WAIT,这个时间设置,要结合最大连接数、实际并发数、还有系统性能一起考虑,一般不宜过大,否则会导致请求响应时间过长,甚至请求失败。但是也不能过小,如果系统资源比较富裕没什么影响,但是相反的话,过小的情况也可能造成较多的请求失败
比如,设置成1分钟,那么假设此时并发数较高(MAX_ACTIVE一直占用着),一直没有得到可用连接,那么这个请求就会出现一直等待的情况,可能会出现给客户的感觉上系统反应卡顿的不良体验。
3、有时为了保证请求快速得到响应,保持一定的空闲连接(setMinIdle)。在连接池饱和状态,最多有(MAX_ACTIVE-MinIdle)个连接数;
4、如果并发数很大呢?大到超过redis支持的最大连接数?
在 Redis2.4 中,最大连接数是被直接硬编码在代码里面的,而在2.6版本中这个值变成可配置的。maxclients 的默认值是 10000.也就是说,redis默认最多允许10000个连接。当然这还要看硬件环境,CPU/内存情况;
真的有再大的需求,就只能是分布式/集群了,这里暂时不讨论。
附上jedis工具类中的获取连接方法:
private static String ADDR = "****";
private static int PORT = 6379;
//访问密码
private static String AUTH = "****";
//可用连接实例的最大数目,默认值为8;
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
private static int MAX_ACTIVE = 100;
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
private static int MAX_IDLE = 10;
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static int MAX_WAIT = 1000;
private static int TIMEOUT = 10000;
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
private static boolean TEST_ON_BORROW = true;
private static JedisPool jedisPool = null;
//获取链接
public static synchronized Jedis getJedis(){
if(jedisPool==null){
log.info("jedisPool==null , to reGet ");
try {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//指定连接池中最大空闲连接数
jedisPoolConfig.setMaxIdle(MAX_IDLE);
//链接池中创建的最大连接数
jedisPoolConfig.setMaxTotal(MAX_ACTIVE);
//设置创建链接的超时时间
jedisPoolConfig.setMaxWaitMillis(MAX_WAIT);
//表示连接池在创建链接的时候会先测试一下链接是否可用,这样可以保证连接池中的链接都可用的。
jedisPoolConfig.setTestOnBorrow(true);
jedisPool = new JedisPool(jedisPoolConfig,ADDR, PORT, TIMEOUT, AUTH);
} catch (Exception e) {
log.error("jedisPool get error "+e.toString());
}
}
return jedisPool.getResource();
}
二、释放连接的替代方法
在网上的资料,很多都是已经废弃不用的方法,我这把替代方法贴一下:
starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be done using @see {@link redis.clients.jedis.Jedis#close()} (废弃方法returnResource和returnBrokenResource的注释)
//回收链接
public static synchronized void returnResource(Jedis jedis){
try {
if(jedis!=null){
/*
* 这些方法已经废弃
*/
jedisPool.returnResource(jedis);
// jedisPool.returnBrokenResource(jedis);
log.info("returnResource success ");
}
} catch (Exception e) {
log.error("returnResource error "+e.toString());
}
}
//释放链接
public static synchronized void returnToPool(Jedis jedis){
try {
if(jedis!=null){
jedis.close();//替代方法
log.info("returnToPool success ");
}
} catch (Exception e) {
log.error("returnToPool error "+e.toString());
}
}
这次就总结到这儿,下次需要更深入研究下redis分布式缓存。