使用redis的scan指令详解

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 使用redis的scan指令详解

在生产环境中使用了keys指令之后容易导致出现短时间内的请求堵塞,这种情况在高并发环境中是比较致命的存在,因此需要尽可能地避免这种情况发生。



常用的查询某些key的指令:


scan


jedis使用方式:


public List<String> scanAll(String cursor, String pattern, Integer limit) {
    try (Jedis jedis = iRedisFactory.getConnection()) {
        List<String> scanList = new LinkedList<>();
        ScanParams scanParams = new ScanParams();
        scanParams.match(pattern);
        scanParams.count(limit);
        while (true) {
            long begin = System.currentTimeMillis();
            ScanResult<String> scanResult = jedis.scan(cursor, scanParams);
            if (ScanParams.SCAN_POINTER_START.equals(scanResult.getCursor())) {
                break;
            }
            System.out.println("耗时:" + (System.currentTimeMillis() - begin) + "ms,查询数目:" + scanResult.getResult().size());
            scanList.addAll(scanResult.getResult());
            cursor = scanResult.getCursor();
        }
        return scanList;
    } catch (Exception e) {
        log.error("jedis scanAll has error, error is ", e);
    }
    return null;
}
复制代码


虽然在使用scan指令的时候复杂度也是o(n)会有部分堵塞,但是由于是多次请求,相当于将之前的keys指令分成了多次小范围的搜索,减少堵塞的时长。


\

sscan


jedis使用方式


public List<String> sScanAll(String key,String cursor, String pattern, Integer limit) {
    try(Jedis jedis = iRedisFactory.getConnection()) {
        List<String> resultList = new LinkedList<>();
        while (true) {
            ScanResult<String> scanResult = jedis.sscan(key,cursor);
            cursor = scanResult.getCursor();
            resultList.addAll(scanResult.getResult());
            if (ScanParams.SCAN_POINTER_START.equals(cursor)){
                break;
            }
        }
        return resultList;
    }catch (Exception e){
        log.error("jedis sscanAll has error, error is ", e);
    }
    return null;
}
复制代码


其实sscan指令结合名字就可以猜到,这是一个用于set集合使用的遍历指令,比较推荐在对set集合中进行遍历使用,例如当我们的set集合元素过多的时候,直接使用smembers指令容易造成堵塞,此时使用sscan指令可以减少堵塞的情况发生。


\

hscan


jedis使用方式


public Map<String,String> hScanAll(String key, String cursor, String pattern, Integer limit) {
    try(Jedis jedis = iRedisFactory.getConnection()) {
        ScanParams scanParams = new ScanParams();
        scanParams.match(pattern);
        scanParams.count(limit);
        Map<String,String> resultList = new HashMap<>();
        while (true) {
            ScanResult scanResult = jedis.hscan(key,cursor,scanParams);
            cursor = scanResult.getCursor();
            if (ScanParams.SCAN_POINTER_START.equals(cursor)){
                break;
            }
            List<Map.Entry<String,String>> result = scanResult.getResult();
            for (Map.Entry<String, String> entry : result) {
                resultList.put(entry.getKey(),entry.getValue());
            }
        }
        return resultList;
    }catch (Exception e){
        log.error("jedis sscanAll has error, error is ", e);
    }
    return null;
}
复制代码


这是一个用于hashmap集合使用的遍历指令

\

zscan


jedis使用方式


public List<String> zScanAll(String key, String cursor, String pattern, Integer limit) {
    try(Jedis jedis = iRedisFactory.getConnection()) {
        ScanParams scanParams = new ScanParams();
        scanParams.match(pattern);
        scanParams.count(limit);
        List<String> resultList = new LinkedList<>();
        while (true) {
            ScanResult scanResult = jedis.zscan(key,cursor,scanParams);
            cursor = scanResult.getCursor();
            if (ScanParams.SCAN_POINTER_START.equals(cursor)){
                break;
            }
            resultList.addAll(scanResult.getResult());
        }
        return resultList;
    }catch (Exception e){
        log.error("jedis sscanAll has error, error is ", e);
    }
    return null;
}
复制代码


这是一个用于有序集合使用的遍历指令


\

在Centos操作系统上边执行这些指令的案例:


scan指令


redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2)  1) "key:611"
    2) "key:711"
    3) "key:118"
    4) "key:117"
    5) "key:311"
    6) "key:112"
    7) "key:111"
    8) "key:110"
    9) "key:113"
   10) "key:211"
   11) "key:411"
   12) "key:115"
   13) "key:116"
   14) "key:114"
   15) "key:119"
   16) "key:811"
   17) "key:511"
   18) "key:11"
复制代码


sscan指令


> sscan vip-info-set 0 count 10
0
[[1001]]
zscan指令
> zscan review:222  0 match * count 10
0
idea
1
idea2
2
idea3
3
idea4
4
复制代码


hscan指令


> hscan user-map  0 match *   count 10
0
1
user1
2
user2
3
user3
复制代码


SCAN的遍历顺序


关于scan命令的遍历顺序,我们可以具体看一下。


> keys *
user
test-key-1
test-key-3
test-key-4
test-key-2
> scan 0 match * count 1
2
test-key-3
> scan 2 match * count 1
6
user
> scan 6 match * count 1
1
test-key-4
> scan 1 match * count 1
7
test-key-1
> scan 7 match * count 1
0
test-key-2
复制代码


我们的Redis中有3个key,我们每次只遍历一个一维数组中的元素。如上所示,SCAN命令的遍历顺序是


0->2->6->1->7->0
复制代码


这个顺序看起来有些奇怪。我们把它转换成二进制就好理解一些了。我们发现每次这个序列是高位加1的。普通二进制的加法,是从右往左相加、进位。而这个序列是从左往右相加、进位的。


000->010->110->001->111->000
复制代码


那么为什么redis的作者想要这么设计呢?


这个地方涉及到了redis的rehash操作,假设我们原先的索引为:


000->100->010->110->001->101->011->111
复制代码


那么在进行了一轮扩容之后,索引会变为:


0000->1000->0100->1100->0010->1010->0110->1110->0001->1001->1101->1111
复制代码


原来挂接在xxx下的所有元素被分配到0xxx和1xxx下。当我们即将遍历010时,dict进行了rehash,这时,scan命令会从0100开始遍历,而000和100(原00下挂接的元素)不会再被重复遍历。


再来看看缩容的情况。假设dict从4位缩容到3位,当即将遍历1110时,dict发生了缩容,这时scan会遍历011。这时011下的元素会被重复遍历,但011之前的元素都不会被重复遍历了。所以,缩容时还是可能会有些重复元素出现的。因此在使用scan指令的时候,时而会出现重复的元素。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
27 3
|
2月前
|
NoSQL Redis 数据库
12)Redis 的游标迭代器(scan)
12)Redis 的游标迭代器(scan)
33 1
|
3月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Azure Reids是否可以开启慢日志(slowlog)和执行config指令
【Azure Redis 缓存】Azure Reids是否可以开启慢日志(slowlog)和执行config指令
|
3月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】Azure Redis 服务不支持指令CONFIG
【Azure Redis 缓存】Azure Redis 服务不支持指令CONFIG
|
5月前
|
存储 JSON NoSQL
Redis第五弹-HASH结构相关指令和介绍,计数功能Hash-哈希(Redis本来就是键值对结构,哈希,就相当于键值对嵌套了一个键值对)的多种指令Hset key field value-
Redis第五弹-HASH结构相关指令和介绍,计数功能Hash-哈希(Redis本来就是键值对结构,哈希,就相当于键值对嵌套了一个键值对)的多种指令Hset key field value-
|
6月前
|
NoSQL Redis 数据库
Redis中的常用命令非常丰富,涵盖了各种数据类型的基本操作以及服务器管理和维护的相关指令
【5月更文挑战第15天】Redis常用命令包括通用(如PING、SELECT)、键操作(KEYS、EXISTS、DEL)、字符串(SET、GET)、哈希(HSET、HGET)、列表(LPUSH、LPOP)、集合(SADD、SMEMBERS)和有序集合(ZADD、ZRANGE)等。这些命令用于数据操作及服务器管理,满足不同场景需求。了解更多命令,建议参考Redis官方文档。
59 2
|
5月前
|
消息中间件 NoSQL Redis
Redis第三弹,定时删除1.优先级队列(堆)2.基于时间轮实现的定时器​编辑Type指令(返回key对应的数据类型)redis的数据类型hset key field value
Redis第三弹,定时删除1.优先级队列(堆)2.基于时间轮实现的定时器​编辑Type指令(返回key对应的数据类型)redis的数据类型hset key field value
|
6月前
|
存储 NoSQL 前端开发
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—实战篇)
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—实战篇)
49 0
|
6月前
|
存储 NoSQL 算法
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—上篇)(二)
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—上篇)
64 0
|
6月前
|
存储 监控 NoSQL
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—上篇)(一)
【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群指令分析—上篇)
55 0