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、不用spring-data-redis集成,那就用原生态的jedis来操作吧,我的另一个老项目在用(定时任务拉取商品资料).
自己写好一个类,来获取JedisPool,对于redis的操作,记住操作完成后,释放连接回连接池,否则就会发生,我这次发生的这种问题。
好处:操作上更加灵活,不限于spring。
坏处:容易出错。
三、改进:
对于资源未释放,想到了在io操作,db操作等情况,最好封装统一的方法,保证最后资源一定是释放的。
redis方面,我参考spring的redisTemplate,
封装一个redis工具类,对每种类型的redis操作,封装一个方法,操作完后将资源释放回连接池,可避免再忘记释放redis。
代码示例:
1 import org.apache.commons.logging.Log; 2 import org.apache.commons.logging.LogFactory; 3 import redis.clients.jedis.Jedis; 4 import redis.clients.jedis.JedisPool; 5 import redis.clients.jedis.JedisPoolConfig; 6 import java.util.Map; 7 import java.util.concurrent.ConcurrentHashMap; 8 9 /** 10 * 描 述:JedisUtil 11 * 作 者:潇邦 12 */ 13 public class JedisUtil { 14 15 private static final Log logger = LogFactory.getLog(JedisUtil.class); 16 17 //Redis服务器IP 18 private static String IP = "127.0.0.1"; 19 20 //Redis的端口号 21 private static int PORT = 6379; 22 23 //可用连接实例的最大数目,默认值为8; 24 //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 25 private static int MAX_ACTIVE = 64; 26 27 //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 28 private static int MAX_IDLE = 20; 29 30 //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException; 31 private static int MAX_WAIT = 3000; 32 33 private static int TIMEOUT = 3000; 34 35 //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; 36 private static boolean TEST_ON_BORROW = true; 37 38 //在return给pool时,是否提前进行validate操作; 39 private static boolean TEST_ON_RETURN = true; 40 41 private static Map<String, JedisPool> maps = new ConcurrentHashMap<String, JedisPool>(); 42 43 44 private JedisUtil() { 45 } 46 47 /** 48 * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。 49 */ 50 private static class RedisUtilHolder { 51 private static JedisUtil instance = new JedisUtil(); 52 } 53 54 /** 55 * 当getInstance方法第一次被调用的时候,它第一次读取 RedisUtilHolder.instance,导致RedisUtilHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静 56 * 态域,从而创建RedisUtil的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。 这个模式的优势在于,getInstance方法并没有被同步, 57 * 并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。 58 */ 59 public static JedisUtil getInstance() { 60 return RedisUtilHolder.instance; 61 } 62 63 /** 64 * 获取连接池. 65 */ 66 private JedisPool getPool(String ip, int port) { 67 String key = ip + ":" + port; 68 JedisPool pool = null; 69 if (!maps.containsKey(key)) {//根据ip和端口判断连接池是否存在. 70 JedisPoolConfig config = new JedisPoolConfig(); 71 config.setMaxTotal(MAX_ACTIVE); 72 config.setMaxIdle(MAX_IDLE); 73 config.setMaxWaitMillis(MAX_WAIT); 74 config.setTestOnBorrow(TEST_ON_BORROW); 75 config.setTestOnReturn(TEST_ON_RETURN); 76 try { 77 pool = new JedisPool(config, ip, port, TIMEOUT); 78 maps.put(key, pool); 79 } catch (Exception e) { 80 logger.error("初始化Redis连接池异常:", e); 81 } 82 } else { 83 pool = maps.get(key); 84 } 85 return pool; 86 } 87 88 /** 89 * 获取Jedis实例 90 */ 91 public Jedis getJedis() { 92 Jedis jedis = null; 93 try { 94 jedis = getPool(IP, PORT).getResource(); 95 } catch (Exception e) { 96 logger.error("获取Jedis实例异常:", e); 97 // 销毁对象 98 getPool(IP, PORT).returnBrokenResource(jedis); 99 } 100 return jedis; 101 } 102 103 /** 104 * 释放jedis资源到连接池 105 */ 106 public void returnResource(final Jedis jedis) { 107 if (jedis != null) { 108 getPool(IP, PORT).returnResource(jedis); 109 } 110 } 111 112 /** 113 * 获取数据 114 */ 115 public Object get(String key) { 116 Object value = null; 117 Jedis jedis = null; 118 try { 119 jedis = getJedis(); 120 value = jedis.get(key); 121 } catch (Exception e) { 122 logger.warn("获取数据异常:", e); 123 } finally { 124 //返还到连接池 125 returnResource(jedis); 126 } 127 return value; 128 } 129 130 public static void main(String[] args) { 131 Object val = JedisUtil.getInstance().get("redisKey"); 132 System.out.println(val); 133 } 134 135 }
四、redis的概念:
redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
五、数据库连接池的概念:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
六、参考的资料:
http://www.cnblogs.com/linjiqin/archive/2013/06/14/3135248.html
http://www.sjsjw.com/kf_www/article/70_11941_26800.asp
http://www.cnblogs.com/tankaixiong/p/3660075.html
http://blog.csdn.net/truong/article/details/46711045
http://my.oschina.net/ydsakyclguozi/blog/465859