问题描述:
在使用java开发时,需要查询redis,但是key是一个不固定的值,只有一个固定的前缀。如:"testKey:",原策略使用keys方法获取redis库中所有符合条件的key,代码如下:
Set<String> keys = redisUtil.keys("testKey:*");
此方法能够获取到所有前缀为"testKey:"的键集合,然后可以通过get(key)获取到相应的value。但是此方法会造成一下问题:
1、keys()方法是全量扫描的 ,每次都会扫描整个redis库
2、长链接,因为要全量扫描,那么当redis库中键值很多是,这个时间会非常非常长
3、阻塞线程,redis是单线程的,每次访问都需要抢夺权柄,当一个链接持有权柄时,其他的链接都只能排队,等它用完释放权柄后再重新抢夺权柄;那么当一个链接长时间持有权柄时,其它的访问链接一直抢不到权柄,一直排队,随着链接的增多,时间会越来越长,严重可能导致服务无法响应。
解决方法:
1、使用 scan 方法
redis的scan方法同样支持模糊查询,但与keys方法的机制不同,keys方法是拿到权柄做全量扫描,直到把redis所有键扫描完才释放权柄;而scan方法虽然也做全量扫描,但是它可以设置每次扫描的数量(或者叫槽位),例如每次扫描1000个,那么当scan方法扫描到1000个之后,就会释放权柄,然后再重新抢夺,再次查询1000个,直到扫描完。
public Set<String> scanMatch(String matchKey) {
Set<String> keys = new HashSet<>();
RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();
Iterator<RedisClusterNode> iterator = redisClusterNodes.iterator();
while (iterator.hasNext()) {
RedisClusterNode next = iterator.next();
Cursor<byte[]> scan = clusterConnection.scan(next, ScanOptions.scanOptions().match(matchKey).count(100000).build());
while (scan.hasNext()) {
keys.add(new String(scan.next()));
System.out.println("线程:" + Thread.currentThread().getName() + ":当前key数量为:" + keys.size());
}
// boolean unFind = true;
// while (scan.hasNext() && unFind) {
// keys.add(new String(scan.next()));
// if (keys.size() > 10) {
// unFind = false;
// }
// System.out.println("线程:" + Thread.currentThread().getName() + ":当前key数量为:" + keys.size());
// }
}
return keys;
}
但是,此方法同样存在问题。
当redis库中键值对特别多时,scan方法会重复多次,虽然可以设置查多少条后结束,但是速度同样会远远高于正常程序的运行速度,而且redis是一个快速查询的、毫秒级库,scan方法的查询时间可能达到几十秒甚至更长(这主要取决于redis库的大小),显然这对于有性能要求的程序是不太适用的。
2、使用集合方法(推荐)
使用一个固定的redis键,比如:testKeyArray,value设置为集合类型;将以上的键在set进redis中时,同时也将其放入testKeyArray这个键所对应的value中,该value可以是一个键的Set集合;这样就可以直接通过固定的key值(testKeyArray)获取由所有的键组成的Set集合,再去遍历这个Set集合,拿到每一个具体key,在通过key拿到对应的value。
Set<String> keys = null;
Map<String,String> map = new HashMap<>();
//判断key是否存在
if (redisUtil.hasKey(REDISKEY)){
//获取key集合
keys = (Set<String>) redisUtil.get(REDISKEY);
if(keys != null && keys.size() > 0){
//遍历集合,拿到key,再拿到对应的value
for(String key : keys){
String value = redisUtil.get(key);
map.put(key,value);
}
}
}
3、外部表
新建一张数据库表,用来存放redis的key,每次直接从表中读取key,再去通过key拿到对应的value,此方法会增强对数据库的依赖性。
具体使用哪种方式需视实际情况而定。