scan命令简介
我们都知道Redis是单线程的,因此我们在使用一些命令(例如keys)的时候需要非常谨慎,可能一不小心就会阻塞进程,进而引起雪崩,这时候 scan命令就有了比较明显的优势;
scan命令简介:
SCAN 命令(在Redis2.8版本引入)是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程,当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
命令格式如下:
SCAN cursor [MATCH pattern] [COUNT count]
其中cursor为游标,MATCH和COUNT为可选参数;
与SCAN 命令相关的命令:
与SCAN 命令相关的命令还有 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令,都是用于增量地迭代(incrementally iterate)一集元素(a collection of elements),区别在于:
1、SCAN 命令用于迭代当前数据库中的数据库键,返回的每个元素都是一个数据库键;
2、SSCAN 命令用于迭代集合键中的元素,返回的每个元素都是一个集合成员;
3、HSCAN 命令用于迭代哈希键中的键值对,返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。
4、ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值),返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。
直接上JAVA代码
/**
* 扫描获取key
* @param prefixKey,redis前缀
* @param jedisCluster,redis集群
* @return
*/
public Map<String,Object> scanKeyValue(String prefixKey, JedisCluster jedisCluster){
Map<String,Object> map = new HashMap<>();
Long start = System.currentTimeMillis();
jedisCluster.getClusterNodes().entrySet().forEach(entry->{
Jedis jedis = new Jedis(entry.getValue().getResource());
ScanResult<String> scanResult = jedis.scan("0",new ScanParams().match(prefixKey+"*"));
while (!scanResult.isCompleteIteration()) {
for(String key :scanResult.getResult()){
map.put(key,jedisCluster.get(key));
}
scanResult = jedis.scan(scanResult.getCursor(),new ScanParams().match(prefixKey+"*"));
}
});
LogUtils.info("scanKeyVlaue="+prefixKey+",time(ms):"+ (System.currentTimeMillis() - start));
LogUtils.info("scanKeyVlaue-size:"+ map.size());
LogUtils.info("scanKeyVlaue-result:"+ JSON.toJSONString(map));
return map;
}
/**
* 扫描获取key列表
* @param prefixKey,redis前缀
* @param jedisCluster 集群
* @return
*/
public Set<String> scanKeyList(String prefixKey, JedisCluster jedisCluster) {
Set<String> resultList = new HashSet<>();
jedisCluster.getClusterNodes().entrySet().forEach(entry->{
Jedis jedis = new Jedis(entry.getValue().getResource());
ScanResult<String> scanResult = jedis.scan("0",new ScanParams().match(prefixKey+"*"));
while (!scanResult.isCompleteIteration()) {
long t1 = System.currentTimeMillis();
int start = resultList.size();
List<String> list = scanResult.getResult();
if(list != null) {
resultList.addAll(list);
}
int end = resultList.size();
scanResult = jedis.scan(scanResult.getCursor(),new ScanParams().match(prefixKey+"*"));
long t2 = System.currentTimeMillis();
// LogUtils.info("获取" + (end-start) + "条数据,耗时: " + (t2 - t1) + "毫秒");
}
});
return resultList;
}
/**
* 扫描获取key,value
* @param prefixKey,redis前缀
* @param jedis,单机
* @return
*/
public Map<String,Object> scanKeyValue(String prefixKey, Jedis jedis) {
Map<String,Object> map = new HashMap<>();
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match(prefixKey);
scanParams.count(1000);
while (true) {
long t1 = System.currentTimeMillis();
//使用scan命令获取数据,使用cursor游标记录位置,下次循环使用
ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
cursor = scanResult.getCursor();// 返回0 说明遍历完成
List<String> list = scanResult.getResult();
if(list != null) {
for(String key :scanResult.getResult()){
map.put(key,jedis.get(key));
}
}
long t2 = System.currentTimeMillis();
// LogUtils.info("获取" + list.size() + "条数据,耗时: " + (t2 - t1) + "毫秒,cursor:" + cursor);
if ("0".equals(cursor)) {
break;
}
}
return map;
}
/**
* 扫描获取key列表
* @param prefixKey,redis前缀
* @param jedis,单机
* @return
*/
public Set<String> scanKeyList(String prefixKey, Jedis jedis) {
Set<String> resultSet = new HashSet<>();
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match(prefixKey);
scanParams.count(1000);
while (true) {
long t1 = System.currentTimeMillis();
//使用scan命令获取数据,使用cursor游标记录位置,下次循环使用
ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
cursor = scanResult.getCursor();// 返回0 说明遍历完成
List<String> list = scanResult.getResult();
if(list != null) {
resultSet.addAll(list);
}
long t2 = System.currentTimeMillis();
// LogUtils.info("获取" + list.size() + "条数据,耗时: " + (t2 - t1) + "毫秒,cursor:" + cursor);
if ("0".equals(cursor)) {
break;
}
}
return resultSet;
}
测试类:
@RunWith(SpringRunner.class)
@Log4j2
@SpringBootTest
public class ScanTest {
private static final String HOST = "192.168.1.XXX";
private static final int PORT = 6379;
@Test
public void scanTest() {
Jedis jedis = new Jedis(HOST, PORT);
// 游标初始值为0
String cursor = ScanParams.SCAN_POINTER_START;
ScanParams scanParams = new ScanParams();
scanParams.match("aaa-*");// 匹配以 aaa-* 为前缀的 key
scanParams.count(1000);
while (true){
//使用scan命令获取数据,使用cursor游标记录位置,下次循环使用
ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
cursor = scanResult.getCursor();// 返回0 说明遍历完成
List<String> list = scanResult.getResult();
long t1 = System.currentTimeMillis();
for(int m = 0;m < list.size();m++){
String mapentry = list.get(m);
System.out.println(mapentry);
//jedis.del(key, mapentry);
}
long t2 = System.currentTimeMillis();
System.out.println("获取" + list.size() + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor);
if ("0".equals(cursor)){
break;
}
}
}
}