写点什么

FunTester 框架 Redis 压测预备

用户头像
FunTester
关注
发布于: 2 小时前

超万字回顾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 !

发布于: 2 小时前阅读数: 3
用户头像

FunTester

关注

公众号:FunTester,650+原创,欢迎关注 2020.10.20 加入

Have Fun,Tester! 公众号FunTester,坚持原创文章的测试人。 FunTester测试框架作者,DCS_FunTester分布式性能测试框架作者。

评论

发布
暂无评论
FunTester框架Redis压测预备