原文:https://www.topjava.cn/category/1391389927996002304 『chenssy』
在前面几篇文章中,小编陆陆续续介绍了 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;
其中各字段的含义如下:
/* Object types */ #define REDIS_STRING 0 #define REDIS_LIST 1 #define REDIS_SET 2 #define REDIS_ZSET 3 #define REDIS_HASH 4
#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 */
下图展示了 redisObject 、Redis 所有数据类型、以及 Redis 所有编码方式(底层实现)三者之间的关系:
下面就这幅图来一一阐述每个数据类型的实现,由于底层实现都已经在前面文章分析了,所以不介绍了,如有不懂的参考以下链接:
字符串对象的 encoding 有三种,分别是:int、raw、embstr。
127.0.0.1:6379> set number 1234 OK 127.0.0.1:6379> object encoding number "int"
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 编码是专门用于保存短字符串的一种优化编码方式,它具有如下优点:
哈希对象的编码有两种,分别是: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 一致。
列表对象的编码有两种: ziplist 和 linkedlist。
当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码:
以上两个条件的是可以修改的,见 list-max-ziplist-value 和 list-max-ziplist-entries。
集合对象的编码有两种:intset 和 hashtable。
当集合对象可以同时满足一下两个条件时,对象使用 intset 编码:
如果不能满足这两个条件的集合对象需要使用 hashtable 编码。其中第二条件可以修改配置文件修改:set-max-intset-entries。
有序集合的编码也有两种:ziplist 和 skiplist。
当有序集合对象可以同时满足以下两个条件,对象使用 ziplist 编码:
不能同时满足以上两个条件的有序集合将使用 skiplist 编码。且上述两个条件可以通过配置文件修改,参数见:zset-max-ziplist-entries 和 zset-max-ziplist-value