对于 Redis 服务器的维护,有时我们需要从成千上万的key中,找出我们指定的key,也就是模糊匹配出来的key,redis提供了一个简单粗暴的命令:keys,它可以用来列出所有满足特定正则字符串规则的 key。
但是对于这个简单粗暴的命令,要是不想被同事吊,生产环境就忘记有这个命令的存在,或者是这个命令在生产环境已经被老大给和谐了,原因想必大家也知道,这个指令没有offset、limit 参数,一次性吐出所有满足条件的key,万一实例中有上百万个key满足条件,你就傻眼了,并且其他同事开发的模块也在调用同一个Redis服务器的时候,他们想杀你的心都有了,keys算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,所有读写Redis的其它的指令都会被延后甚至会超时报错,因为 Redis是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续。这里插个闲话,Redis也逃脱不了真香定理,要向多线程方向发展,Redis 6.0引入的最重大的改变就是多线程IO,对性能提升至少是一倍以上,等大家升级到6.0版本以上之后,也许keys命令的弊病就没有了,不过还没验证。
先不说多线程版本的Redis,先来说一下单线程版本的Redis是怎么解决这个遍历key的问题,redis提供了另外一个命令,就是scan:
scan命令的特点如下:
1、复杂度和keys命令一样,也是 O(n),但是它是通过游标分步进行的,不会阻塞线程
2、提供limit参数,可以控制每次返回结果的最大条数,这里是最大条数,而不是等于limit的条数,因为是匹配查询,是在limit的范围内匹配查询
3、返回的结果可能会有重复
4、遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的;
5、单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零;
scan的命令格式为:
SCAN cursor [MATCH pattern] [COUNT count]
其中cursor是游标位置,是整数值;pattern是遍历key的正则表达式;count是遍历元素的最大条数。
先写一个程序向redis服务器写入一万个key:
然后用scan遍历符合bingmayong88的元素:
大家都知道Java中 HashMap 的底层实现结构,是数组+链表的形式,在JDK1.7之后还加入了 红黑树 ,在Redis当中,所有的key都存储在一个很大的字典中,这个字典结构就是一维数组+二维链表的结构,scan指令返回的游标就是第一维数组的位置 索引 ,这个位置索引称为槽 (slot)。 如果不考虑字典的扩容缩容,直接按数组下标挨个遍历就行了。limit 参数就表示需要遍历的槽位数,之所以返回的结果可能多可能少,是因为不是所有的槽位上都会挂接链表,有些槽 位可能是空的,还有些槽位上挂接的链表上的元素可能会有多个。每一次遍历都会将 limit 数量的槽位上挂接的所有链表元素进行模式匹配过滤后,一次性返回给客户端。
从图中可以看出,scan 的遍历顺序非常特别。它不是从第一维数组的第 0 位一直遍历到末尾,而是采用 了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容 时避免槽位的遍历重复和遗漏。关于高位进位加法,大家有兴趣的可以查阅资料了解一下。
2001; 170 539 554 We wonder whether these observations in MГјller glia may be related to a lower expression level of GFAP compared to retinal astrocytes and or a possibility of non canonical NF ОєB signaling in these glia
Confusion surrounding LPD is the result of inconsistent and unreliable diagnostic criteria
PMID 32063842 Free PMC article
Estimating causal effects from large data sets using propensity scores