最近做的项目的技术需求,里面需要scan redis里面的所有key,在单机下面的话,使用一下方法是直接没问题的,附上工具代码
public List<String> getKeysByPattern(String patternKey, Integer scanCount) {
try {
long start = System.currentTimeMillis();
List<String> resultKeys = objectRedisTemplate.execute((RedisCallback<List<String>>) connection -> {
List<String> tmpKeys = Lists.newArrayList();
Jedis jedis = (Jedis) connection.getNativeConnection();
ScanParams scanParams = new ScanParams();
scanParams.match(patternKey);
scanParams.count(scanCount);
long start1 = System.currentTimeMillis();
ScanResult<String> scan = jedis.scan("0", scanParams);
log.info("patternKey:[{}],scan once time:[{}]", patternKey, System.currentTimeMillis() - start1);
long start2 = System.currentTimeMillis();
while (null != scan.getStringCursor()) {
tmpKeys.addAll(scan.getResult());
log.info("patternKey:[{}],scan getResult time:[{}]", patternKey, System.currentTimeMillis() - start2);
start2 = System.currentTimeMillis();
if (!StringUtils.equals("0", scan.getStringCursor())) {
scan = jedis.scan(scan.getStringCursor(), scanParams);
continue;
} else {
break;
}
}
return tmpKeys;
});
log.info("scan扫描共耗时:{} ms,patternKey[{}],key数量:{}", System.currentTimeMillis() - start, patternKey, resultKeys.size());
return resultKeys;
} catch (Exception e) {
log.error("getKeysByPattern Exception :[{}]", e.getMessage(), e);
}
return null;
}
但是如果在redis cluster集群下 会报node节点异常,原因是使用RedisTemplate这个工具没办法做协调节点,后面找了很多博客做什么每个节点遍历都不行。
后面看到有人说 可以设计 key 特定前缀的key 落在某一个节点,因为的的key设计里面有时间,所以redis的key可以这么设计
key: {xxxxxxx:202104271530}:xxx:xxx
这样的话 同一个分钟的数据就会落在同一个节点,而不同分钟的数据可以落在不同节点 ( {}的作用是使 里面内容一样的key 落在同一个节点)
不过虽然scan游标的话不会像keys一样阻塞线程,但是如果频繁的请求redis的话,会造成redis的cpu负载过大,从而拖慢服务速度,而且scan的速度不快。因此,真心不推荐用scan。
后面我就想了另一种方案解决,就是做二级索引,采用 redis 的 set结构
key:{xxxxxx:202104271530}
value:[
{xxxxxx:202104271530}:1111
{xxxxxx:202104271530}:2222
.....
{xxxxxx:202104271530}:9999
]
这样的话就不需要去scan整个redis集群,直接通过特定前缀获取所有的reids key,但是这样还是有一个问题,如果遇到所有的key的数量太过庞大 set 太大的话 在做get的时候也会有问题
这样的话 其实可以对前缀key进行分片优化,分片方法如下:
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import java.util.SortedMap;
import java.util.TreeMap;
public class PartitionByMurmurHashFunction {
private static final int DEFAULT_VIRTUAL_BUCKET_TIMES = 160;
private static final int DEFAULT_WEIGHT = 1;
private int count;
private int seed;
private int virtualBucketTimes = DEFAULT_VIRTUAL_BUCKET_TIMES;
private HashFunction hash;
private SortedMap<Integer, Integer> bucketMap;
public PartitionByMurmurHashFunction(int count) {
this.count = count;
this.bucketMap = new TreeMap<>();
generateBucketMap();
}
private void generateBucketMap() {
hash = Hashing.murmur3_32(seed);//计算一致性哈希的对象
for (int i = 0; i < count; i++) {//构造一致性哈希环,用TreeMap表示
StringBuilder hashName = new StringBuilder("SHARD-").append(i);
for (int n = 0, shard = virtualBucketTimes * DEFAULT_WEIGHT; n < shard; n++) {
bucketMap.put(hash.hashUnencodedChars(hashName.append("-NODE-").append(n)).asInt(), i);
}
}
}
public Integer calculate(String columnValue) {
SortedMap<Integer, Integer> tail = bucketMap.tailMap(hash.hashUnencodedChars(columnValue).asInt());
if (tail.isEmpty()) {
return bucketMap.get(bucketMap.firstKey());
}
return tail.get(tail.firstKey());
}
public static void main(String[] args) {
PartitionByMurmurHashFunction pp = new PartitionByMurmurHashFunction(10);
System.out.println(pp.calculate("166666661"));
System.out.println(pp.calculate("260266661"));
System.out.println(pp.calculate("366788663"));
System.out.println(pp.calculate("466666664"));
System.out.println(pp.calculate("566125666"));
System.out.println(pp.calculate("666900666"));
}
}
采用该方法分片后的key:
key:{xxxxxx:202104271530}:0
value:[
{xxxxxx:202104271530}:1111
.....
]
key:{xxxxxx:202104271530}:1
value:[
{xxxxxx:202104271530}:2222
.....
]
这样就解决了