深入Redis(九)Scan

Scan

在Redis线上维护中,有时候需要从Redis实例成千上万的key中找出特定前缀的key列表来手动处理数据,有可能是修改值,也有可能是删除key,那么如何从海量key中找出满足特定前缀的key列表来?

Redis提供了一个暴力指令keys来列出所有满足正则规则的key。

但这个指令有两个明显缺陷。

  1. 没有offset、limit参数,一次性吐出所有满足条件的key,万一实例中有几百万个key满足条件,满屏字符串会不断刷新,很难受;
  2. keys算法是O(n)的,如果有千万级key,该指令会导致Redis服务卡顿,所有读写Redis的指令都会被延后甚至超时报错。

Redis为了解决这两个问题,在2.8版本中加入scan指令,相比keys具备以下特点:

  1. 复杂度也是O(n),但其是通过游标分步进行的,不会阻塞线程;
  2. 提供limit参数,可以控制每次返回结果的最大条数;
  3. 同keys一样,提供正则匹配;
  4. 服务器不需要为游标保存状态,游标的唯一状态就是scan返回给客户端的游标整数;
  5. 返回的结果可能会有重复,需要客户端去重,非常重要;
  6. 遍历的过程中,如果有数据修改,改动后的数据能不能遍历到是不确定的;
  7. 单词返回的结果是空不代表遍历结束,要看返回的游标值是否是0。

基础使用

scan 0 match key99* count 1000,三个参数,cursor整数值(这里是0),正则模式(match key99*),遍历的limit hint(count 1000)。

第一次遍历是cursor是0,然后返回结果中的第一个整数值作为下一次遍历的cursor,直到遍历返回的cursor值为0时结束。

虽然提供的limit是1000,但返回的结果却没几个,这是因为指定的1000代表hashmap中的数组索引,遍历到索引上有链表时会匹配所有数据然后返回,但更多的是很多索引上没有数据,因而会出现返回空数据,但只要cursor不为0就表示遍历没有结束。

scan遍历顺序

不是从0到末尾,而是高位进位加法遍历,使用这种特殊方法是考虑到字典扩容和缩容时避免槽位遍历重复和遗漏。

字典扩容

Java的HashMap扩容的直接扩容,一次性将旧数组下的元素全部转移到新数组中。这个过程叫rehash。

渐进式rehash

如果HashMap中的元素特别多,线程会出现卡顿现象,Redis为解决这个问题,采用渐进式rehash。

同时保留新旧数组,在定时任务中以及后续hash指令操作中渐渐将旧数组中的元素迁移到新数组上,其同时扫描新旧数组,将结果融合后返回。

更多的scan命令

除了遍历所有的key,还可对指定容器集合进行遍历,zscan遍历zset集合元素,hscan遍历hash字典的元素,sscan遍历set集合的元素。

大key扫描

在集群环境中,如果某个key太大会导致迁移卡顿,在它需要扩容时会申请更大的内存,也会导致卡顿,如果删除,内存也会一次性回收,还是会导致卡顿。

如果观察到Redis内存大起大落,则极有可能是因为大key导致的。

如何定位大key?

使用scan指令对每一个key使用type指令获得key的类型,然后应用相应数据结构的size或len方法得到其大小,保留前N名作为结果展示出来。

上述过程需要用脚本实现,比较繁琐。Redis官方在redis-cli中提供了这样的扫描功能。

redis-cli -h 127.0.0.1 -p 6379 --bigkeys

如果担心该命令大幅抬升Redis的ops导致线上报警,增加一个休眠参数。

redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.1

每隔100条指令就会休眠0.1秒,ops就不会剧烈抬升,但扫描时间会变长。

猜你喜欢

转载自www.cnblogs.com/ikct2017/p/9499455.html