问题
程序开发过程中,因为错误代码导致在redis中写入了错误的数据,可能使得读取的时候报错。如果没有设置ttl自动过期,将长期留存在redis中。
例如,想在set的同时指定ttl,但是少给了一个TimeUnit参数:
1
| redisTpl.opsForValue().set(key, siteId, expire);
|
使得调用方法:
1 2 3 4
| void set(K key, V value, long timeout, TimeUnit unit);
void set(K key, V value, long offset);
|
从而产生了垃圾数据,还不能自动过期。
KEY前缀
项目中使用redis的时候,需要严格规划KEY的前缀,这样:
- 一来可以避免KEY冲突。
- 二来可以按业务范围查询维护相关的KEY。
上边代码中,我们错误设置的KEY的prefix=org_site_。
清理
有了统一的KEY浅醉,我们可以用keys
命令查询出所有相关的key。
1 2 3 4 5 6 7 8
| redis.crazy1984.com:6379[2]> keys org_site_* 1) "org_site_12_0" 2) "org_site_12_174" 3) "org_site_12_1" 4) "org_site_344_174" 5) "org_site_344_3182" 6) "org_site_345_0" 7) "org_site_344_3178"
|
暴力清理
对于查出的key,如果可以全部清理,那么可以用以前介绍过的awk
命令,自动生成del命令(将下边的get替换成del即可),然后执行。
1 2 3 4 5 6 7 8 9 10
| $ echo "keys org_site_*" |redis-cli -h redis.crazy1984.com -n 2 --raw | awk '{print "get "$1}' | redis-cli -h redis.crazy1984.com -n 2 --raw 12 12 1 12 12 344 1003181 345 344
|
条件清理
如果线上环境不允许我们暴力清理,只能定向清理错误的数据。那么如何处理呢?这时可以用上redis支持执行lua脚本的功能了。
以我们的情况为例,我们的value本想存入一个int,但是因为错误的设置,使得value的数据变长了。
比如:原来的value=123,因为错误的set
把ttl作为了offset,使得实际的数据可能变成
1 2 3 4
| redis.crazy1984.com:6379[2]> setrange org_site_123 100 123 (integer) 103 redis.crazy1984.com:6379[2]> get org_site_123 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00123"
|
正常情况下:value应该是个int,长度不会超过32 bytes。
错误情况下:因为ttl(本处使用的单位是秒)至少都是60以上,所以可以通过判断数据的长度来判断是否是错误的数据。lua脚本的参数以KEYS
全局变量方式传入。
The arguments can be accessed by Lua using the KEYS
global variable in the form of a one-based array (so KEYS[1]
, KEYS[2]
, …).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| local del_keys = {}; local used_keys = {};
for i,k in ipairs(KEYS) do local ktest = redis.call("STRLEN", k) local kval = redis.call("GET", k) if ktest > 10 then redis.call("DEL", k) local stest = string.format("DEL %s %d", k, ktest) del_keys[#del_keys+1] = {stest, kval}; else local stest = string.format("GET %s %d", k, ktest) used_keys[#used_keys+1] = {stest, kval}; end end return del_keys;
|
把这段脚本保存为文件 redis_clean.lua
然后再结合redis的keys
命令,把所有以org_site_
开头的key作为参数传给lua处理。
1 2 3
| $ echo "keys org_site_*" |redis-cli -h redis.crazy1984.com -n 2 --raw | xargs redis-cli -h redis.crazy1984.comm -n 2 --eval redis_clean.lua 1) 1) "DEL org_site_123 103" 2) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00123"
|
问题解决。