redis对象内存回收和整数对象共享池

一、内存回收

redis使用c语言实现的,c语言没有自动内存回收功能,所以在redis的值对象中使用了一个引用计数的属性(refcount)来实现值对象的内存存回收,redis根据这一属性维护对象的回收,当创建对象时初始化为1,被程序引用时加1,当引用计数为0时,对象占用的内存释放

二、redisObject结构

基于redis 5.0.5版本

typedef struct redisObject {
     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;

 

三、整数对象共享池

对象的引用计数除用来实现内存回收外,还用来实现对象共享;redis在启动的时候,创建了一个10000个(个数在server.h的OBJ_SHARED_INTEGERS来定义)0到9999的整数值对象(整数对象池),用于共享,当redis需要使用0到9999的值对象时,直接从池中拿来使用,拿来使用的对象只是引用计数加1,而不用创建,从而有效的节省内存,这就是整数对象共享池。

 

四、查看使用整数对象共享池

192.168.91.128:6379> set k1 1000
OK
192.168.91.128:6379> object refcount k1
(integer) 2
192.168.91.128:6379> set k11 1000
OK
192.168.91.128:6379> object refcount k11
(integer) 3

当set k1 为1000时,引用计数为2 ,直接使用了整数对象共享池,当再set k11 为1000时,引用计数为3,

 

五、整数对象共享池使用条件

整数对象共享池与maxmemory和maxmemory-policy设置冲突,原因是:

  • LRU算法需要获取对象最后被访问时间, 以便淘汰最长未访问数据, 每个对象最后访问时间存储在redisObject对象的lru字段。 对象共享意味着多个引用共享同一个redisObject, 这时lru字段也会被共享, 导致无法获取每个对象的最后访问时间
  • 如果没有设置maxmemory,那么服务器内存耗尽时都不会驱逐键,一般会报oom的错误,所以对象共享池能正常工作

 

所以:当设置了maxmemory+maxmemory-policy(如volatile-lru, allkeys-lru),对象共享池是禁用的

 

六、为什么只有整数对象共享池?

首先整数对象池复用的几率最大, 其次对象共享的一个关键操作就是判断相等性, Redis之所以只有整数对象池, 是因为整数比较算法时间复杂度为O( 1) , 只保留一万个整数为了防止对象池浪费。 如果是字符串判断相等性, 时间复杂度变为O( n) , 特别是长字符串更消耗性能( 浮点数在Redis内部使用字符串存储) 。 对于更复杂的数据结构如hash、 list等, 相等性判断需要O( n2) 。 对于单线程的Redis来说, 这样的开销显然不合理, 因此Redis只保留整数共享对象池。