2022-08-26  阅读(64)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/series/article/2734920701

在前面几篇文章中,小编陆陆续续介绍了 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 设计与实现》

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] ,回复【面试题】 即可免费领取。

阅读全文