写在前面

节省空间

  1、redis对于它所支持的五种数据类型,每种都提供了两种及以上的编码方式去存储(具体对应的编码方式可以百度)。因为基于内存的缘故,所以为了平衡时间与空间的使用效率在元素数量较多或较少时采用不同的策略,当然对于使用者这是透明的。

  2、查看redis键值的内部编码方式

    OBJECT ENCODING key

  3、对于每一个键,都会有一个结构去存储它的数据类型,编码格式,数据地址等信息。

1 typedef struct redisObject
2 {
3     unsigned type:4;            // 这种语法叫做位字段
4     unsigned notused:2;
5     unsigned encoding:4;
6     unsigned lru:22;
7     int refcount;
8     void *ptr;
9 }

   4、对于字符串类型(示意结构如下),键值可以用一个64位整数表示时,就会使用数据指针表示数据内容(REDIS_ENCODING_INT编码方式),以节省空间;redis3.0引入了REDIS_ENCODING_EMBSTR编码方式,在键值长度小于39时,字符串本身就会跟在redisObject结构之后,减少申请释放内存次数;当对REDIS_ENCODING_EMBSTR编码方式的字符串做任何修改后,都会改为REDIS_ENCODING_RAW编码方式;redis启动后,会预先建立10000个从0~9999这些数字的redisObject结构,当我们set这些值时会直接引用向它们,并自增refcount这个引用,但如果在配置文件参数中设置了maxmemory参数,将不会预先存储这些共享对象。

1 struct shshdr          // 仅仅是示意结构
2 {
3     int len;
4     int free;
5     char buf[];
6 }

  5、对于散列类型,当字段个数小于hash-max-ziplist-entries(配置文件参数,默认512)并且每个字段值长度小于hash-max-ziplist-value(配置文件参数,默认64)时,采用REDIS_ENCODING_ZIPLIST(示意结构如下)编码,否则采用REDIS_ENCODING_HT(真正的散列表);对于redis的键值对存储也是用散列表,但并不使用redisObject结构,所以数字键名不会比字符串名节省空间。

1 zlbytes // uint32 整个结构占用的空间
2 zltail  // uint32 末尾元素偏移
3 zllen   // unit16 元素数量     ————————————————————
4 元素1 ----------------------> | 前一个元素大小     |
5 元素2                         | 当前元素的编码类型 |
6 元素3                         | 当前元素大小       |
7 ......                        | 当前元素内容      |
8 zlend   // 结尾标识,永远为255  ————————————————————

  REDIS_ENCODING_ZIPLIST中的每个元素由四部分构成。

  第一部分存储前一个元素大小,用于倒序查找,当前一个元素小于254字节时,第一部分占用一个字节,否则占用5个字节;

  第二、三部分为元素编码类型和大小,当元素长度不大于63字节时,编码为ZIP_STR_06B(即0<<6),同时第三部分用6个二进制位记录长度,此时二、三部分占用一个字节;当元素长度大于63且小于16383字节时,二、三部分占用2字节,大于16383时占用5字节;

  第四部分如果元素内容可以转为数字的话会用相应数字存储,并用二、三部分来表示元素数字的类型(int16_t,int32_t等)。

  使用REDIS_ENCODING_ZIPLIST存储散列类型时,元素1存储字段1,元素2存储字段1值;插入,删除,查找(一跳一跳查找,跳过字段值)时都将移动后面的元素或遍历,因此上文的两个参数不能过大。

  6、对于列表类型,有REDIS_ENCODING_LINKEDLIST 和REDIS_ENCODING_ZIPLIST两种编码,也有list-max-ziplist-entries和list-max-ziplist-value两个参数控制变换编码的时机;REDIS_ENCODING_LINKEDLIST即双向链表,优化方式与字符串类型的键值相同;较新版本的redis增加了REDIS_ENCODING_QUICKLIST编码方式,它将一个长列表分成若干个以链表形式组织的ziplist,在减少空间占用的同时,提示REDIS_ENCODING_ZIPLIST编码的性能。

  7、对于集合类型,有REDIS_ENCODING_HT和REDIS_ENCODING_INTSET(结构如下),当元素都为整数且元素个数小于set-max-intset-entries(配置文件参数,默认512)时,采用REDIS_ENCODING_INTSET;intset默认的encoding是INTSET_ENC_INT16(即2字节),当无法满足时会升级为INTSET_ENC_INT32或INTSET_ENC_INT64,同时调整之前的元素;intset按序存储,采用二分查找,插入和删除效率较低;当初先非数字元素时,编码立刻变为REDIS_ENCODING_HT,此时即便将非数字元素删除,编码也不会回转。

1 typedef struct intset
2 {
3     uint32_t encoding;
4     uint32_t length;
5     int8_t contents[];
6 } intset;

  8、对于有序集合类型,有REDIS_ENCODING_SKIPLIST和REDIS_ENCODING_ZIPLIST,同样有两个参数zset-max-ziplist-entries和zset-max-ziplist-value去控制何时变换编码为跳跃表REDIS_ENCODING_SKIPLIST;在这种编码方式下使用两种数据结构来存储有序集合类型键值,散列表用来存储元素值与元素分数的映射关系以实现O(1)时间复杂度的命令,跳跃表存储元素分数及到元素的映射用以实现排序功能;这里的跳跃表允许分数相同的元素存在,并且增加了指向前一个元素的指针以实现倒序查找;此时元素值是用redisObject结构存储的,与字符串类型键值优化方式相同,分数按照double存储;采用REDIS_ENCODING_ZIPLIST时,按照“元素1的值,元素1的分数”的顺序排列,并且分数是有序的。

 

07-14 06:21