在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
《清理 redis 死键》参考: 场景: 在这类项目运行久了之后,一些老的key会不断在redis里积压,导致redis内存越来越高,对redis的使用效率产生影响,因此需要对于redis数据进行定期清理。 1、死key(死键)所谓死键,在redis里有两个定义: 2、轮转时间轮转时间即idletime,是指该key有多长时间没有被访问过(单位 s)。 3、相关命令其他命令 SSCAN 命令用于迭代集合键中的元素。 # SCAN 命令是一个基于游标的迭代器(cursor based iterator):SCAN 命令每次被调用之后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数,以此来延续之前的迭代过程。 3.1、scan语法redis Scan 命令基本语法如下: SCAN cursor [MATCH pattern] [COUNT count]
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。
示例说明: 这里使用scan 0 match key1111* count 20命令来完成这个查询,稍显意外的是,使用一开始都没有查询到结果,这个要从scan命令的原理来看。 第一行:scan在遍历key的时候,0就代表第一次,key1111*代表按照key1111开头的模式匹配,count 20中的20并不是代表输出符合条件的key,而是限定服务器单次遍历的字典槽位数量(约等于)。 那么,什么又叫做槽的数据?这个槽是不是Redis集群中的slot?答案是否定的。其实上图已经给出了答案了。
scan 指令是一系列指令,除了可以遍历所有的 key 之外,还可以对指定的容器集合进行遍历。 3.2、zscan 遍历 zset 集合元素语法redis Zscan 命令基本语法如下: redis 127.0.0.1:6379> ZSCAN key cursor [MATCH pattern] [COUNT count]
可用版本:>= 2.8.0 返回值:返回的每个元素都是一个有序集合元素,一个有序集合元素由一个成员(member)和一个分值(score)组成。 示例: > ZADD site 1 "Google" 2 "Runoob" 3 "Taobao" 4 "Weibo" (integer) 4 > ZSCAN site 0 match "R*" 1) "0" 2) 1) "Runoob" 2) 2.0
3.3、hscan 遍历 hash 字典的元素语法redis Sscan 命令基本语法如下: SSCAN key cursor [MATCH pattern] [COUNT count]
可用版本:>= 2.8.0 返回值:返回的每个元素都是一个元组,每一个元组元素由一个字段(field) 和值(value)组成。 示例: > HMSET sites google "google.com" runoob "runoob.com" weibo "weibo.com" 4 "taobao.com" OK > HSCAN sites 0 match "run*" 1) "0" 2) 1) "runoob" 2) "runoob.com" 3.4、sscan 遍历 set 集合的元素语法redis Sscan 命令基本语法如下: SSCAN key cursor [MATCH pattern] [COUNT count]
可用版本:>= 2.8.0 返回值:数组列表。 示例: > SADD myset1 "Google" (integer) 1 > SADD myset1 "Runoob" (integer) 1 > SADD myset1 "Taobao" (integer) 1 > SSCAN myset1 0 match R* 1) "0" 2) 1) "Runoob" SSCAN 命令、 HSCAN 命令和 ZSCAN 命令的第一个参数总是一个数据库键(某个指定的key)。 另外,使用redis desktop manager的时候,当刷新某个库的时候,控制台自动不断刷新scan命令,也就知道它在干嘛了。 4、扫描未设置ttl的key4.1、shell脚本方式好在redis-cli提供了scan的参数,来执行scan逻辑。redis-cli的用法见:
# vim redis_no_ttl_key.sh #!/bin/bash # Redis 通过 scan 找出不过期的 key # SCAN 命令是一个基于游标的迭代器(cursor based iterator):SCAN 命令每次被调用之后,都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数,以此来延续之前的迭代过程。 # 注意:当 SCAN 命令的游标参数被设置为 0 时,服务器将开始一次新的迭代,而当服务器向用户返回值为 0 的游标时,表示迭代已结束! db_ip=10.100.41.148 # redis 连接IP db_port=6379 # redis 端口 password='IootCdgN05srE' # redis 密码 cursor=0 # 第一次游标 cnt=100 # 每次迭代的数量 new_cursor=0 # 下一次游标 redis-cli -c -h $db_ip -p $db_port -a $password scan $cursor count $cnt > scan_tmp_result new_cursor=`sed -n '1p' scan_tmp_result` # 获取下一次游标 sed -n '2,$p' scan_tmp_result > scan_result # 获取 key cat scan_result |while read line # 循环遍历所有 key do ttl_result=`redis-cli -c -h $db_ip -p $db_port -a $password ttl $line` # 获取key过期时间 if [[ $ttl_result == -1 ]];then #if [ $ttl_result -eq -1 ];then # 判断过期时间,-1 是不过期 echo $line >> no_ttl.log # 追加到指定日志 fi done while [ $cursor -ne $new_cursor ] # 若游标不为0,则证明没有迭代完所有的key,继续执行,直至游标为0 do redis-cli -c -h $db_ip -p $db_port -a $password scan $new_cursor count $cnt > scan_tmp_result new_cursor=`sed -n '1p' scan_tmp_result` sed -n '2,$p' scan_tmp_result > scan_result cat scan_result |while read line do ttl_result=`redis-cli -c -h $db_ip -p $db_port -a $password ttl $line` if [[ $ttl_result == -1 ]];then #if [ $ttl_result -eq -1 ];then echo $line >> no_ttl.log fi done done rm -rf scan_tmp_result rm -rf scan_result 只能扫描redis的0号库。 网上看到lua实现的: ---------------------------------------------------------------------------------------------------- 踩坑1:所以我写的第一版的lua脚本如下: local c = 0 local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000) c = tonumber(resp[1]) local dataList = resp[2] for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then redis.call('DEL',d) end end if c==0 then return 'all finished' else return 'end' end 在本地的测试redis环境中,通过执行以下命令mock了1k的测试数据: 在redis的客户端或console输入如下命令执行: 测试环境-不要误删:0>eval "for i = 1, 1000 do redis.call('SET','authToken_' .. i,i) end" 0 null 测试环境-不要误删:0>
成功插入1000个ttl为-1的key-value
然后执行script load命令上传lua脚本得到SHA值,然后执行evalsha去执行得到的SHA值来运行。具体过程如下: 测试环境-不要误删:0>script load "local c = 0 local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000) c = tonumber(resp[1]) local dataList = resp[2] for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then print(d) redis.call('DEL',d) end end if c==0 then return 'all finished' else return 'end' end" "21df2cc32036b90018d5af738759830144ffa28c" 测试环境-不要误删:0> evalsha执行: 测试环境-不要误删:0>evalsha 21df2cc32036b90018d5af738759830144ffa28c 0 "all finished" 测试环境-不要误删:0>
结果: 我每删1w数据,执行下dbsize(因为这是我本地的redis,里面只有mock的数据,dbsize也就等同于这个前缀key的数量了)。 发现游标虽然没有到达末尾,但是key的列表却是空的。 经过详细研读,发现count选项所指定的返回数量还不是一定的,虽然知道可能是count的问题,但无奈文档的解释实在难以很通俗的理解,依旧不知道具体问题在哪,后来经过某个小伙伴的提示,看到了另外一篇对于scan命令count选项通俗的解释: 看完之后恍然大悟。原来count选项后面跟的数字并不是意味着每次返回的元素数量,而是scan命令每次遍历字典槽的数量。我scan执行的时候每一次都是从游标0的位置开始遍历,而并不是每一个字典槽里都存放着我所需要筛选的数据,这就造成了我最后的一个现象:虽然我count后面跟的是10000,但是实际redis从开头往下遍历了10000个字典槽后,发现没有数据槽存放着我所需要的数据。所以我最后的dbsize数量永远停留在了124204个。 可用版本:至此,心中的疑惑就此解开,改了一版lua: local c = tonumber(ARGV[1]) local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000) c = tonumber(resp[1]) local dataList = resp[2] for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then redis.call('DEL',d) end end return c 在本地上传: 测试环境-不要误删:0>script load "local c = tonumber(ARGV[1]) local resp = redis.call('SCAN',c,'MATCH','authToken*','COUNT',10000) c = tonumber(resp[1]) local dataList = resp[2] for i=1,#dataList do local d = dataList[i] local ttl = redis.call('TTL',d) if ttl == -1 then redis.call('DEL',d) end end return c" "a9e4f8116fec50c81e0716e4fdb2f0bbdd327f5c" 测试环境-不要误删:0>
执行: 测试环境-不要误删:0>eval "for i = 1, 200000 do redis.call('SET','authToken_' .. i,i) end" 0 null 测试环境-不要误删:0>dbsize "200012" 测试环境-不要误删:0>evalsha a9e4f8116fec50c81e0716e4fdb2f0bbdd327f5c 1 0 0 "77104" 测试环境-不要误删:0>dbsize "190012" 测试环境-不要误删:0>evalsha a9e4f8116fec50c81e0716e4fdb2f0bbdd327f5c 1 0 77104 "185490" 测试环境-不要误删:0>
77104的参数是上次evalsha返回的值。 可以看到,scan命令没法完全保证每次筛选的数量完全等同于给定的count,但是整个迭代却很好的延续下去了。最后也得到了游标返回0,也就是到了末尾。至此,测试数据20w被全部删完。 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1、需要清理的keys。 未设置ttl的keys,且半年内无访问。 2、redis 删除过期建策略。 Redis删除过期键有两种策略:passive way和active way
3、获取keys的idletime 127.0.0.1:6379> set aa bb OK 127.0.0.1:6379> object idletime aa (integer) 12 127.0.0.1:6379> get aa # 访问后,idletime会清零 "bb" 127.0.0.1:6379> object idletime aa (integer) 2
4、清理过期的idletime脚本 #!/bin/bash file_dir="/data/tmp" redis-cli -h jkb-hw-prod-apple1 -p 6391 --scan >$file_dir/$port.keys ruleTime='12960000' while read line do storageTime=$(redis-cli -h jkb-hw-prod-apple1 -p 6391 OBJECT IDLETIME ${line}|awk '{print $1}') if [ ! "${storageTime}x" == "x" ];then if [ ${storageTime} -gt ${ruleTime} ];then redis-cli -h jkb-hw-prod-apple1 -p 6391 del ${delKey} fi fi echo "${rowProcessed}" >/data/tmp/record-number.txt done <$file_dir/$port.keys github地址这个项目是对于redis中的key进行筛选,查找到轮转时间(长期没有使用的时间)大于某个阈值的key,并将它做一些清理落地处理。
参考: https://www.cnblogs.com/sunshine-long/p/12582681.html |
请发表评论