Redis 实用小技巧——记一次 Redis 「大扫除」行动
永不过期的键
最近一直在做服务器成本优化的工作。在整理 Redis 使用情况的时候,发现存在的问题还真不少。其中一个比较头疼的问题,就是某个实例未设置过期时间的键,居然有八十万之多——这是多么恐怖的数字!
所有键加起来才一百万左右,未设置过期时间的键占比超过了 80%。
正常来讲,我们业务使用的 Redis 不会有这么多键,而且大部分都未设置过期时间,这里面肯定也是有问题的。
所以,第一步,就是需要把「问题」键找出来。
怎么找呢?
在不了解所有键分布的情况下,我一般先从RANDOMKEY
这个命令入手。这个命令可以随机返回库里的一个键。
如果某种类型的键分布比较集中的话,通过RANDOMKEY
命令,会比较大几率命中这种类型的键。
经过一番操作以后,我发现比较频繁出现的是这种键:
128e29fa66cabb33b5938c2de3cf0656
当看到这种键的时候,我第一反应就是有人用md5
值作为键。通过对比,发现这种键具备md5
值的特点:
- 32 位固定长度。
- 由数字 0-9 和小写字母 a-f 组成。
虽然不清楚这种键具体有多少,但通过简单的概率理论推测,这种键占比大概在 60% 左右。
在和业务同事沟通过以后,确认这种键只是做缓存使用,缓存时间正常应该设置为一天,因为大意导致没有设置过期时间。键生成规则正是几个参数经过md5
运算得到的值,和我猜想的一样。
这里不得不又提到一个老生常谈的问题——键名设计规范的问题。
这种通过md5
值直接命名键名的方式,存在以下明显的缺点:
- 不具备可读性。看到这种键,完全不知道干嘛用的。
- 存在
md5
撞库的可能性。md5
只是生成一个固定的哈希值,哈希值本身具有随机性,但不具备唯一性。 - 维护性差。如果不了解具体业务的,碰上这种键,根本不敢动。
既然已经找到了「罪魁祸首」,下一步问题就是如何把这些键全部找出来,然后设置一个过期时间了。
把问题键揪出来
现在,我们要做的,就是如何在百万个键中,把以md5
值这种命名规则的键给找出来。
思路一
假设我们的键符合以下规则:
USERINFO:[1-10000之间的整数]
如果想找出符合条件的所有键来,可以通过遍历整数部分的后缀,然后拼接成一个完整的键名,再通过EXISTS
命令查看是否存在。
理论上讲,通过KEYS USERINFO:*
命令,也可以将所有的键找出来。
温馨提醒: 这里强调了,只是理论上,实际千万别这么干。因为线上
KEYS
命令就是个潘多拉魔盒。哪怕某次KEYS
命令很快返回了你想要的结果,也不要心存侥幸,因为说不定哪次就把你抬坑里去了。
这种具有明显规律性的键名,都可以采用限定区间,然后遍历的方式来获取目标键。
但是这种方式对于md5
值生成的键还适用么?
md5
长度为 32 位,每一位都有 0-9 和 a-f 共 16 种可能。所有md5
值的范围为0000000000000000000000000000000
到fffffffffffffffffffffffffffffff
,共 2 ^ 128 个值。
呃,PASS……
思路二
既然通过事先生成的样本到目标库判断的思路行不通,那只能反其道而行之了。
也就是从目标库取出样本,然后判断是否符合md5
值命名的规则。
但是这种方式实现起来也有难度,数据库里键的数量过大,这里就涉及到一个从海量的 Redis 键中,取出符合条件的键的问题了。
KEYS
是个危险命令,会阻塞线程,这里自然就不考虑了。
除了KEYS
命令,我们还可以使用SCAN
命令进行遍历。SCAN
命令使用方法如下:
SCAN cursor [MATCH pattern] [COUNT count]
我们可以借助MATCH
参数进行模糊匹配。不过SCAN
命令的匹配模式,仅支持glob
风格的正则模式,即通配符相关的简单正则模式。所以这里如果想匹配md5
格式的话,就有些难度了。
另外,即使支持,我们也建议尽量不要直接在命令中使用复杂的匹配模式,这会增加 Redis 命令的处理时间。
我们可以借助脚本语言,使用SCAN
命令进行遍历,在程序中进行正则判断,获取到目标键。
这种方式实现起来没有什么难度,不过需要注意控制每次返回元素的数量,数量不能太多,以免引起阻塞。
除了需要遍历全部的数据外,SCAN
也算是比较稳妥的处理方式了,毕竟不会阻塞线程,只是调用命令的次数会多一些。
还有其他处理方式吗?因为实在不想写代码。
有,思路三。
思路三
这也是我经常使用的方式了。之前在 《Redis 实用小技巧——一文教你如何选择合适的 Key 类型》 一文中做过介绍,就是借rdb
备份文件和rdbtools
工具进行操作。基础用法大家可以参考下之前的文章,这里就不做介绍了。这里仅说一下实现的步骤。
其实这种实现思路也比较简单,主要处理好以下几个关键步骤就可以了。
1. 导出所有未设置过期时间的键
使用rdb
命令从rdb
文件导出所有未设置过期时间的键。
rdb -c justkeyvals -x -f target.csv ./origin.rdb
-c justkeyvals
:导出键和值的格式-x
:通过协议命令排除过期的键-f target.csv
:结果输出到目标文件/origin.rdb
:原始的rdb
文件
通过这一步,将所有的未设置过期时间的键导入到目标文件中。
2. awk 命令匹配目标键
这里之所以把值也导了出来,是为了通过值的格式进一步限制范围,避免其他业务也有使用了md5
值的命名规则,也被一并处理。
接下来,我们需要把符合条件的键筛选出来。使用awk
命令实现:
cat target.csv | awk -F ' ' '{if($1 ~ /^[0-9a-fA-F]{32}$/ && $2 ~ /keyword/){print $1}}'
-F ' '
:指定空格为awk
分隔符$1 ~ /^[0-9a-fA-F]{32}$/
:键名符合md5
值格式$2 ~ /keyword/
:键值包含keyword
关键字
通过这一步,可以将所有键名为md5
值格式的键筛选出来。
3. awk 命令生成处理过期命令
通过wc
命令统计,发现筛选出来的键有六十万左右。
这么多的键,肯定不能一个个设置过期。通过程序脚本设置过期倒是可行,但是已经到这一步了,还是不想写代码怎么办?
那就直接生成一个 Redis 命令的文件,通过redis-cli
客户端直接以外部文件的形式批量运行命令。
我们可以先生成一个 Redis 命令文件。
生成方式当然还是使用强大的awk
命令了。直接上命令:
cat target.csv | awk -F ' ' '{expireAt=86400*6+int(86400 * rand());if($1 ~ /^[0-9a-fA-F]{32}$/ && $2 ~ /keyword/){print "EXPIRE " $1 " " expireAt}}' > redis-script.txt
这里就是在输出的时候,拼接了一个EXPIRE
字符串和一个expireAt
变量。expireAt
定义在命令的前边,表示过期时间。
这里过期时间定义为 6 天的秒数加上 0 - 86400 之间的一个随机数,是为了避免六十多万的键集中过期,造成「缓存雪崩」的情况。
4. 预估脚本执行时间
在正式执行脚本之前,保险起见,我们还需要做一步评估,预估一下整体的执行时间。
我们可以先确定执行一条命令所用的时间,然后再乘以基数,进而得到整体的耗时。
这里,我们使用事务的方法,在命令执行前后各执行一次TIME
命令,进而得到执行一次命令的时间。
128.0.0.1:6379> MULTI
OK
128.0.0.1:6379> TIME
QUEUED
128.0.0.1:6379> EXPIRE 06c97b4b15ac2b94068bc2576aeb9ad5 568990
QUEUED
128.0.0.1:6379> TIME
QUEUED
128.0.0.1:6379> EXEC
执行结果如下:
1) 1) "1718777686"
2) "424625"
2) (integer) 1
3) 1) "1718777686"
2) "424639"
经过计算得出,执行一次命令的时间在 14 微秒左右。则六十万数据执行完一遍命令,所需的时间大概为:
600000 * 14 = 8400000 微秒 = 8.4 秒
因为是批量执行的单条命令,所有时间基本在可控范围内。
5. 外部执行 Redis 命令
接下来,就是通过redis-cli
的方式,从外部脚本文件中批量执行命令了。执行方式如下:
cat redis-script.txt | redis-cli -h 127.0.0.1 -a *** --pipe
--pipe
:启用 pipe 协议,它不仅仅能减少返回结果的输出,还能更快地执行指令。
[ 开始运行—— ]
[ DONE! ]
实际上,只用了五秒不到的时间,命令就执行完了,对业务并没有造成什么影响。
6. 验证结果
对设置完过期的键进行抽样检测。
我们分别从头部,中部和尾部取部分样本,使用TTL
命令查看过期时间,发现都已经设置了过期时间,说明脚本执行的没有问题。
总结
如果使用了云 Redis 的话,基本都会进行定期备份。有了备份的rdb
文件,我们就可以做很多事情了。
像 key 活动监控、慢日志、bigkey 这些基础功能,现在很多云平台都已经支持。
不过,像本文介绍的这种针对具体键进行的操作,rdb
文件可以发挥的空间还是很大的。
有时候,偷懒,反而是一种捷径。
本作品采用《CC 协议》,转载必须注明作者和本文链接
好好好,不过怎么样都可能处理到其他业务的md5值吧
:+1:
皮拉夫的文章我是篇篇都收藏 :+1: