zoukankan      html  css  js  c++  java
  • 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次

    效果还是很明显的。后续还可以增加对连接池的定时任务监控等~~~

  • 相关阅读:
    PO BO VO DTO POJO DAO DO这些Java中的概念分别指一些什么?
    前端面试题汇总(待续)
    vue lottie vue-lottie : 使用教程
    webstorm 换行时 代码不对齐
    webstorm 导出编辑器配置.editorconfig
    vue 查看dist文件里的结构
    vue-cli 生产打包
    element form 校验数组每一项
    typescript无法识别vue中的$refs
    mac 10.14.5 [vue create的时候 mkdir没有权限]
  • 原文地址:https://www.cnblogs.com/color-my-life/p/5799929.html
Copyright © 2011-2022 走看看