一、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。
代碼示例:
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