问题引入
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 10000struct sharedObjectsStruct { //共享的整数对象 *integers[OBJ_SHARED_INTEGERS],};set keya 1set keyb 1set keyc 1
因而,上述命令执行后,keya、keyb以及keyc对应的值是同一个对象。而当咱们拜访keyc键时,其对应值对象的lru会更新,导致object idletime查问到的keya与keyb的闲暇工夫随之扭转。
127.0.0.1:6379> object idletime keya(integer) 1302127.0.0.1:6379> set keyb 1OK127.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统计键的闲暇工夫,须要特地留神这一点。