还在用 keys 命令模糊匹配删除数据吗? 这就是一颗随时爆炸的炸弹!
Redis 中没有批量删除特定前缀 key 的指令, 但我们往往需要根据前缀来删除, 那么究竟该怎么做呢? 可能你一通搜索后会得到下边的答案
Redis-cli --raw keys "ops-coffee-*" | xargs Redis-cli del
直接在 Linux 下通过 Redis 的 keys 命令匹配到所有的 key, 然后调用系统命令 xargs 来删除, 看似非常完美, 实则风险巨大
因为 Redis 的单线程服务模式, 命令 keys 会阻塞正常的业务请求, 如果你一次 keys 匹配的数量过多或者在 del 的时候遇到大 key, 都会直接导致业务的不可用, 甚至造成 Redis 宕机的风险
所以我们在生产环境中应当避免使用上边的方法, 那有什么优雅的方法来解决呢? SCAN!
SCAN 介绍及使用
Redis 从 2.8 版本开始支持 scan 命令, SCAN 命令的基本用法如下:
SCAN cursor [MATCH pattern] [COUNT count]
cursor: 游标, SCAN 命令是一个基于游标的迭代器, SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程, 直到服务器向用户返回值为 0 的游标时, 一次完整的遍历过程就结束了
MATCH: 匹配规则, 例如遍历以 ops-coffee - 开头的所有 key 可以写成 ops-coffee-*, 中间包含 - coffee - 的可以写成 *-coffee-*
COUNT: COUNT 选项的作用就是让用户告知迭代命令, 在每次迭代中应该从数据集里返回多少元素, COUNT 只是对增量式迭代命令的一种提示, 并不代表真正返回的数量, 例如你 COUNT 设置为 2 有可能会返回 3 个元素, 但返回的元素数据会与 COUNT 设置的正相关, COUNT 的默认值是 10
以下是一个 SCAN 命令的迭代过程示例:
- 127.0.0.1:6379> scan 0 MATCH ops-coffee-*
- 1) "38"
- 2) 1) "ops-coffee-25"
- 2) "ops-coffee-19"
- 3) "ops-coffee-29"
- 4) "ops-coffee-10"
- 5) "ops-coffee-23"
- 6) "ops-coffee-5"
- 7) "ops-coffee-14"
- 8) "ops-coffee-16"
- 9) "ops-coffee-11"
- 10) "ops-coffee-15"
- 11) "ops-coffee-7"
- 12) "ops-coffee-1"
- 127.0.0.1:6379> scan 38 MATCH ops-coffee-* COUNT 1000
- 1) "0"
- 2) 1) "ops-coffee-13"
- 2) "ops-coffee-9"
- 3) "ops-coffee-21"
- 4) "ops-coffee-6"
- 5) "ops-coffee-30"
- 6) "ops-coffee-20"
- 7) "ops-coffee-2"
- 8) "ops-coffee-12"
- 9) "ops-coffee-28"
- 10) "ops-coffee-3"
- 11) "ops-coffee-26"
- 12) "ops-coffee-4"
- 13) "ops-coffee-31"
- 14) "ops-coffee-8"
- 15) "ops-coffee-22"
- 16) "ops-coffee-27"
- 17) "ops-coffee-18"
- 18) "ops-coffee-24"
- 19) "ops-coffee-17"
SCAN 命令返回的是一个包含两个元素的数组, 第一个数组元素是用于进行下一次迭代的新游标, 而第二个数组元素则是一个数组, 这个数组中包含了所有被迭代的元素
上面这个例子的意思是扫描所有前缀为 ops-coffee - 的 key
第一次迭代使用 0 作为游标, 表示开始一次新的迭代, 同时使用了 MATCH 匹配前缀为 ops-coffee - 的 key, 返回了游标值 38 以及遍历到的数据
第二次迭代使用的是第一次迭代时返回的游标, 也即是命令回复第一个元素的值 38, 同时通过将 COUNT 选项的参数设置为 1000, 强制命令为本次迭代扫描更多元素
在第二次调用 SCAN 命令时, 命令返回了游标 0, 这表示迭代已经结束, 整个数据集已经被完整遍历过了
KEYS 命令的时间复杂度为 O(n), 而 SCAN 命令会将遍历操作分解成 m 次时间复杂度为 O(1) 的操作来执行, 从而解决使用 keys 命令遍历大量数据而导致服务器阻塞的情况, 使用下边的指令可以达到优雅删除的目的:
Redis-cli --scan --pattern "ops-coffee-*" | xargs -L 2000 Redis-cli del
其中 xargs -L 指令表示 xargs 一次读取的行数, 也就是每次删除的 key 数量, 一次读取太多 xargs 会报错
其他几种数据结构的优雅删除
类似的 SCAN 命令, 对于 Redis 不同的数据类型还有另外几个 SSCAN,HSCAN 和 ZSCAN, 使用方法类似:
- > sscan ops-coffee 0 MATCH v1*
- 1) "7"
- 2) 1) "v15"
- 2) "v13"
- 3) "v12"
- 4) "v10"
- 5) "v14"
- 6) "v1"
与 SCAN 命令不同的是这几个命令需要多加一个 key 的参数, 例如上边的 ops-coffee
对于一个大的 set key, 借助 sscan 使用下边的代码可以实现优雅的批量删除:
- import Redis
- def del_big_set_key(key_name):
- r = Redis.StrictRedis(host='localhost', port=6379)
- # count 表示每次删除的元素数量, 这里每次删除 300 元素
- for key in r.sscan_iter(name=key_name, count=300):
- r.srem(key_name, key)
- del_big_set_key('ops-coffee')
对于一个大的 hash key, 则可借助 hscan 使用下边的代码实现优雅的删除:
- import Redis
- def del_big_hash_key(key_name):
- r = Redis.StrictRedis(host='localhost', port=6379)
- # hscan_iter 获取出来的结果是个元祖, 下边 hdel 删除用 key[0] 取到 key
- for key in r.hscan_iter(name=key_name, count=300):
- r.hdel(key_name, key[0])
- del_big_hash_key('ops-coffee')
对于大的有序集合的删除就比较简单了, 直接根据 zremrangebyrank 排行范围删除
- import Redis
- def del_big_sort_key(key_name):
- r = Redis.StrictRedis(host='localhost', port=6379)
- while r.zcard(key_name)> 0:
- # 判断集合中是否有元素, 如有有则删除排行 0-99 的元素
- r.zremrangebyrank(key_name, 0, 99)
- del_big_sort_key('ops-coffee')
big list 大列表的删除可以参考上边这个方法, 通过 llen 判断数量, 然后 ltrim 移除范围内的元素, 这里不赘述
至此对于 Redis 的五中数据结构大 key 的优雅删除就全部实现了, 生产环境择优使用~
来源: https://www.cnblogs.com/37Y37/p/11037923.html