------------------------------------------------------------------------------------------------------慢慢来,一切都来得及
了解
问题:在平时线上 Redis 维护工作中,有时候需要从 Redis 实例成千上万的 key 中找出特定前缀的 key 列表来手动处理数据,可能是修改它的值,也可能是删除 key。这里就有一个问题,如何从海量的 key 中找出满足特定前缀的 key 列表来?
Redis 提供了一个简单暴力的指令 keys 用来列出所有满足特定正则字符串规则的 key。
这个指令使用非常简单,提供一个简单的正则字符串即可,但是有很明显的两个缺点。
1、没有 offset、limit 参数,一次性吐出所有满足条件的 key,万一实例中有几百 w 个key 满足条件,当你看到满屏的字符串刷的没有尽头时,你就知道难受了。
2
、
keys
算法是遍
历算法
,
复
杂度是
O(n)
,
如果
实例中有千万级以上的
key
,
这个指令就会导致
Redis
服
务卡顿
,
所有
读写
Redis
的其它的指令都会被延后甚至会超
时报错
,
因
为Redis 是
单线程程序
,
顺序执行所有指令
,
其它指令必
须等到当前的
keys
指令
执行完了才可以继续
。
Redis
为了解决这个问题,它在
2.8
版本中加入了大海捞针的指令——
scan
。
scan
相比
keys
具备有以下特点
:
1
、
复
杂度虽然也是
O(n)
,
但是它是通
过游标分步进行的
,
不会阻塞线程
;
2
、
提供 limit 参数
,
可以控制每次返回
结果的最大条数
,
limit
只是一个
hint
,
返回的结果可多可少;
3
、
同
keys
一
样
,
它也提供模式匹配功能
;
4
、
服
务器不需要为游标保存状态
,
游
标的唯一状态就是
scan
返回
给客户端的游标整数
;
5
、
返回的
结果可能会有重复
,
需要客户端去重复
,
这点非常重要
;
6
、
遍
历的过程中如果有数据修改
,
改
动后的数据能不能遍历到是不确定的
;
7
、
单次返回的结果是空的并不意味着遍历结束
,
而要看返回的游
标值是否为零
;
使用
scan
参数提供了三个参数,第一个是
cursor 整数值
,第二个是
key 的正则模式
,第三个是
遍历的 limit hint
。第一次遍历时
cursor
值为
0
,然后将返回结果中第一个整数值作为下一次遍历的 cursor
。一直遍历到返回的
cursor
值为
0
时结束。
scan 遍历顺序
scan
的遍历顺序非常特别。它不是从第一维数组的第
0
位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。
普通加法和高位进位加法的区别
高位进位法从左边加,进位往右边移动,同普通加法正好相反。但是最终它们都会遍历所有的槽位并且没有重复。