Redis客戶端鏈接池

  

使用場景

對於一些大對象,或者初始化過程較長的可複用的對象,咱們若是每次都new對象出來,那麼意味着會耗費大量的時間。

咱們能夠將這些對象緩存起來,當接口調用完畢後,不是銷燬對象,當下次使用的時候,直接從對象池中拿出來便可。

下面以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鏈接對象的生命週期的管理,結合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     }
redis帶鏈接池的客戶端

這裏jedis採用單機的形式。
首先是afterPropertiesSet方法,這裏對jedis鏈接池作了一些配置,好比池的大小,borrow jedis鏈接的時候等待時間(borrow的時候採用的樂觀鎖),池中空閒對象超過多少的時候,return鏈接直接就銷燬。。。等等
而後是putobjWithExp方法,這裏首先是從池中borrow一個連接,若是池中沒有的話,commons-pool會自動建立。而後獲取到鏈接了之後,調用下jedis的set方法,將數據保存。json

這裏採用的是fastJson來作序列化的,保存的內容也是String格式的。 而tair是支持自定義序列化工具的,並且它的序列化是

最後,將jedis鏈接歸還到pool去就好啦。
除了對象複用之外,其實尚未提到一個很重要的使用對象池的緣由:緩存

對於鏈接池的場景而言,鏈接是有限的資源,不採用池化,那麼沒法對資源的分配進行管理。

打個比方,不採用鏈接池,每一個請求進來生成一個鏈接,那麼若是忽然某個業務請求量遞增,直接致使鏈接數都被該系統佔用了。可是採用了鏈接池,不單單能夠對象複用,同時還能作資源的管控。ide

測試:

測試環境:10個線程,同時採用jedispool和非jedispool的方式向redis put數據,put的數據同樣,一共put 50000次,如下是測試結果。
帶jedis鏈接池的--->任務執行完畢,執行時間5055ms,共有50000個任務,執行異常0次
不帶鏈接池的--->任務執行完畢,執行時間14654ms,共有50000個任務,執行異常0次工具

效果仍是很明顯的。後續還能夠增長對鏈接池的定時任務監控等~~~gitlab

相關文章
相關標籤/搜索