在前面几篇文章中,小编陆陆续续介绍了 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 所有编码方式(底层实现)三者之间的关系:
下面就这幅图来一一阐述每个数据类型的实现,由于底层实现都已经在前面文章分析了,所以不介绍了,如有不懂的参考以下链接:
- 【死磕 Redis】----- Redis 数据结构:dict
- 【死磕 Redis】----- Redis 数据结构:sds
- 【死磕 Redis】----- Redis 数据结构:ziplist
- 【死磕 Redis】----- Redis 数据结构: skiplist
- 【死磕 Redis】----- Redis 数据结构: intset
字符串对象 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 编码:
- 列表对象保存的所有字符串元素的长度都小于 64 字节
- 列表对象保存的元素数量小于 512 个
- 如果不能同时满足这两个条件,列表对象则使用 linkedlist
以上两个条件的是可以修改的,见 list-max-ziplist-value 和 list-max-ziplist-entries。
集合对象 SET
集合对象的编码有两种:intset 和 hashtable。
当集合对象可以同时满足一下两个条件时,对象使用 intset 编码:
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过 512 个
如果不能满足这两个条件的集合对象需要使用 hashtable 编码。其中第二条件可以修改配置文件修改:set-max-intset-entries。
有序集合对象 ZSET
有序集合的编码也有两种:ziplist 和 skiplist。
当有序集合对象可以同时满足以下两个条件,对象使用 ziplist 编码:
- 有序集合保存的元素数量小于 128 个
- 有序集合保存的所有元素成员的长度都小于 64 字节
不能同时满足以上两个条件的有序集合将使用 skiplist 编码。且上述两个条件可以通过配置文件修改,参数见:zset-max-ziplist-entries 和 zset-max-ziplist-value
参考
- 《Redis 设计与实现》
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。