下面以redis客戶端舉例,說明下鏈接池的基礎實現。
commons-pool2,是經常使用的對象池工具包,實現了對象池中對象的整個生命週期的管理,同時還能夠手動指定對象生命週期的調度閥值。
Jedis是java的redis客戶端的實現,可以實現對redis單機以及切片集羣的連接。使用起來還很方便。
下面使用Jedis和commons-pool實現客戶端鏈接池的管理。java
首先定義生成Jedis連接的工廠git
1 public class JedisPooledFactory extends BasePooledObjectFactory<Jedis> { 2 3 //jedis server url 4 private String url = null; 5 6 //redis server port 7 private int port = 6379; 8 9 /** 10 * @param url 11 * @param port 12 */ 13 public JedisPooledFactory(String url, int port) { 14 super(); 15 this.url = url; 16 this.port = port; 17 } 18 19 /** 20 * @see org.apache.commons.pool2.BasePooledObjectFactory#create() 21 */ 22 @Override 23 public Jedis create() throws Exception { 24 Assert.notNull(url); 25 return new Jedis(url, port); 26 } 27 28 @Override 29 public boolean validateObject(PooledObject<Jedis> p) { 30 //if closed,validate error 31 if(!p.getObject().isConnected()){ 32 return false; 33 } 34 return super.validateObject(p); 35 } 36 37 @Override 38 public void destroyObject(PooledObject<Jedis> p) throws Exception { 39 // close the connection 40 p.getObject().close(); 41 super.destroyObject(p); 42 } 43 44 /** 45 * @see org.apache.commons.pool2.BasePooledObjectFactory#wrap(java.lang.Object) 46 */ 47 @Override 48 public PooledObject<Jedis> wrap(Jedis obj) { 49 return new DefaultPooledObject<Jedis>(obj); 50 } 51 }
咱們能夠看到,這個工廠主要是實現了對Jedis鏈接對象的生命週期的管理,結合Jedis來講明定義的行爲:1.怎麼建立Jedis鏈接(好比鏈接池中jedis鏈接不夠用的時候)。2.怎麼銷燬對象(好比鏈接池中大量空閒鏈接)。3.每次borrow/return Jedis鏈接的時候,判斷jedis鏈接的有效性。,若是無效就將該對象銷燬,而後從新borrow。4.wrap,將任意對象池化的時候,須要讓對象支持一些對象池中的特定的一些特性,好比在對象池中,若是空閒對象超過了閥值而且超過了必定的時間,borrow的時候就清除掉對象,這個意味着池中的對象須要支持池化後的一些特性,主要是與生命狀態相關的特性。那麼這個wrap就是對象的包裝類,有個默認的實現:redis
DefaultPooledObject
咱們如今要開始使用Jedis鏈接工廠了apache
1 public class RedisClientImpl implements InitializingBean, RedisClient { 2 3 private final static Logger logger = LoggerFactory.getLogger(RedisClientImpl.class); 4 5 /** redis url */ 6 private String url = null; 7 private int port = 6379; 8 9 /** 10 * The Max wait time. 11 */ 12 private int maxWaitTime = 1000; 13 14 /** jedis池化 */ 15 private GenericObjectPool<Jedis> jedisPool = null; 16 17 /** 18 * Instantiates a new Redis client. 19 */ 20 public RedisClientImpl() { 21 } 22 23 /** 24 * Instantiates a new Redis client. 25 * 26 * @param url the url 27 * @param port the port 28 */ 29 public RedisClientImpl(String url, int port){ 30 setPort(port); 31 setUrl(url); 32 } 33 34 /** 35 * 不帶異常的put數據 36 * @param key 37 * @param value 38 */ 39 public void putobjWithExp(String key, Object value) { 40 Jedis jedis = null; 41 try { 42 jedis = getJedis(); 43 jedis.set(key, JSON.toJSONString(value)); 44 } catch (Exception e) { 45 logger.error("獲取Jedis異常", e); 46 } finally { 47 if (jedis != null) { 48 returnJedis(jedis); 49 } 50 } 51 } 52 53 /** 54 * 獲取jedis 55 * @return 從池中獲取jedis 56 * @throws Exception 57 */ 58 private Jedis getJedis() throws Exception { 59 LoggerUtils.info(logger, "borrow jedis,borrowwed=", jedisPool.getNumActive(), ",maxTotal=", 60 jedisPool.getMaxTotal()); 61 return jedisPool.borrowObject(maxWaitTime); 62 } 63 64 /** 65 * 歸還jedis 66 * @param jedis 67 */ 68 private void returnJedis(Jedis jedis) { 69 LoggerUtils.info(logger, "return jedis,borrowwed=", jedisPool.getNumActive(), ",maxTotal=", 70 jedisPool.getMaxTotal()); 71 jedisPool.returnObject(jedis); 72 } 73 74 /** 75 * Setter method for property <tt>port</tt>. 76 * 77 * @param port value to be assigned to property port 78 */ 79 public void setPort(int port) { 80 this.port = port; 81 } 82 83 /** 84 * Setter method for property <tt>url</tt>. 85 * 86 * @param url value to be assigned to property url 87 */ 88 public void setUrl(String url) { 89 this.url = url; 90 } 91 92 @Override 93 public void afterPropertiesSet() throws Exception { 94 LoggerUtils.info(logger, "開始初始化jedis池,url=", url, ",port=", port); 95 Assert.notNull(url); 96 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); 97 poolConfig.setBlockWhenExhausted(true); 98 poolConfig.setMaxWaitMillis(100); 99 poolConfig.setLifo(false); 100 poolConfig.setMaxIdle(50);//// 最大空閒鏈接數 101 poolConfig.setMinIdle(20);// 最小空閒鏈接數 102 poolConfig.setMaxTotal(500);// 整個池的最大值,最大鏈接數 103 poolConfig.setTestOnBorrow(true); 104 poolConfig.setTestOnCreate(true); 105 poolConfig.setTestOnReturn(true); 106 poolConfig.setTestWhileIdle(false); 107 jedisPool = new GenericObjectPool<>(new JedisPooledFactory(url, port), poolConfig); 108 }
這裏jedis採用單機的形式。
首先是afterPropertiesSet方法,這裏對jedis鏈接池作了一些配置,好比池的大小,borrow jedis鏈接的時候等待時間(borrow的時候採用的樂觀鎖),池中空閒對象超過多少的時候,return鏈接直接就銷燬。。。等等
而後是putobjWithExp方法,這裏首先是從池中borrow一個連接,若是池中沒有的話,commons-pool會自動建立。而後獲取到鏈接了之後,調用下jedis的set方法,將數據保存。json
最後,將jedis鏈接歸還到pool去就好啦。
除了對象複用之外,其實尚未提到一個很重要的使用對象池的緣由:緩存
打個比方,不採用鏈接池,每一個請求進來生成一個鏈接,那麼若是忽然某個業務請求量遞增,直接致使鏈接數都被該系統佔用了。可是採用了鏈接池,不單單能夠對象複用,同時還能作資源的管控。ide
測試環境:10個線程,同時採用jedispool和非jedispool的方式向redis put數據,put的數據同樣,一共put 50000次,如下是測試結果。
帶jedis鏈接池的--->任務執行完畢,執行時間5055ms,共有50000個任務,執行異常0次
不帶鏈接池的--->任務執行完畢,執行時間14654ms,共有50000個任務,執行異常0次工具
效果仍是很明顯的。後續還能夠增長對鏈接池的定時任務監控等~~~gitlab