FunTester框架Redis压测预备

超万字回顾FunTester的前世今生一文中我分享了FunTester测试框架一个优点:针对所有Java可实现的接口都能进行功能封装进而进行性能测试。

之前都已经分享过了HTTP协议、Socket协议以及MySQL的测试案例,最近要准备对Redis的接口进行测试,所以未雨绸缪,我提前将Redis的功能接口封装类重写了一下,不得不说之前对Redis的认知真是肤浅。

话不多说,首先我分享一下自己的思路:

  • Redis连接池管理类
  • Redis资源回收
  • Redis功能封装类

池化技术

在开始正文之前,先分享一个技术名词叫做池化技术。池化技术的应用非常的广泛,比如我们经常说到的线程池、mysql数据库连接池,Http协议连接池以及我们今天要分享的Redis连接池。

池化技术 (Pool) 是一种很常见的编程技巧,在请求量大时能明显优化应用性能,降低系统频繁建连的资源开销。我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将 “昂贵的”、“费时的” 的资源维护在一个特定的 “池子” 中,规定其最小连接数、最大连接数、阻塞队列等配置,方便进行统一管理和复用,通常还会附带一些探活机制、强制回收、监控一类的配套功能。

在做性能测试的过程中,也是用到了很多实话的技术,我的一个基本思路就是拿协议的连接池和线程池做绑定,这样既能避免资源的共享导致的问题,也能够提升性能并发能力。

这里有一个非常重要的知识点,就是资源的回收复用。后面我会分享到。

Redis连接池管理类

这个连接池管理类的主要功能就是创建连接池,设置连接配置。本次我并没有做配置文件,如果大家有需求的话,可以进行配置文件,去把这些配置存起来。

package com.funtester.db.redis;

import com.alibaba.fastjson.JSONObject;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * redis连接池
 */
public class RedisPool extends SourceCode {

    static Logger logger = LogManager.getLogger(RedisPool.class);

    /**
     * 最大连接数
     */
    private static int MAX_TOTAL = 1000;

    /**
     * 在jedispool中最大的idle状态(空闲的)的jedis实例的个数
     */
    private static int MAX_IDLE = 300;

    /**
     * 在jedispool中最小的idle状态(空闲的)的jedis实例的个数
     */
    private static int MIN_IDLE = 10;

    /**
     * 获取实例的最大等待时间
     */
    private static long MAX_WAIT = 5000;

    /**
     * redis连接的超时时间
     */
    private static int TIMEOUT = 5000;

    /**
     * 在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的
     */
    private static boolean testOnBorrow = true;

    /**
     * 在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
     */
    private static boolean testOnReturn = true;

    /**
     * 连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true
     */
    private static boolean blockWhenExhausted = true;

    private static JedisPoolConfig config = getConfig();

    /**
     * 初始化连接池
     */
    public static JedisPool getPool(String host, int port) {
        logger.info("redis连接池IP:{},端口:{},超时设置:{}", host, port, TIMEOUT);
        return new JedisPool(config, host, port, TIMEOUT);
    }

    /**
     * 默认连接池配置
     *
     * @return
     */
    private static JedisPoolConfig getConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(MAX_TOTAL);
        config.setMaxIdle(MAX_IDLE);
        config.setMinIdle(MIN_IDLE);
        config.setTestOnBorrow(testOnBorrow);
        config.setTestOnReturn(testOnReturn);
        config.setBlockWhenExhausted(blockWhenExhausted);
        config.setMaxWaitMillis(MAX_WAIT);
        logger.debug("连接redis配置:{}", JSONObject.toJSONString(config));
        return config;
    }

}

资源回收知识

这里的资源就是指Redis连接池的连接,在我们获取一个连接,进行一些操作之后,我们需要把这个连接重新放回到连接池里面,一共其他线程获取,已达到复用的目的。这里我们主要参考的是官方的实现案例。其实方法也是非常简单的,只需要我们在获取一个连接Jedis对象进行操作之后,调用close()方法即可。

这里我用到了Javatry-catch语法:

 /**
     * 设置key的有效期,单位是秒
     *
     * @param key
     * @param exTime
     * @return
     */
    public boolean expire(String key, int exTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false;
        }
    }

这种写法等同于:

    public boolean expire(String key, int exTime) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false;
        } finally {
            if (jedis != null) jedis.close();
        }
    }

功能封装类

在我开始看Redis操作常用api之前,我觉得这个api的数量会比较少,但是在我详细查完所有api的之后。我就发现自己真的是too young,too simple!这个功能封装类我写了一些非常常用的操作,对于一些不太常用的,这里并没有写出。如果大家有需要,可以通过继承这个com.funtester.db.redis.RedisBase类来添加方法的封装。

package com.funtester.db.redis;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.*;

public class RedisBase {

    private static Logger logger = LogManager.getLogger(RedisBase.class);

    String host;

    int port;

    JedisPool pool;

    public int index;

    public RedisBase() {
    }

    public RedisBase(String host, int port) {
        this.host = host;
        this.port = port;
        pool = RedisPool.getPool(host, port);
    }

    /**
     * 获取jedis操作对象,回收资源方法close,3.0以后废弃了其他方法,默认连接第一个数据库
     * 默认使用0个index
     *
     * @return
     */
    public Jedis getJedis() {
        Jedis resource = pool.getResource();
        resource.select(index);
        return resource;
    }

    /**
     * 设置key的有效期,单位是秒
     *
     * @param key
     * @param exTime
     * @return
     */
    public boolean expire(String key, int exTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.expire(key, exTime) == 1;
        } catch (Exception e) {
            logger.error("expire key:{} error", key, e);
            return false;
        }
    }

    /**
     * 设置key-value,过期时间
     *
     * @param key
     * @param value
     * @param expiredTime 单位s
     * @return
     */
    public boolean set(String key, String value, int expiredTime) {
        try (Jedis jedis = getJedis()) {
            return jedis.setex(key, expiredTime, value).equalsIgnoreCase("OK");
        } catch (Exception e) {
            logger.error("setex key:{} value:{} error", key, value, e);
            return false;
        }
    }

    /**
     * 设置redis内容
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.set(key, value).equalsIgnoreCase("OK");
        } catch (Exception e) {
            logger.error("set key:{} value:{} error", key, value, e);
            return false;
        }
    }

    /**
     * 获取value
     *
     * @param key
     * @return
     */
    public String get(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.get(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 设置redis list内容
     * Redis Lpush 命令将一个或多个值插入到列表头部。 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表类型时,返回一个错误
     *
     * @param key
     * @param list
     * @return list长度
     */
    public Long lpush(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpush(key, list);
        } catch (Exception e) {
            return 0l;
        }
    }


    /**
     * 将一个或多个值插入到已存在的列表头部,列表不存在时操作无效
     *
     * @param key
     * @param list
     * @return
     */
    public Long lpushx(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpushx(key, list);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return 0l;
        }
    }

    /**
     * 将一个或多个值插入到列表的尾部(最右边)。
     * <p>
     * 如果列表不存在,一个空列表会被创建并执行 RPUSH 操作。 当列表存在但不是列表类型时,返回一个错误。
     *
     * @param key
     * @param list
     * @return
     */
    public Long rpush(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpush(key, list);
        } catch (Exception e) {
            return 0l;
        }
    }

    /**
     * 用于将一个或多个值插入到已存在的列表尾部(最右边)。如果列表不存在,操作无效
     *
     * @param key
     * @param list
     * @return
     */
    public Long rpushx(String key, String... list) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpushx(key, list);
        } catch (Exception e) {
            return 0l;
        }
    }

    /**
     * 从数组中获取第一个值,并删除该值
     *
     * @param key
     * @return
     */
    public String lpop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.lpop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 用于移除并返回列表的最后一个元素
     *
     * @param key
     * @return
     */
    public String rpop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.rpop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 获取数组长度
     *
     * @param key
     * @return
     */
    public Long llen(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.llen(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return 0l;
        }
    }

    /**
     * 获取固定索引的值
     *
     * @param key
     * @param index 从0开始
     * @return
     */
    public String lindex(String key, long index) {
        try (Jedis jedis = getJedis()) {
            return jedis.lindex(key, index);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 获取list某一段
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<String> lrange(String key, long start, long end) {
        try (Jedis jedis = getJedis()) {
            return jedis.lrange(key, start, end);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return new ArrayList<>();
        }
    }

    /**
     * 设置list中index的值
     *
     * @param key
     * @param index
     * @param value
     * @return
     */
    public String lset(String key, long index, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.lset(key, index, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 将哈希表 key 中的域 field 的值设为 value
     *
     * @param key
     * @param hkey
     * @param value
     * @return 行数
     */
    public Long hset(String key, String hkey, String value) {
        try (Jedis jedis = getJedis()) {
            return jedis.hset(key, hkey, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回哈希表 key 中给定域 field 的值
     *
     * @param key
     * @param hkey
     * @return
     */
    public String hget(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hget(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略
     *
     * @param key
     * @param hkey
     * @return
     */
    public Long hdel(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hdel(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 查看哈希表 key 中,给定域 field 是否存在
     *
     * @param key
     * @param hkey
     * @return
     */
    public Boolean hexists(String key, String hkey) {
        try (Jedis jedis = getJedis()) {
            return jedis.hexists(key, hkey);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回哈希表 key 中的所有域
     *
     * @param key
     * @return
     */
    public Set<String> hkeys(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hkeys(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回哈希表 key 中所有域的值
     *
     * @param key
     * @return
     */
    public List<String> hvals(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hvals(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回哈希表 key 中域的数量
     *
     * @param key
     * @return
     */
    public Long hlen(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hlen(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回哈希表 key 中,所有的域和值
     *
     * @param key
     * @return
     */
    public Map<String, String> hgetAll(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.hgetAll(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 为哈希表 key 中的域 field 的值加上增量 increment。如果域 field 不存在,域的值先被初始化为 0
     *
     * @param key
     * @param hkey
     * @param num
     * @return
     */
    public Long hincrBy(String key, String hkey, long num) {
        try (Jedis jedis = getJedis()) {
            return jedis.hincrBy(key, hkey, num);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 为哈希表 key 中的域 field 的值加上增量 increment。如果域 field 不存在,域的值先被初始化为 0
     *
     * @param key
     * @param hkey
     * @param num
     * @return
     */
    public Double hincrByFloat(String key, String hkey, double num) {
        try (Jedis jedis = getJedis()) {
            return jedis.hincrByFloat(key, hkey, num);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }


    /**
     * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略
     *
     * @param key
     * @param value
     * @return
     */
    public Long sadd(String key, String... value) {
        try (Jedis jedis = getJedis()) {
            return jedis.sadd(key, value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回集合 key 的基数(集合中元素的数量)
     *
     * @param key
     * @param value
     * @return
     */
    public Long scard(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.scard(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回集合 key 中的所有成员。 不存在的 key 被视为空集合
     *
     * @param key
     * @return
     */
    public Set<String> smembers(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.smembers(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 移除并返回集合中的一个随机元素
     *
     * @param key
     * @return
     */
    public String spop(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.spop(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
     * 如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
     *
     * @param key
     * @param count
     * @return
     */
    public List<String> srandmember(String key, int count) {
        try (Jedis jedis = getJedis()) {
            return jedis.srandmember(key, count);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**
     * 返回集合中的一个随机元素
     *
     * @param key
     * @return
     */
    public String srandmember(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.srandmember(key);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }

    /**移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略
     * @param key
     * @param value
     * @return
     */
    public Long srem(String key,String... value) {
        try (Jedis jedis = getJedis()) {
            return jedis.srem(key,value);
        } catch (Exception e) {
            logger.error("get key:{} error", key, e);
            return null;
        }
    }
    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public boolean exists(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.exists(key);
        } catch (Exception e) {
            logger.error("exists key:{} error", key, e);
            return false;
        }
    }

    /**
     * 删除key
     * jedis返回值1表示成功,0表示失败,可能是不存在的key
     *
     * @param key
     * @return
     */
    public Long del(String... key) {
        try (Jedis jedis = getJedis()) {
            return jedis.del(key);
        } catch (Exception e) {
            logger.error("del key:{} error", key, e);
            return null;
        }
    }

    /**
     * 获取key对应value的类型
     *
     * @param key
     * @return
     */
    public String type(String key) {
        try (Jedis jedis = getJedis()) {
            return jedis.type(key);
        } catch (Exception e) {
            logger.error("type key:{} error", key, e);
            return null;
        }
    }

    /**
     * 获取符合条件的key集合
     *
     * @param pattern
     * @return
     */
    public Set<String> getKeys(String pattern) {
        try (Jedis jedis = getJedis()) {
            return jedis.keys(pattern);
        } catch (Exception e) {
            logger.error("type key:{} error", pattern, e);
            return new HashSet<String>();
        }
    }

    /**
     * 将 value 追加到 key 原来的值的末尾
     *
     * @param key
     * @param content
     * @return
     */
    public Long append(String key, String content) {
        try (Jedis jedis = getJedis()) {
            return jedis.append(key, content);
        } catch (Exception e) {
            logger.error("append key:{} ,content:{},error", key, content, e);
            return null;
        }
    }

    /**
     * 关闭连接池
     */
    public void close() {
        pool.close();
    }

}

Have Fun ~ Tester !