问题引入
Redis 是一款基于内存的 key-value 数据库,内存使用率是咱们十分关注的一个指标。
通常咱们都能够通过『EXPIRE key seconds』等形式,为指定键设置生存工夫;待过期后,Redis 会主动删除该键值对,以此避免内存应用持续增长。
然而,生产环境中,往往存在很多键值对都没有设置正当的生存工夫;随着业务的继续倒退,内存使用量也持续增长。
DBA 或者研发人员,在监控到 Redis 占用内存过高时,能够手动清理某些长期不拜访的键值对。如何判断一个键值对多久没有被拜访了呢?能够通过命令 object idletime 实现,该命令返回的是以后键从上一次拜访到当初通过的工夫(单位,秒):
127.0.0.1:6379> object idletime key
(integer) 2
在一次统计过程中,发现有一批 key 曾经很长时间没有拜访了(数月),然而 object idletime 命令却表明最近刚被拜访过(甚至数秒内 )。
为什么会不准呢?为此钻研了下 object idletime 的实现原理。
object idletime 实现
Redis 外部对象应用构造体 redisObject 示意,其定义为:
typedef struct redisObject {
// 数据类型,string,list,hash 等
unsigned type:4;
// 编码,即底层采纳哪种数据结构
unsigned encoding:4;
// 缓存淘汰应用;会存储上一次该对象的拜访工夫
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
// 援用计数
int refcount;
// 指向对应的数据结构
void *ptr;
} robj;
能够看到,正因为 redisObject.lru 字段的存在,咱们才能够获取到任何一个对象的闲暇工夫,以后工夫 – 上次访问工夫 lru,即可。
object 命令的实现函数为 objectCommand。
objectCommand(client *c)
objectCommandLookupOrReply(client *c, robj *key, robj *reply)
objectCommandLookup(client *c, robj *key)
robj *objectCommandLookup(client *c, robj *key) {
dictEntry *de;
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL;
return (robj*) dictGetVal(de);
}
objectCommandLookup 函数返回的是该键值对的值,即最终是根据 key-value 值对象的 lru 计算的。
值对象有什么非凡吗?Redis 外部的对象在某些状况下是能够共享的,比方整数。
#define OBJ_SHARED_INTEGERS 10000
struct sharedObjectsStruct {
// 共享的整数对象
*integers[OBJ_SHARED_INTEGERS],
};
set keya 1
set keyb 1
set keyc 1
因而,上述命令执行后,keya、keyb 以及 keyc 对应的值是同一个对象。而当咱们拜访 keyc 键时,其对应值对象的 lru 会更新,导致 object idletime 查问到的 keya 与 keyb 的闲暇工夫随之扭转。
127.0.0.1:6379> object idletime keya
(integer) 1302
127.0.0.1:6379> set keyb 1
OK
127.0.0.1:6379> get keyb
"1"
127.0.0.1:6379> object idletime keya
(integer) 4
业务中很多时候设置 key-value 只是设置一个标识,value 都为数字 1 等(比方通过 setnx 实现锁),这就导致大量的键对应的值对象关联起来,object idletime 随之也相互影响。
论断
当多个键的值是一些简略整数时,他们共享同一个值对象,而 object idletime 命令返回的是值对象的闲暇工夫,就导致这些键的 object idletime 相互影响。在应用 object idletime 统计键的闲暇工夫,须要特地留神这一点。