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()
方法即可。
这里我用到了Java
中try-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 !
版权声明: 本文为 InfoQ 作者【FunTester】的原创文章。
原文链接:【http://xie.infoq.cn/article/99a724860808f1275e3173167】。文章转载请联系作者。
FunTester
公众号:FunTester,650+原创,欢迎关注 2020.10.20 加入
Have Fun,Tester! 公众号FunTester,坚持原创文章的测试人。 FunTester测试框架作者,DCS_FunTester分布式性能测试框架作者。
评论