Redis清理

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://guoruibiao.blog.csdn.net/article/details/86237628

Redis是个好东西,但也禁不住乱用,毫无节制,毫无规范地使用,其结果就是后期清理的时候异常艰难。

近期有清理的需求,就搜集整理了下,作为笔记备用。


RDB

线上Redis实例一般都会很大,而且通常服务器宿主环境的内存都不会充裕,为了避免服务器宕机或者出现服务阻塞,服务卡顿的情况,就不要在master服务器或者线上服务器上操作了。

https://jmaitrehenry.ca/2017/11/22/found-keys-without-expiration-in-redis/

这篇文章介绍了一个很不错的探查方法,里面大致讲了两个维度:

  • 未过期KEY: 堆积久了,服务器内存会吃紧
  • 大值KEY: 累积多了,服务延迟,卡顿

未过期KEY

大致的意思是拿到keys 然后对每一个key进行ttl 计算出对应的过期时间。在

http://doc.redisfans.com/key/ttl.html

命令参考中有关于TTL命令的详细解释:

Redis 2.8及以后,TTL对于key有三种返回结果:
X(>=0 大于零的整数)表示还有X秒后expire
-1 表示该key无过期时间
-2 表示此key已过期,并且被Redis删除

通过对两个文件整合,过滤出keys中过期时间为-1的那部分key,以此来发现业务代码中是否有忘记了加expire时间的错误, 核心思路如下:

  1. 拿到keys
    链接中使用的是docker,起一个Redis示例,将volume挂载过去,通过命令就可以搞到了。这样的好处就是及时Redis挂了也无伤大雅,反正是docker,大不了再起一个。 但是这不是唯一的方法,如果电脑内存充足,拿到了dump.rdb之后就可以随心所欲地用,只要最后拿到keys就可以。由于不需要特别精确,所以我这里用的是scan cursor count 方式。
  2. 拿到keys对应的ttl
#!/usr/bin/bash
cat keys | xargs -n 1 -L 1 redis-cli ttl > ttl

上面这个方法,简单粗暴,但是效率低,不太好。理想状态时使用PIPELINE 但是一次PIPELINE也不应该太多,超过了处理上限也不太好,所以尽可能找一个均衡量来处理。

  1. 过滤出没有过期时间的key集合
#!/usr/bin/bash
paste -d " " keys ttl | grep .*-1$ | cut -d " " -f 1 > without_ttl

这样就可以找到Redis中没有过期时间的key了,通过对文本进行分析,对前缀字符串进行模式匹配等等,相信很快就能找到问题所在。

大值KEY

一般来说,KEY的分布和大小都与业务息息相关,通常这种类型的KEY不会很多,但是极端情况下,只要是一出现,就会给服务带来不稳定因素。每当对这些key进行HGETALL,LRANGE,SMEMBERS,ZRANGE的时候,都要阻塞服务器,卡顿感明显。

Redis自带了一个查看大值KEY的命令。

redis-cli -h host -p port --bigkeys 

它会自动帮助我们分析当前各个类型下大KEY的具体信息。


非RDB

由于我没有内存充足的本地服务器,所以就不能对心所欲地按照第一个方式来弄了。

scan cursor count

是个不错的方式,scan拿到keys集合,再慢慢地对keys获取TTL,虽然效率不高,但是一点一点脚踏实地的去分析,效果还不赖。

制造数据源

#coding: utf8
__author__ = "郭 璞"
__email__ = "marksinoberg@gmail.com"
# padding rand keys to redis
import redis
import random
client = redis.Redis("localhost", 6379)

# 跑两遍,第一遍为True,存KEY用,第二遍额外添加EXPIRE,模拟线上生产环境
WITHOUT_TTL = False


# padding strings
print("padding string keys")
for i in range(1, 1000000):
    if WITHOUT_TTL == True:
        client.set("string:{}".format(i), "value: {}".format(i))
    else:
        if i % 2 == 0:
            client.expire("string:{}".format(i), random.randint(0, i))

# padding list
print("padding list keys")
for i in range(1, 1000000):
    ls = random.choices([item for item in range(1, 1000)], k=5)
    if WITHOUT_TTL == True:
        client.lpush("list:{}".format(i), ls)
    else:
        if i % 2 == 0:
            client.expire("list:{}".format(i), random.randint(0, i))

# padding set
print("padding set keys")
for i in range(1, 1000000):
    ls = random.choices([item for item in range(1, 1000)], k=5)
    if WITHOUT_TTL == True:
        client.sadd("set:{}".format(i), ls)
    else:
        if i % 2 == 0:
            client.expire("set:{}".format(i), random.randint(0, i))

# padding hash
print("padding hash keys")
for i in range(1, 1000000):
    members = {key: key**2 for key in random.choices([item for item in range(1, 1000)], k=5)}
    if WITHOUT_TTL == True:
        client.hmset("hash:{}".format(i), members)
    else:
        if i % 2 == 0:
            client.expire("hash:{}".format(i), random.randint(0, i))

# padding sorted_set
print("padding zset keys")
for i in range(1, 1000000):
    if WITHOUT_TTL == True:
        for j in range(i):
            client.zadd("zset:{}".format(i), i*2, i)
    else:
        if i % 2 == 0:
            client.expire("zset:{}".format(i), random.randint(0, i))

上午吃饭前放了500万个KEY,下午回来一看已经有不少过期了。

➜  rediscleaner redis-cli info keyspace
# Keyspace
db0:keys=3873122,expires=1870761,avg_ttl=261110455
➜  rediscleaner redis-cli info memory
# Memory
used_memory:760203104
used_memory_human:724.99M
used_memory_rss:326615040
used_memory_rss_human:311.48M
used_memory_peak:847701600
used_memory_peak_human:808.43M
used_memory_peak_perc:89.68%
used_memory_overhead:251183030
used_memory_startup:980528
used_memory_dataset:509020074
used_memory_dataset_perc:67.04%
total_system_memory:8589934592
total_system_memory_human:8.00G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:0.43
mem_allocator:libc
active_defrag_running:0
lazyfree_pending_objects:0

SCAN

#coding: utf8
__author__ = "郭 璞"
__email__ = "marksinoberg@gmail.com"
# cleaner for rubbish keys without ttl in redis.

import redis

client = redis.Redis("localhost", 6379)
offset, sequence = client.scan(0, count=100)
print("handling offset: {}".format(0))
with open("./keys", "a") as file:
    while offset !=0:
        print("handling offset: {}".format(offset))
        file.writelines([item.decode("utf8") + str("\n") for item in sequence])
        offset, sequence = client.scan(offset, count=100)
print("done.")

过滤 查找

ttl.sh

#!/usr/bin/bash
cat keys | xargs -n 1 -L 1 redis-cli ttl > ttl

filter_keys.sh

#!/usr/bin/bash
paste -d " " keys ttl | grep .*-1$ | cut -d " " -f 1 > without_ttl

最后400多万个key跑了6个小时才跑到(没用PIPELINE的后果)效果:

➜  rediscleaner wc keys && wc ttl && wc without_ttl
 4004138 4004138 48594962 keys
 4004138 4004138 18833511 ttl
 2002317 2002317 24300318 without_ttl
➜  rediscleaner

通过获取keys, ttl, without_ttl ,就可以拿到没有过期时间的key集合了。接下来就可以用于分析具体的业务问题了,对照着项目代码来看会有更好的效果。

分析工具

TODO 目前有个想法,做一套完整的分析工具

  • 探查整个Redis的key现状
  • monitor一段时间内的key请求现状
展开阅读全文

没有更多推荐了,返回首页