对大量交易信息放入redis,且单一key数据量存在存储上限,单一key放置数据量,数据量大,使用数据是逻辑操作耗时较多,将数据存储方式进行优化,将数据分片存储,设置整体存储总量,设置每个分片存储size,分片过期时间,整体过期时间。优化后的逻辑已经经过生产验证,效果提升。
代码思路是:
1.在redis中设置一个set,用于存储分片key,在每次存储元素是set的key都会进行过期时间的延长。
2.分片中放入数据时,需要判断: 1) 当前是否存在分片,不存在,直接新建返回,
2)存在分片,且当前分片为达到设置的分片的最大数据量,(判断最新分片是否元素存储元素是否已经达到上限,未到达上限直接将元素,塞进去即可,并延长过期时间。若已到达分片size,在在创建新分片进行存储)
3)存在分片,且已经达到最大分片数据,那么清除最早的分片,将数据丢弃,并创建新的分片,将数据存储在最新的分片上,设置过期时间。
/**
* 设置key比splitKey过期时间长,默认多1分钟
*/
private static final int EXPIRE_ADD = 60;
/**
* 定义splitKey常量前缀。splitKey有两部分构成 [ 前缀+序号 ]
*/
private static final String SPLIT_KEY_PAD = "MCT";
/**
* 分片添加数据redis种
*
* @param jedisCluster redis连接 jedisCluster 可以按照你的使用框架进行替换为对应的实例对象,但是下方中关于设置的相关操作也需要改为对应的语法。
* @param key 一级key 信息中可区分的标识即可
* @param hashFieldKey 存放hash时field的值 [使用信息中的唯一标识ID]
* @param value 添加的数据对象 [商户交易]
* @param limitSize 总存放大小,用以计算清理路由set [设置为1000*10]
* @param shardingSize 数据每片大小 [推荐设置为1000]
* @param sends 数据存放时间 [设置为1小时]
* @return
*/
public static AddRes add(JedisCluster jedisCluster,
String key, String hashFieldKey,
Object value, int limitSize, int shardingSize,
int sends) {
//key值校验
if (key == null || key.length() < 1) {
logger.info("key参数值有误 {}", key);
return null;
}
//hashFeildKey值校验
if (hashFieldKey == null || hashFieldKey.length() < 1) {
logger.info("hashFieldKey参数值有误 {}", hashFieldKey);
return null;
}
//limitSize & splitSize & sends 值校验
if (sends < 1 || shardingSize < 1 || limitSize < shardingSize) {
logger.info("sends & splitSize & limitSize 有误 {},{},{}", sends, shardingSize, limitSize);
return null;
} //二级splitKey计算
SplitKeyDto splitKeyDto = generateSplitKey(jedisCluster, key, limitSize, shardingSize);
String splitKey = splitKeyDto.getSplitKey();
//返回值构建
AddRes addRes = new AddRes(key, -1, splitKey, -1, -1, -1);
//二级splitKey添加到路由zset
if (splitKeyDto.isNew) {
addRes.setSplitKeyAdd(jedisCluster.sadd(key, splitKey));
}
//重置大key过期
addRes.setKeyExpire(jedisCluster.expire(key, sends + EXPIRE_ADD));
//hash中添加交易
addRes.setValueAdd(jedisCluster.hset(splitKey, hashFieldKey, JSON.toJSONString(value)));
//设置splitKey过期
addRes.setSplitKeyExpire(jedisCluster.expire(splitKey, sends));
logger.info("key={},hashFieldKey={}添加结果:{}", key, hashFieldKey, addRes);
return addRes; }
/**
* 利用路由表计算出本次数据该放入的分片splitKey
* 在生成分片splitKey时候会计算路由表大小,如果大小超过限制
* 会做一次清理计算
*
* @param jedisCluster redis连接
* @param key 一级key
* @param limitSize 总存放大小,用以计算清理路由zset
* @param shardingSize 数据每片大小
* @return 本次数据该放入的分片key
*/
private static SplitKeyDto generateSplitKey(JedisCluster jedisCluster, String key, int limitSize, int shardingSize) {
SplitKeyDto res = new SplitKeyDto(true, String.format("%s_%s$1", SPLIT_KEY_PAD,key));
Set<String> keys = jedisCluster.smembers(key);
if (keys == null) {
return res;
}
int keysSize = keys.size();
if (keysSize < 1) {
return res;
} long max = 1;
TreeSet<String> treeSet = new TreeSet<>((x, y) -> {
long longX = Long.valueOf(x.split("\\$")[1]);
long longY = Long.valueOf(y.split("\\$")[1]);
return (int) (longX - longY);
});
//路由表过大,做一次清理
if (keysSize > limitSize / shardingSize) {
//路由表排序[正序]
treeSet.addAll(keys);
max = Long.valueOf(treeSet.last().split("\\$")[1]);
clearKeys(jedisCluster, key, treeSet, keysSize);
} else {
treeSet.addAll(keys);
max = Long.valueOf(treeSet.last().split("\\$")[1]);
}
res.setNew(false);
res.setSplitKey(String.format("%s_%s$%d", SPLIT_KEY_PAD,key, max));
Long len = jedisCluster.hlen(res.getSplitKey());
if (len >= shardingSize) {
res.setNew(true);
res.setSplitKey(String.format("%s_%s$%d", SPLIT_KEY_PAD,key, max + 1));
}
return res;
}