详解 Jedis 的 SCAN、SSCAN、HSCAN、ZSCAN 用法

网上百度和谷歌花了大量的时间去搜索 Jedis 的相关用法,要么不全,要么乱用。基本上没有完整的用例,于是我就写了这篇文章。

参考我前面的那篇文章《删除 Redis 大 Key 让程序出现雪崩导致程序员被开除!》,当我们使用 keys * 进行查询 key 的时候会进行堵塞,导致 redis 整体不可用,而使用 scan 命令则不会。

SCAN、SSCAN、HSCAN、ZSCAN 4 个命令,分别用于集合、哈希键及有序集等。

  • SCAN:命令用于迭代当前数据库中的数据库键。
  • SSCAN:命令用于迭代集合键中的元素。
  • HSCAN:命令用于迭代哈希键中的键值对。
  • ZSCAN:命令用于迭代有序集合中的元素(包括元素成员和元素分值)。

命令格式如下:

SCAN cursor [MATCH pattern] [COUNT count]

scan 游标 MATCH <返回和给定模式相匹配的元素> count 每次迭代所返回的元素数量 SCAN 命令是增量的循环,每次调用只会返回一小部分的元素。所以不会有 KEYS 命令的坑(key 的数量比较多,一次 KEYS 查询会 block 其他操作)。

在 Redis 中的具体用法如下:

scan 0 match xttblog.com* count 5 
sscan myset 0 match *

SCAN 命令对应的 Jedis 中的操作如下:

public static void delLargeListKey(Jedis jedis){
    // 游标初始值为0
    String cursor = ScanParams.SCAN_POINTER_START;
    String key = "test:xttblog:*";
    ScanParams scanParams = new ScanParams();
    scanParams.match(key);// 匹配以 test:xttblog:* 为前缀的 key
    scanParams.count(1000);
    while (true){
        //使用scan命令获取500条数据,使用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);
            //jedis.del(key, mapentry);
            jedis.ltrim("test:xttblog:", 0 ,1);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("删除" + list.size()
            + "条数据,耗时: " + (t2-t1) + "毫秒,cursor:" + cursor);
        if ("0".equals(cursor)){
            break;
        }
    }
}

SSCAN 命令对应的 Jedis 中的操作如下:

public static void delLargeSetKey(Jedis jedis){
    // 游标初始值为0
    String cursor = ScanParams.SCAN_POINTER_START;
    ScanParams scanParams = new ScanParams();
    scanParams.count(1000);
    String key = "test:xttblog";
    while (true){
        //使用sscan命令获取500条数据,使用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);
            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 delLargeHashKey(Jedis jedis){
    // 游标初始值为0
    String cursor = ScanParams.SCAN_POINTER_START;
    ScanParams scanParams = new ScanParams();
    scanParams.count(1000);
    String key = "test:xttblog";
    while (true){
        //使用hscan命令获取500条数据,使用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);
            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 delLargeZSetKey(Jedis jedis){
    // 游标初始值为0
    String cursor = ScanParams.SCAN_POINTER_START;
    String key = "test:xttblog:*";
    ScanParams scanParams = new ScanParams();
    scanParams.match(key);// 匹配以 test:xttblog:* 为前缀的 key
    scanParams.count(1000);
    while (true){
        //使用 zscan 命令获取 500 条数据,使用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;
        }
    }
}

上面 4 个案例都没有对应具体的业务。基本上写的都很明白了。

SCAN 命令、 SSCAN 命令、 HSCAN 命令和 ZSCAN 命令都返回一个包含两个元素的 multi-bulk 回复。

回复的第一个元素是字符串表示的无符号 64 位整数(游标),SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。当 SCAN 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。

回复的第二个元素是另一个 multi-bulk 回复, 这个 multi-bulk 回复包含了本次被迭代的元素。

注意:SCAN 命令不能保证每次返回的值都是有序的,另外同一个 key 有可能返回多次,不做区分,需要应用程序去处理。不要在使用 scan(int) 这个方法,它存在一个 bug,参数应该是 unsigned long 而不是 int,这个方法在以后 jedis 版本大改时会被删除。

详解 Jedis 的 SCAN、SSCAN、HSCAN、ZSCAN 用法

: » 详解 Jedis 的 SCAN、SSCAN、HSCAN、ZSCAN 用法

原创文章,作者:carmelaweatherly,如若转载,请注明出处:https://blog.ytso.com/251995.html

(0)
上一篇 2022年5月3日
下一篇 2022年5月3日

相关推荐

发表回复

登录后才能评论