Redis客戶端連接池


  

使用場景

對於一些大對象,或者初始化過程較長的可復用的對象,我們如果每次都new對象出來,那么意味着會耗費大量的時間。

我們可以將這些對象緩存起來,當接口調用完畢后,不是銷毀對象,當下次使用的時候,直接從對象池中拿出來即可。

下面以redis客戶端舉例,說明下連接池的基礎實現。
commons-pool2,是常用的對象池工具包,實現了對象池中對象的整個生命周期的管理,同時還可以手動指定對象生命周期的調度閥值。
Jedis是java的redis客戶端的實現,能夠實現對redis單機以及切片集群的鏈接。使用起來還很方便。
下面使用Jedis和commons-pool實現客戶端連接池的管理。

首先定義生成Jedis鏈接的工廠

 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就是對象的包裝類,有個默認的實現:

DefaultPooledObject

我們現在要開始使用Jedis連接工廠了

  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方法,將數據保存。

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

最后,將jedis連接歸還到pool去就好啦。
除了對象復用以外,其實還沒有提到一個很重要的使用對象池的原因:

對於連接池的場景而言,連接是有限的資源,不采用池化,那么無法對資源的分配進行管理。

打個比方,不采用連接池,每個請求進來生成一個連接,那么如果突然某個業務請求量遞增,直接導致連接數都被該系統占用了。但是采用了連接池,不僅僅可以對象復用,同時還能做資源的管控。

測試:

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

效果還是很明顯的。后續還可以增加對連接池的定時任務監控等~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM