wangxiaoxue 2020-01-23
我们都知道Redis是单线程的,因此我们在使用一些命令(例如keys)的时候需要非常谨慎,可能一不小心就会阻塞进程,进而引起雪崩,这时候 scan命令就有了比较明显的优势;
SCAN 命令(在Redis2.8版本引入)是一个基于游标的迭代器(cursor based iterator): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程,当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
命令格式如下:
SCAN cursor [MATCH pattern] [COUNT count]
其中cursor为游标,MATCH和COUNT为可选参数;
迭代过程示例如下(查询以PLFX-ZZSFP-为前缀的key):
127.0.0.1:6379> scan 0 match PLFX-ZZSFP-* count 5 1) "1536" 2) (empty list or set) 127.0.0.1:6379> scan 1536 match PLFX-ZZSFP-* count 5 1) "1280" 2) (empty list or set) 127.0.0.1:6379> scan 1280 match PLFX-ZZSFP-* count 5 1) "1664" 2) (empty list or set) 127.0.0.1:6379> scan 1664 match PLFX-ZZSFP-* count 10 1) "1216" 2) (empty list or set) 127.0.0.1:6379> scan 1216 match PLFX-ZZSFP-* count 10 1) "32" 2) (empty list or set) 127.0.0.1:6379> scan 32 match PLFX-ZZSFP-* count 10 1) "1184" 2) (empty list or set) 127.0.0.1:6379> scan 1184 match PLFX-ZZSFP-* count 10 1) "1888" 2) 1) "PLFX-ZZSFP-AYHZ-10112301000032441693" 127.0.0.1:6379> scan 1888 match PLFX-ZZSFP-* count 10 1) "2016" 2) (empty list or set) ...... 127.0.0.1:6379> scan 3 MATCH * COUNT 1 1) "0" 2) (empty list or set)
从上面迭代过程可以看到增量式迭代命令并不保证每次执行都返回某个给定数量的元素。增量式命令甚至可能会返回零个元素, 但只要命令返回的游标不是 0 , 应用程序就不应该将迭代视作结束;
与SCAN 命令相关的命令还有 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令,都是用于增量地迭代(incrementally iterate)一集元素(a collection of elements),区别在于:
1、SCAN 命令用于迭代当前数据库中的数据库键,返回的每个元素都是一个数据库键;
2、SSCAN 命令用于迭代集合键中的元素,返回的每个元素都是一个集合成员;
3、HSCAN 命令用于迭代哈希键中的键值对,返回的每个元素都是一个键值对,一个键值对由一个键和一个值组成。
4、ZSCAN 命令用于迭代有序集合中的元素(包括元素成员和元素分值),返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。
SCAN 命令对应java的 Jedis 中的操作示例如下:
public static void testScan(Jedis jedis){ // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; String key = "PLFX-ZZSFP-*"; ScanParams scanParams = new ScanParams(); scanParams.match(key);// 匹配以 PLFX-ZZSFP-* 为前缀的 key scanParams.count(1000); while (true){ //使用scan命令获取数据,使用cursor游标记录位置,下次循环使用 ScanResult<String> scanResult = jedis.scan(cursor, scanParams); cursor = scanResult.getStringCursor();// 返回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; } } }
SSCAN 命令对应的 Jedis 中的操作如下:
public static void testSscan(Jedis jedis) { // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; ScanParams scanParams = new ScanParams(); scanParams.count(1000); String key = "PLFX-ZZSFP"; while (true) { //使用sscan命令获取数据,使用cursor游标记录位置,下次循环使用 ScanResult<String> sscanResult = jedis.sscan(key, cursor, scanParams); cursor = sscanResult.getStringCursor();// 返回0 说明遍历完成 List<String> scanResult = sscanResult.getResult(); long t1 = System.currentTimeMillis(); for (int m = 0; m < scanResult.size(); m++) { String mapentry = scanResult.get(m); System.out.println(mapentry); jedis.srem(key, mapentry);//处理过程,可以换成其他的 } long t2 = System.currentTimeMillis(); System.out.println("处理" + scanResult.size() + "条数据,耗时: " + (t2 - t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)) { break; } } }
HSCAN 命令对应的 Jedis 中的操作如下:
public static void testHcan(Jedis jedis) { // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; ScanParams scanParams = new ScanParams(); scanParams.count(1000); String key = "PLFX-ZZSFP"; while (true) { //使用hscan命令获取数据,使用cursor游标记录位置,下次循环使用 ScanResult<Map.Entry<String, String>> hscanResult = jedis.hscan(key, cursor, scanParams); cursor = hscanResult.getStringCursor();// 返回0 说明遍历完成 List<Map.Entry<String, String>> scanResult = hscanResult.getResult(); long t1 = System.currentTimeMillis(); for (int m = 0; m < scanResult.size(); m++) { Map.Entry<String, String> mapentry = scanResult.get(m); System.out.println(mapentry); jedis.hdel(key, mapentry.getKey());//处理过程,可以换成其他的 } long t2 = System.currentTimeMillis(); System.out.println("处理" + scanResult.size() + "条数据,耗时: " + (t2 - t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)) { break; } } }
ZSCAN 命令对应的 Jedis 中的操作如下:
public static void testZscan(Jedis jedis) { // 游标初始值为0 String cursor = ScanParams.SCAN_POINTER_START; String key = "PLFX-ZZSFP-*"; ScanParams scanParams = new ScanParams(); scanParams.match(key);// 匹配以 PLFX-ZZSFP-* 为前缀的 key scanParams.count(1000); while (true) { //使用 zscan 命令获取数据,使用cursor游标记录位置,下次循环使用 ScanResult<Tuple> scanResult = jedis.zscan(key, cursor, scanParams); cursor = scanResult.getStringCursor();// 返回0 说明遍历完成 List<Tuple> list = scanResult.getResult(); long t1 = System.currentTimeMillis(); for (int m = 0; m < list.size(); m++) { Tuple tuple = list.get(m); System.out.println("Element:" + tuple.getElement() + ",Score:" + tuple.getScore()); } long t2 = System.currentTimeMillis(); System.out.println("处理" + list.size() + "条数据,耗时: " + (t2 - t1) + "毫秒,cursor:" + cursor); if ("0".equals(cursor)) { break; } } }
本文只是简单介绍了一下RedisScan命令,及在java中使用Jedis实现Scan命令的方式;