一、背景

由于Redis是单线程的,因此在使用一些像KEYS、SMEMBERS等时间复杂度为O(N)的命令时,数据量大的键就会阻塞进程,导致Redis卡顿。

Redis在2.8版本之后增加了SCAN命令:

SCAN cursor [MATCH pattern] [COUNT count]

SCAN及相关命令:

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

优点:

  • SCAN命令每次执行的复杂度为 O(1) ,虽然一次完整迭代的复杂度也是 O(N) ,但它是分次进行的,不会阻塞线程
  • SCAN命令提供了limit参数,可以控制每次返回结果的最大条数

缺点:

  • 对键进行增量式迭代的过程中,如果数据有修改,可能会造成结果的重复,对查询结果只能提供有限的保证(Redis使用了reverse binary iteration算法,保证数据只会重复,不会遗漏)
  • 每次返回的数据条数不一定,极度依赖内部实现

二、用法

  1. 基础命令
    SCAN 命令是一个基于游标的迭代器(cursor based iterator):SCAN命令每次被调用之后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为SCAN命令的游标参数,以此来延续之前的迭代过程。当SCAN命令的游标参数被设置为0时,服务器将开始一次新的迭代, 而当服务器向用户返回值为0的游标时,表示迭代已结束。
// 第一次请求
10.0.1.61-共用开发环境:1>scan 0
1)"7"
2)1)   "key1"
  2)   "Akey9"
  3)   "Bkey12"
  4)   "Akey10"
  5)   "key5"
  6)   "Bkey11"
  7)   "Akey7"
  8)   "key3"
  9)   "Akey8"
  10)  "key2"
 
 
// 第二次请求
10.0.1.61-共用开发环境:1>scan 7
1)"0"
2)1)   "key4"
  2)   "Akey6"
  1. MATCH选项
    和KEYS命令一样,SCAN命令的MATCH选项是一个通配符参数
// 第一次请求
10.0.1.61-共用开发环境:1>scan 0 MATCH key*
1)"7"
2)1)   "key1"
  2)   "key5"
  3)   "key3"
  4)   "key2"
// 第二次请求
10.0.1.61-共用开发环境:1>scan 7 MATCH key*
1)"0"
2)1)   "key4"
  1. COUNT选项
    COUNT 选项只是对增量式迭代命令的一种提示(hint),但是在大多数情况下,这种提示都是有效的(在数据量少的情况下,COUNT值与返回的结果数量不相等)。
10.0.1.61-共用开发环境:1>scan 0 MATCH key* COUNT 1
1)"8"
2)1)   "key1"

三、JAVA RedisTemplate 中的应用

/**
 * count可以直接写死为固定数值
 */
 
 
@Resource(name = "redisTemplate_15")
private RedisTemplate redis;
 
 
// SCAN 命令
public List<String> sCan(String match, Integer count) {
    List<String> result = new ArrayList<>();
    try {
        Set<String> key = new HashSet<>();
        Cursor<byte[]> cursor = redis.getConnectionFactory().getConnection().scan(
                ScanOptions.scanOptions().match(match).count(count).build()
        );
 
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
        }
        cursor.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}
 
 
// SSCAN 命令
public List<String> sSCan(String key, String match, Integer count) {
    List<String> result = new ArrayList<>();
    try {
        Cursor<String> cursor = redis.opsForSet().scan(key,
                ScanOptions.scanOptions().match(match).count(count).build()
        );
        while (cursor.hasNext()) {
            result.add(cursor.next());
        }
        cursor.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}
 
 
// HSCAN 命令
public Map<String, String> hSCan(String key, String match, Integer count) {
    Map<String, String> result = new HashMap<>(0);
    try {
        Cursor<Map.Entry<String, String>> cursor = redis.opsForHash().scan(key,
                ScanOptions.scanOptions().match(match).count(count).build()
        );
        while (cursor.hasNext()) {
            Map.Entry<String, String> cursorMap = cursor.next();
            result.put(cursorMap.getKey(), cursorMap.getValue());
        }
        cursor.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}
 
 
// ZSCAN 命令
public Set<ZSetOperations.TypedTuple<String>> zSCan(String key, String match, Integer count) {
    Set<ZSetOperations.TypedTuple<String>> result = new HashSet<>();
    try {
        Cursor<ZSetOperations.TypedTuple<String>> cursor = redis.opsForZSet().scan(key,
                ScanOptions.scanOptions().match(match).count(count).build()
        );
        while (cursor.hasNext()) {
            result.add(cursor.next());
        }
        cursor.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return result;
}
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐