死磕 Redis----- Redis 数据结构:对象(RedisObject)

Posted chenssy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了死磕 Redis----- Redis 数据结构:对象(RedisObject)相关的知识,希望对你有一定的参考价值。

原文:https://www.cmsblogs.com/category/1391389927996002304chenssy


在前面几篇文章中,小编陆陆续续介绍了 Redis 用到的所有主要数据结构,如比如简单动态字符串(SDS)字典(dict)压缩列表(ziplist)整数集合( intset)跳跃表(skiplist)。然而 Redis 并没有直接使用这些数据结构来实现键值对的数据库,而是在这些数据结构之上又包装了一层 RedisObject(对象),RedisObject 有五种对象:字符串对象、列表对象、哈希对象、集合对象和有序集合对象。

redisObject 定义在 redis.h 文件中:

    typedef struct redisObject 
        unsigned type:4;
        unsigned encoding:4;
        unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
        int refcount;
        void *ptr;
     robj;

其中各字段的含义如下:

  • 4 位的 type 表示具体的数据类型。Redis 中共有 5 种数据类型。2^4 = 8 足以表示这些类型。
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
  • 4 位的 encoding 表示该类型的物理编码方式,同一种数据类型可能有不同的编码方式。目前 Redis 中主要有8种编码方式:
    #define REDIS_ENCODING_RAW 0     /* Raw representation */
    #define REDIS_ENCODING_INT 1     /* Encoded as integer */
    #define REDIS_ENCODING_HT 2      /* Encoded as hash table */
    #define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
    #define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
    #define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
    #define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
    #define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
  • lru字段表示当内存超限时采用LRU算法清除内存中的对象。
  • refcount表示对象的引用计数。
  • ptr指针指向真正的存储结构。

下图展示了 redisObject 、Redis 所有数据类型、以及 Redis 所有编码方式(底层实现)三者之间的关系:

下面就这幅图来一一阐述每个数据类型的实现,由于底层实现都已经在前面文章分析了,所以不介绍了,如有不懂的参考以下链接:

字符串对象 STRING

字符串对象的 encoding 有三种,分别是:int、raw、embstr。

  • 如果一个字符串对象保存的是整数值,并且这个整数值可以用 long 类型标识,那么字符串对象会将整数值保存在 ptr 属性中,并将 encoding 设置为 int。如:
127.0.0.1:6379> set number 1234
OK
127.0.0.1:6379> object encoding number
"int"
  • 如果字符串对象保存的是一个字符串值,并且这个字符串的长度大于 44 字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为 raw。如:
127.0.0.1:6379>  set str "Spring Boot lets you externalize your configu"
OK
127.0.0.1:6379> strlen str
(integer) 45
127.0.0.1:6379> object encoding str
"raw"

如果这个字符串的长度小于 45 字节,那么字符串对象将使用 embstr 编码的方式来保存这个字符串值了。

127.0.0.1:6379>  set str "Spring Boot lets you externalize your config"
OK
127.0.0.1:6379> strlen str
(integer) 44
127.0.0.1:6379> object encoding str
"embstr"

可能有小伙伴说,既然有了 raw 的编码方式,为什么还会有 embstr 的编码方式呢?因为 embstr 编码是专门用于保存短字符串的一种优化编码方式,它具有如下优点:

  • embstr 编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次
  • 释放 embstr 编码的字符串对象只需要调用一次内存释放函数,而释放 raw 编码的字符串对象需要调用两次内存释放函数
  • 因为 embstr 编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这种编码的字符串对象比起 raw 编码的字符串对象能够更好地利用缓存带来的优势

哈希对象 HASH

哈希对象的编码有两种,分别是:ziplist、hashtable。

当哈希对象保存的键值对数量小于 512,并且所有键值对的长度都小于 64 字节时,使用压缩列表存储;否则使用 hashtable 存储。这两个条件是可以修改的。见 hash-max-ziplist-value 和 hash-max-ziplist-entries。

下面将演示一番:

# 插入一个 value 不超过 64 字节的键值对
127.0.0.1:6379> hset book name "sikeRedis"
(integer) 1
127.0.0.1:6379> object encoding book
"ziplist"
# 插入一个 value 为 64 字节的键值对
127.0.0.1:6379> hset person des "chenssy1chenssy1chenssy1chenssy1chenssy1chenssy1chenssy1chenssy1"
(integer) 1
127.0.0.1:6379> object encoding person
"ziplist"
# value 增大到 65 字节
127.0.0.1:6379> hset person des "chenssy1chenssy1chenssy1chenssy1chenssy1chenssy1chenssy1chenssy11"
(integer) 0
127.0.0.1:6379> object encoding person
"hashtable"

我们看到当我们插入一个 value 为 64 字节的键值对时,类型依然是 ziplist,当把 value 调整到 65 个字节的时候,对象的编码也由 ziplist 变为 hashtable 了。

下面演示因为键值对数量过多引起的编码转换的情况:

# 插入 512 个键值对
127.0.0.1:6379> eval "for i = 1,512 do  redis.call('HSET',KEYS[1],i,i) end " 1 "numbers"
(nil)
127.0.0.1:6379> hlen numbers
(integer) 512
# 编码为 ziplist
127.0.0.1:6379> object encoding numbers
"ziplist"
# 继续插入一个键值对
127.0.0.1:6379> hset numbers 513 "513"
(integer) 1
127.0.0.1:6379> hlen numbers
(integer) 513
# 编码变为 hashtable
127.0.0.1:6379> object encoding numbers
"hashtable"

以下的数据类型就不再演示了,具体流程和 hash 一致。

列表对象 LIST

列表对象的编码有两种: ziplist 和 linkedlist。

当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码:

  1. 列表对象保存的所有字符串元素的长度都小于 64 字节
  2. 列表对象保存的元素数量小于 512 个
  3. 如果不能同时满足这两个条件,列表对象则使用 linkedlist

以上两个条件的是可以修改的,见 list-max-ziplist-value 和 list-max-ziplist-entries。

集合对象 SET

集合对象的编码有两种:intset 和 hashtable。

当集合对象可以同时满足一下两个条件时,对象使用 intset 编码:

  1. 集合对象保存的所有元素都是整数值
  2. 集合对象保存的元素数量不超过 512 个

如果不能满足这两个条件的集合对象需要使用 hashtable 编码。其中第二条件可以修改配置文件修改:set-max-intset-entries。

有序集合对象 ZSET

有序集合的编码也有两种:ziplist 和 skiplist。

当有序集合对象可以同时满足以下两个条件,对象使用 ziplist 编码:

  1. 有序集合保存的元素数量小于 128 个
  2. 有序集合保存的所有元素成员的长度都小于 64 字节

不能同时满足以上两个条件的有序集合将使用 skiplist 编码。且上述两个条件可以通过配置文件修改,参数见:zset-max-ziplist-entries 和 zset-max-ziplist-value

参考

  • 《Redis 设计与实现》

以上是关于死磕 Redis----- Redis 数据结构:对象(RedisObject)的主要内容,如果未能解决你的问题,请参考以下文章

死磕 Redis----- Redis 数据结构: skiplist

死磕 Redis----- Redis 数据结构:ziplist

死磕 Redis----- Redis 数据结构:ziplist

死磕 Redis----- Redis 数据结构:ziplist

死磕 Redis----- Redis 数据结构:dict

死磕 Redis----- Redis 数据结构:sds