使用redis的scan指令详解

本文涉及的产品
云数据库 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
目录
相关文章
|
6月前
|
缓存 NoSQL 算法
Redis之MoreKey问题及Scan命令解读
Redis之MoreKey问题及Scan命令解读
|
9月前
|
存储 SpringCloudAlibaba NoSQL
Redis(基础篇): 数据结构和常用指令
Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率
149 0
Redis(基础篇): 数据结构和常用指令
|
10月前
|
存储 运维 NoSQL
redis集群详细搭建方式(含原理、操作指令、异常处理)
1.概述 概念: redis集群是从3.0版本开始支持的一个功能,是redis的一种水平扩展方式,将全局数据分散的存储在N个结点上,从而来将请求流量打散到各个结点上,减轻单结点压力。 实现原理: redis的集群进行数据散列时使用了一致性hash算法的思想,如果对一致性hash算法有兴趣可以参照博主的另一篇文章:
761 0
|
10月前
|
存储 NoSQL Unix
《微服务实战》 第二十章 Redis连接指令 客户端指令 服务器指令
《微服务实战》 第二十章 Redis连接指令 客户端指令 服务器指令
70 0
|
10月前
|
机器学习/深度学习 存储 NoSQL
【Redis基础知识 十二】Redis通用指令
【Redis基础知识 十二】Redis通用指令
72 0
|
11月前
|
存储 NoSQL 算法
Redis进阶-如何从海量的 key 中找出特定的key列表 & Scan详解
Redis进阶-如何从海量的 key 中找出特定的key列表 & Scan详解
361 0
|
11月前
|
存储 消息中间件 缓存
[最详细] redis 常见指令 与 实际使用场景
redis的7中常见数据结构String、List、Set、Zset、Hash,HyperLogLogs(基数统计),Bitmap (位存储)详解,常见指令的使用,以及相关原理与实战场景的使用,感兴趣的大佬直接戳进来就对了
38 0
|
NoSQL Java 程序员
Redis scan 命令的一次坑
Redis scan 命令的一次坑
275 0
|
NoSQL Redis 数据安全/隐私保护
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
252 0
【Redis 技术探索】「数据迁移实战」手把手教你如何实现在线 + 离线模式进行迁移 Redis 数据实战指南(scan模式迁移)
|
存储 NoSQL 算法
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理
136 0
【Redis核心原理专题】(1)「技术提升系列」分析探究如何实现LFU的热点key发现机制以及内部的Scan扫描技术的原理

热门文章

最新文章