前两天遇到了redis资源未释放的问题,在博客上面看到有人也遇到了一样的问题,特此转载,并附上自己的心得体会。
一、redis资源未释放的起因:
N年前,在修改一个古老程序时,不小心把redis释放的这块给干掉了,
if (jedis != null) { if (!isInProcess) { jedis.del(currentPageRunControlRedisKey); } JedisUtil.getInstance().closeJedis(jedis); } |
程序调用了一会之后,就获取不到redis连接了,异常如下:
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at redis.clients.util.Pool.getResource(Pool.java:42) |
对比代码,定位到问题之后,修复上线。
二、事后分析出错的原因:
1、对于redis的认知不足.
2、java从集成redis角度上,我的认知:
2.1、选择spring-data-redis集成,目前我们的osp框架支持我们用这种方式,我目前的项目在用(po服务化).
集成后用RedisTemplate即可来操作。
好处:spring来帮你管理redis的连接获取和释放,我们只需要关注自己的业务就好.
坏处:限制于spring框架,其他暂未感觉...
2.2、用jedis来操作,
自己写好一个类,来获取JedisPool,对于redis的操作,记住操作完成后,释放连接回连接池,否则就会发生,我这次发生的这种问题。
好处:操作上更加灵活,不限于spring。
坏处:容易出错。
三、改进:
对于资源未释放,想到了在io操作,db操作等情况,最好封装统一的方法,保证最后资源一定是释放的。
redis方面,我参考spring的redisTemplate,
封装一个redis工具类,对每种类型的redis操作,封装一个方法,操作完后将资源释放回连接池,可避免再忘记释放redis。
代码示例:
-
import org.apache.commons.logging.Log;
-
import org.apache.commons.logging.LogFactory;
-
import redis.clients.jedis.Jedis;
-
import redis.clients.jedis.JedisPool;
-
import redis.clients.jedis.JedisPoolConfig;
-
import java.util.Map;
-
import java.util.concurrent.ConcurrentHashMap;
-
/**
-
* 描 述:JedisUtil
-
* 作 者:潇邦
-
*/
-
public class JedisUtil {
-
private static final Log logger = LogFactory.getLog(JedisUtil.class);
-
//Redis服务器IP
-
private static String IP = "127.0.0.1";
-
//Redis的端口号
-
private static int PORT = 6379;
-
//可用连接实例的最大数目,默认值为8;
-
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
-
private static int MAX_ACTIVE = 64;
-
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
-
private static int MAX_IDLE = 20;
-
//等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
-
private static int MAX_WAIT = 3000;
-
private static int TIMEOUT = 3000;
-
//在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
-
private static boolean TEST_ON_BORROW = true;
-
//在return给pool时,是否提前进行validate操作;
-
private static boolean TEST_ON_RETURN = true;
-
private static Map<String, JedisPool> maps = new ConcurrentHashMap<String, JedisPool>();
-
private JedisUtil() {
-
}
-
/**
-
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
-
*/
-
private static class RedisUtilHolder {
-
private static JedisUtil instance = new JedisUtil();
-
}
-
/**
-
* 当getInstance方法第一次被调用的时候,它第一次读取 RedisUtilHolder.instance,导致RedisUtilHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静
-
* 态域,从而创建RedisUtil的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。 这个模式的优势在于,getInstance方法并没有被同步,
-
* 并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
-
*/
-
public static JedisUtil getInstance() {
-
return RedisUtilHolder.instance;
-
}
-
/**
-
* 获取连接池.
-
*/
-
private JedisPool getPool(String ip, int port) {
-
String key = ip + ":" + port;
-
JedisPool pool = null;
-
if (!maps.containsKey(key)) {//根据ip和端口判断连接池是否存在.
-
JedisPoolConfig config = new JedisPoolConfig();
-
config.setMaxTotal(MAX_ACTIVE);
-
config.setMaxIdle(MAX_IDLE);
-
config.setMaxWaitMillis(MAX_WAIT);
-
config.setTestOnBorrow(TEST_ON_BORROW);
-
config.setTestOnReturn(TEST_ON_RETURN);
-
try {
-
pool = new JedisPool(config, ip, port, TIMEOUT);
-
maps.put(key, pool);
-
} catch (Exception e) {
-
logger.error("初始化Redis连接池异常:", e);
-
}
-
} else {
-
pool = maps.get(key);
-
}
-
return pool;
-
}
-
/**
-
* 获取Jedis实例
-
*/
-
public Jedis getJedis() {
-
Jedis jedis = null;
-
try {
-
jedis = getPool(IP, PORT).getResource();
-
} catch (Exception e) {
-
logger.error("获取Jedis实例异常:", e);
-
// 销毁对象
-
getPool(IP, PORT).returnBrokenResource(jedis);
-
}
-
return jedis;
-
}
-
/**
-
* 释放jedis资源到连接池
-
*/
-
public void returnResource(final Jedis jedis) {
-
if (jedis != null) {
-
getPool(IP, PORT).returnResource(jedis);
-
}
-
}
-
/**
-
* 获取数据
-
*/
-
public Object get(String key) {
-
Object value = null;
-
Jedis jedis = null;
-
try {
-
jedis = getJedis();
-
value = jedis.get(key);
-
} catch (Exception e) {
-
logger.warn("获取数据异常:", e);
-
} finally {
-
//返还到连接池
-
returnResource(jedis);
-
}
-
return value;
-
}
-
public static void main(String[] args) {
-
Object val = JedisUtil.getInstance().get("redisKey");
-
System.out.println(val);
-
}
-
}
四、redis的概念:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
五、数据库连接池的概念:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
自我总结:redis资源池避免了多次资源新建与释放的cup消耗问题。但是jedis相较于redisTemplate的操作更多一些。而且其配置文件也很不好弄,比较适合单点服务器的操作。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"
>
<!-- 使用redis缓存 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}" />
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="minIdle" value="${redis.minIdle}" />
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" >
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1">
<list>
<bean class="redis.clients.jedis.JedisShardInfo">
<!-- 生产环境需要放开注释 -->
<constructor-arg name="host" value="${redis.host}" />
<constructor-arg name="port" value="${redis.port}" type="int" />
<constructor-arg name="timeout" value="${redis.timeout}" type="int"/>
<property name="password" value="${redis.password}"/>
</bean>
</list>
</constructor-arg>
</bean>
<!-- end使用redis缓存 -->
</beans>
zhangqin10@cmbc.com.cn
如上所示,是我的redis.xml的配置文件,有一个比较烦人的地方就是redis.clients.jedis.JedisShardInfo这个类的构造方法里面不含有password,所以password还得以属性的性质添加进去,然后就各种bug啊,最后改好了。原因是因为我配置了两个地方的redis配置文件,因为redis服务器添加了密码,所以两处地方都需要修改,但是我只是修改一处,导致一直报错说redis无法连接,所以经验教训告诉我们,最好还是把相关的配置文件配置在同一个地方,如果不得已只能配置在多个地方,修改的时候一定要加倍小心,要把需要修改的地方全部修改了,不要遗漏。不然你找bug找半天,会发现这个问题就会吐血的。