文章目录

  • 前言
  • scan 命令简介
  • 常用命令
  • Scan 命令原理解析
    • 扩容与缩容
    • scan 中的渐进式 rehash

前言

之前在项目里用 keys 命令找出 Redis 中所有满足特定正则字符串规则的 key。但是 keys 没有 offset 和 limit 参数,也就是分页。而且 keys 的算法是遍历算法,复杂度是 O(n) ,效率太低。后来为了优化这个功能,改用了 Redis 的 scan 命令。

scan 命令简介

scan 相比 keys 具备以下特点。

  1. 复杂度虽然也是 0(n),但它是通过游标分步进行的,不会阻塞线程。
  2. 提供 limit 参数,可以控制每次返回结果的最大条数,limit 只是个 hint,
    返回的结果可多可少。
  3. 同 keys 一样,它也提供模式匹配功能。
  4. 服务器不需要为游标保存状态 ,游标的唯一状态就是 scan 返回给客户端的游标整数。
  5. 返回的结果可能会有重复,需要客户端去重,这点非常重要。
  6. 遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的。
  7. 单次返回的结果是空的并不意昧着遍历结束,而要看返回的游标值是否为零。

常用命令

  • SCAN cursor [MATCH pattern] [COUNT count]

    • cursor - 游标。
    • pattern - 匹配的模式。
    • count - 指定从数据集里返回多少元素,默认值为 10 。
redis 127.0.0.1:6379> scan 0   # 使用 0 作为游标,开始新的迭代
1) "17"                        # 第一次迭代时返回的游标
2)  1) "key:12"2) "key:8"3) "key:4"4) "key:14"5) "key:16"6) "key:17"7) "key:15"8) "key:10"9) "key:3"10) "key:7"11) "key:1"
redis 127.0.0.1:6379> scan 17  # 使用的是第一次迭代时返回的游标 17 开始新的迭代
1) "0"
2) 1) "key:5"2) "key:18"3) "key:0"4) "key:2"5) "key:19"6) "key:13"7) "key:6"8) "key:9"9) "key:11"

Scan 命令原理解析

Redis 中所有的 key 都存储在一个很大的字典中,这个字典的结构和 Java 中的 HashMap 一样,是数组加链表的结构。数组的长度总是 2 的 n 次方,每次扩容,数组的容量 * 2。上面例子中 scan 指令返回的游标就是数组的索引,我们将这个位置索引称为槽(slot)。
Redis 每次每次扩容时,都会进行 rehash 操作,就是将所有的元素全部挂到新的数组下面。rehash 就是将元素的 hash 值对数组长度进行取模运算,因为长度变了,所以每个元素挂接的槽位可能也发生了变化。又因为数组的长度是 2 的 n 次方,所以取模运算等价于位与操作。

接下来我们看看 rehash 前后元素槽位的变化。

如图所示,假设此时字典的长度由 8 位扩容到 16 位,那么 3 号槽位将会被 rehash 到 3 号槽位和 3 + 8(8 是扩容时新增的容量) 即 11 号槽位。也就是说该槽位的链表中有大约一半的元素还在 3 号槽位,其他的元素放到了 11 号槽位。而 3 号槽位的二进制是 011,11 号槽位的二进制又是 1011,1011 正好是 011(3) + 1000(8) = 1011(11),即 011(3)的二进制增加了一个高位 1。
抽象一点就是假设当前 Redis 的容量是 n,某个槽位的二进制位是 xxx,那么该槽位中的元素将在扩容时时被 rehash 到 0xxx 和 1xxx(xxx + n)中。这里可能需要一点二进制运算的基础才比较好理解。

扩容与缩容


仔细观察上图,我们可以发现 Redis 是采用高位进位加法的遍历顺序,rehash 后的槽位在遍历顺序上是相邻的。

这里插播一下「高位进位加法」:

从图中我们可以看出高位进位加法从左边加,进位往右边移动,同普通加法正好相反。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容与缩容时避免槽位的遍历重复和遗漏。

回到扩容和缩容的话题,假设当前要遍历 110 这个位置(橙色),那么扩容后,当前槽位上所有的元素对应的新槽位是 0110 和 1110 (深绿色),也就是在槽位的二进制数增加 1 个高位 0 或 1。这时我们可以直接从 0110 这个槽位开始往后继续遍历, 0110 槽位之前的所有槽位都是已经遍历过的,这样就可以避免扩容后对已经遍历过的槽位进行重复遍历。
再考虑缩容,假设当前即将遍历 110 这个位置(橙色),那么缩容后,当前槽位所有的元素对应的新槽位是 10(深绿色),也就是去掉槽位二进制最高位。这时我们可以直接从 10 这个槽位继续往后遍历,10 槽位之前的所有槽位都是已经遍历过的,这样就可以避免缩容的重复遍历。不过缩容时,会对图中 010 这个槽位的元素进行重复遍历,因为缩容后 10 槽位的元素是 010 和 110 上的链表的元素的总和。

scan 中的渐进式 rehash

我们知道 redis 采用的是渐进式 rehash,对于 rehash 中的字典,scan 命令会同时扫描新旧槽位,然后就结果融合返回给客户端。

Redis 使用 scan 命令代替 keys相关推荐

  1. java redis优化_Redis性能优化:使用scan命令替换keys

    由于每个Redis实例是使用单线程处理所有请求的,故keys命令和其他命令都是在同一个队列排队等待执行的,如果keys命令执行时间长(数量多),则会阻塞其他命令的执行,导致性能问题. scan命令是在 ...

  2. redis的scan命令的源码分析,实现原理

    简言 1. 线上环境keys命令不可用,会导致redis卡死.scan命令因为可以分批遍历,比较实用 2. scan命令包括多个 遍历整个数据库的scan命令,处理函数 scanCommand(),最 ...

  3. 深入理解Redis的scan命令

    熟悉Redis的人都知道,它是单线程的.因此在使用一些时间复杂度为O(N)的命令时要非常谨慎.可能一不小心就会阻塞进程,导致Redis出现卡顿. 有时,我们需要针对符合条件的一部分命令进行操作,比如删 ...

  4. Redis Scan 命令

    文章目录 1. Scan 命令的基本用法 1.1 Scan 命令的输入值 1.2 Scan 命令的返回值 2. Scan 命令与 Keys 命令比较 2.1 Keys 命令的缺点 2.2 Scan 命 ...

  5. Redis Scan命令

    原地址:https://www.cnblogs.com/tekkaman/p/4887293.html [Redis Scan命令] SCAN cursor [MATCH pattern] [COUN ...

  6. Redis scan命令原理

    scan类型命令 SCAN cursor [MATCH pattern] [COUNT count]SSCAN KEY cursor [MATCH pattern] [COUNT count]HSCA ...

  7. Redis - 使用scan代替keys与hgetall操作

    1.scan前言 当我们使用 keys * 或 hgetall 进行查询的时候会进行堵塞,导致 redis 整体不可用(因为redis是单线程的),而使用 scan 命令则不会. 从Redis v2. ...

  8. redis 用scan 代替keys,hgetAll

    转载自:https://blog.csdn.net/w05980598/article/details/80264568 众所周知,当redis中key数量越大,keys 命令执行越慢,而且最重要的会 ...

  9. 采坑记录-Redis使用scan代替keys

    [提前声明] 文章由作者:张耀峰 结合自己生产中的使用经验整理,最终形成简单易懂的文章 写作不易,转载请注明,谢谢! spark代码案例地址: https://github.com/Mydreaman ...

  10. redis scan 命令底层原理(为什么会重复扫描?)

    文章目录 前言 一.迭代器 1. 全遍历 2. 间断遍历 二.scan 扫描原理 1. 扫描算法: 2. 减少重复扫描? 2.1 扩容 2.2 缩容 3. 迭代过程中正在进行rehash 4. 完整的 ...

最新文章

  1. http://weibo.com/cnblogs
  2. LED调光,PFM即pulse frequence modulation
  3. 程序员面试金典--第k个数
  4. POJ - 2400 Supervisor, Supervisee(KM+打印方案)
  5. python copy 文件,图片等..
  6. SAP ERP里如何创建一个新的material类型
  7. Github Actions 中 Service Container 的使用
  8. Ubuntu下tftp服务器的搭建
  9. linux下 最常用基本命令
  10. 今晚直播丨分布式数据库:从PG-XL到TBASE
  11. PHP学习总结(9)——PHP入门篇之WAMPServer服务控制面板介绍
  12. 微服务乱码_本地正常服务器乱码
  13. servlet和jsp的转发与重定向代码以及区别
  14. [C#][Quartz]帮助类
  15. 自动化测试平台(七):头像展示、下拉菜单及用户管理模块增删改功能实现
  16. 01:电机控制的基本原理
  17. 石家庄地铁售票系统源代码
  18. 新浪短网址生成java_新浪短网址(T.cn)/腾讯短链接(Url.cn)在线生成以及API接口申请的教程...
  19. 2018秋招面经有感
  20. #4.2混沌数学与混沌理论

热门文章

  1. python最新抢票脚本
  2. 会唱歌的程序员为何如此受欢迎?
  3. coffeescript java 执行_javascript – CoffeeScript中的方法调用语法
  4. H5页面调用微信授权获取code
  5. The COMMIT TRANSACTION request has no corresponding BEGIN
  6. CSS3-设置基本边框和背景
  7. OpenGl 之学习笔记 glNormal3f 函数理解和光源相关知识总结
  8. 【Mysql 第11章_数据处理之增删改】
  9. 正在与拖延症病魔抗争中
  10. 程序员怎样更优雅的接私活赚外快