Redis从入门到精通【进阶篇】之对象机制详解

这篇具有很好参考价值的文章主要介绍了Redis从入门到精通【进阶篇】之对象机制详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


Redis从入门到精通【进阶篇】之对象机制详解,Redis从入门到精通2023版,redis,数据库,后端,面试

0. 前言

Redis 之所以是一款高性能和受大家欢迎的的内存数据库,不仅是它支持多种数据类型,包括字符串、列表、哈希、集合、有序集合等数据结构。而且这些数据类型都是由对象结构(redisObject) 和对应编码的数据结构组合而成。在 Redis 中,对象结构是所有数据类型的底层实现,它包含了数据对象的类型、引用计数、编码方式、值等信息。使得Redis在性能和内存利用方面有着无可比拟的地位。奠定了它的江湖地位。

本文将重点介绍 Redis 对象结构(redisObject) 的实现原理。我们将从对象结构的基本原理入手,分析对象结构的组成部分,以及对象结构的内存管理和引用计数等重要概念。同时,我们也会探讨 Redis 对象结构的应用场景和实际应用案例,帮助大家更好地理解 Redis 的内部实现机制。

今天我们换一种分享方式,先抛出一些问题,这些问题可能是面试经常被问到的

1. 什么是redisObject对象?
2. redisObject数据结构解析?
4. redisObject对象如何实现数据共享和对象池技术?
5. redisObject对象的大小是否会随着数据类型的不同而变化?
6. redisObject对象的序列化和反序列化有哪些常用的方法?
7. Redis中还有哪些技术可以提高系统的性能和稳定性?
8. Redis中的对象池技术如何管理内存?
9. Redis中的共享池如何管理共享字符串对象?

1. 详解

Redis 中的 redisObject 对象是 Redis 中的基本数据结构,它是 Redis 实现高效存储和处理数据的关键之一。redisObject 对象可以表示各种不同类型的数据,如字符串、列表、哈希表等,它们在 Redis 中被广泛使用。

1.1 redisObject 对象设计目的

增强数据结构的灵活性:Redis 中支持多种不同类型的数据结构,如字符串、列表、哈希表、集合和有序集合等,这些数据结构在 Redis 中都被表示为 redisObject 对象。通过将不同类型的数据统一封装成 redisObject 对象,Redis 可以更加灵活地处理不同类型的数据,从而支持多种不同的应用场景。

简化内存管理:Redis 中所有数据都存储在内存中,因此需要对内存进行有效的管理。通过使用 redisObject 对象可以简化内存管理的复杂性,因为 redisObject 对象可以自己管理内部的内存空间,从而避免了手动管理内存的问题。

提高数据存储和访问的效率:Redis 中的 redisObject 对象是一个轻量级的对象,它的大小通常只有几个字节,因此可以快速地存储和访问。此外,Redis 还通过对象共享和对象池等技术来优化内存使用效率,从而提高了数据存储和访问的效率。

方便数据序列化和反序列化:Redis 中的 redisObject 对象可以方便地进行序列化和反序列化,因为 redisObject 对象是一种结构化的数据类型,它可以被序列化成二进制格式或其他格式,方便数据的传输和存储。

1.2 redisObject数据结构

redisObject 是 Redis 中的一个基本数据结构,它用于表示 Redis 中的各种数据类型,如字符串、列表、哈希表、集合和有序集合等。redisObject 结构体定义如下:
源码地址 https://github.com/redis/redis/blob/6.0/src/server.h
Redis从入门到精通【进阶篇】之对象机制详解,Redis从入门到精通2023版,redis,数据库,后端,面试
我把源码复制出来解析一下

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

字段含义:

  • type:表示 redisObject 对象的类型,它是一个 4 位的整数,用于区分不同的数据类型。Redis 中支持多种不同的数据类型,如字符串、列表、哈希表、集合和有序集合等,每种数据类型都有一个对应的 type 值。
  • encoding:表示 redisObject 对象的编码方式,它是一个 4 位的整数,用于区分不同的编码方式。由于 Redis 中的每种数据类型可以用多种不同的编码方式来表示,因此需要使用 encoding 字段来区分不同的编码方式。
  • lru:表示 redisObject 对象的 LRU 时间,它是一个整数,用于记录对象最后一次被访问的时间。LRU(Least Recently Used)算法用于判断哪些对象最近最少使用,从而进行淘汰。
  • refcount:表示 redisObject 对象的引用计数,它是一个整数,用于记录当前对象被引用的次数。当引用计数为 0 时,表示当前对象可以被释放。
  • ptr:表示 redisObject 对象的实际数据,它是一个指针,指向对象的实际数据。不同类型的对象的实际数据类型不同,如字符串对象的实际数据是一个 char 数组,列表对象的实际数据是一个链表等。

1.2 Redis 是如何使用redisObject

Redis 在使用 redisObject 时,主要涉及以下几个方面:

1.2.1. 对象创建

Redis 使用 redisObjectCreate 函数来创建数据对象,并初始化对象的类型、编码方式和实际值等属性。在创建对象时,Redis 会根据实际值的类型来选择适当的编码方式,以提高内存使用效率。
redisObjectCreate 函数的定义和实现位于 Redis 源码的 src/object.c 文件中。
源码位置https://github.com/redis/redis/blob/6.0/src/object.c
还是老办法我们把源码复制出来解析一下

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = REDIS_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;
    return o;
}

我们在源码中可以看到,createObject 函数接受两个参数:type 和 ptr,分别表示对象的类型和实际值。在函数内部,它首先使用 zmalloc 函数分配一块内存,大小为 redisObject 结构体的大小。然后,它初始化 redisObject 结构体的成员变量,包括对象类型(type)、编码方式(encoding)、实际值指针(ptr) 和引用计数(refcount)。最后,它返回创建的 redisObject 对象。

声明一下Redis 中还有其他的 redisObjectCreate 函数,用于创建不同类型的数据对象。例如,createStringObject 函数用于创建字符串对象,createListObject 函数用于创建列表对象,createSetObject 函数用于创建集合对象等。这些函数都是在 src/object.c 源码 文件中定义和实现的。
我就不仔细讲了方便大家理解记忆点击上面的链接进去大概扫一遍源码有个基本认知

1.2.2. 对象引用计数

Redis 使用对象引用计数来管理数据对象的生命周期,确保对象在使用过程中不会被意外释放。每个对象都包含一个引用计数(refcount)属性,表示对象当前被引用的次数。在对象被创建时,引用计数初始化为1,当对象被引用时,引用计数加1,当对象被释放时,引用计数减1。当引用计数为0时,对象被释放。所以这个也是JVM 的GC 机制中的一种策略。引用计数法。思想和目的都是一样的,大家可以类比学习。

1.2.3. 对象共享

Redis 中有些数据对象是共享的,例如字符串常量、空列表等。为了节省内存,Redis 使用共享对象来表示这些数据对象,多个变量可以共享同一个对象。Redis 通过将共享对象的引用计数设置为负数来区分共享对象和普通对象。其实如果是java同学看到这块可能会联想到java语言中的一个特性,叫做常量池(Constant Pool)。最常见的字符串常量池(String Pool)专门用于存储字符串常量对象。
Java 中的字符串常量池是一种特殊的内存区域,用于存储字符串常量对象。这些字符串常量对象是在编译期间或运行期间通过字面量(Literal)创建的,例如字符串直接量、字符直接量等。当 Java 程序中出现字符串直接量时,Java 编译器会自动将其添加到字符串常量池中,并在运行时共享这些字符串常量对象,以提高内存使用效率。
那么Redis其实也是这个思想和java的常量池思想类似,以提高内存使用效率 。所以我们可以看到其实设计思想和语言无关,只要在恰当的场景中,能够有效的解决某种问题,就是最优秀的设计思想。其实C++,python,javascript 都使用这种思想,以提高内存使用效率和性能。 所以大家理解和对比记忆很重要,会发现计算机的世界里有的一套思想横行几十年还都是最优解。好了不扯了,咱们继续。

redis预分配的值对象如下:各种命令的返回值,比如成功时返回的OK,错误时返回的ERROR,命令入队事务时返回的QUEUE,等等包括0在内,小于REDIS_SHARED_INTEGERS的所有整数(REDIS_SHARED_INTEGERS的默认值是10000)

Redis预分配的整数对象和字符串值对象的源码位置在Redis的src目录下的以下文件中:

  • redis.h:定义了Redis的整数对象结构体redisObject和字符串值对象结构体robj,以及REDIS_SHARED_INTEGERS宏定义。

  • object.c:定义了Redis的对象操作函数,包括创建、释放、增加和减少引用计数等操作,同时也包括预分配整数对象和字符串值对象的函数initServerSharingObjects()。
    Redis从入门到精通【进阶篇】之对象机制详解,Redis从入门到精通2023版,redis,数据库,后端,面试

在initServerSharingObjects()函数中,Redis会预分配一定数量的整数对象和字符串值对象,并将它们缓存在全局共享对象池中。这些对象的数量可以通过在redis.conf配置文件中设置sharedobjects-pool-size选项来调整。默认情况下,REDIS_SHARED_INTEGERS宏定义的值为10000,表示预分配10000个整数对象,而字符串值对象则根据需要预分配一些常见的字符串值对象,如"OK"、"ERROR"和"QUEUE"等。

1.2.4. 对象的编码方式

Redis 支持多种编码方式来表示不同类型的数据对象,例如字符串可以使用 int、embstr 或 raw 编码方式。Redis 会根据实际值的类型和大小来选择适当的编码方式,以提高内存使用效率。在对象创建时,Redis 会自动选择适当的编码方式,并将编码方式保存在对象的编码(encoding)属性中。
对象的 ptr 指针指向对象的底层实现数据结构, 而这些数据结构由对象的 encoding 属性决定。encoding 属性记录了对象所使用的编码, 也即是说这个对象使用了什么数据结构作为对象的底层实现。

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// encoding 的10种类型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字符串对象是简单动态字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long类型的整数
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //双端链表,不在使用
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */         //压缩列表
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */          //整数集合
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */      //跳跃表和字典
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */   //embstr编码的简单动态字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由压缩列表组成的双向列表-->快速列表

每种类型的对象都至少使用了两种不同的编码,下面列出了每种对象可使用的编码方式。
来自网上
Redis从入门到精通【进阶篇】之对象机制详解,Redis从入门到精通2023版,redis,数据库,后端,面试

1.2.4. 对象的值

Redis 使用 redisObject 中的 ptr 属性来指向实际的数据对象,实际数据对象的类型和内容取决于对象的类型和编码方式。例如,对于字符串对象,ptr 指向字符串的字符数组,对于列表对象,ptr 指向列表的头节点。

所以这样来看,我们之前讲过的所有Redis的基本数据类型,其实底层创建的时候基本上都是。Redis 通过使用 redisObject 来统一表示不同类型的数据对象,并使用对象引用计数、共享对象、编码方式和实际值等机制来管理和优化数据对象的使用。而且使用 redisObject 可以帮助 Redis 实现高效的内存管理和数据存储。所以如果面试的花,能简略的答出来这一段内容的关键点,也算是有一定理解了。

2. 总结

redis使用自己实现的对象机制(redisObject)来实现类型判断、命令多态和基于引用次数的垃圾回收;redis会预分配一些常用的数据对象,并通过共享这些对象来减少内存占用,和避免频繁的为小对象分配内存。
最后我们来回答一下上面抛出的问题

2.1. redisObject对象如何实现数据共享和对象池技术?

数据共享技术:Redis 中的字符串对象是可以共享的,如果多个字符串对象的值相同,那么它们可以共享同一个 redisObject 对象。Redis 通过使用共享池来实现字符串对象的共享,共享池中存储了所有的共享字符串对象,每个共享字符串对象都有一个引用计数,当字符串对象不再被使用时,可以将它从共享池中删除。

对象池技术:Redis 中的对象池是用于存储 redisObject 对象的内存池,它可以有效地管理对象的内存,减少内存碎片和内存分配的开销。Redis 中的对象池使用两个栈来管理空闲的 redisObject 对象,一个是大对象栈,用于管理较大的对象,另一个是小对象栈,用于管理较小的对象。当需要分配新的 redisObject 对象时,Redis 会先从对象池中查找是否有空闲对象,如果有,则直接分配给新的对象,如果没有,则从系统中分配新的内存空间。

2.2. redisObject对象的大小是否会随着数据类型的不同而变化

Redis 中的 redisObject 对象的大小会随着数据类型的不同而变化。不同类型的 redisObject 对象所需要的内存空间是不同的,因此它们的大小也会不同。
以字符串对象为例,Redis 中的字符串对象包含一个 len 字段和一个 buf 字段,其中 len 字段表示字符串的长度,buf 字段表示字符串的实际内容。因此,字符串对象的大小等于 len 字段的大小加上 buf 字段的大小。而对于列表对象、哈希表对象、集合对象和有序集合对象等,它们的大小也会随着数据结构的复杂度和元素数量的不同而变化。
需要注意的是,Redis 中的 redisObject 对象还包含一些额外的字段,如 type、encoding、lru 和 refcount 等,这些字段也会占用一定的内存空间。因此,在计算 redisObject 对象的大小时,还需要考虑这些额外的字段所占用的内存空间。

2.3. Redis中的对象池技术如何管理内存?

Redis 中的对象池技术是一种用于管理内存的技术,它可以有效地减少内存碎片和内存分配的开销,提高 Redis 的内存使用效率。对象池技术可以分为两个部分:内存池和空闲对象管理。

内存池:Redis 中的对象池使用一块大的内存空间来存储 redisObject 对象。这个内存空间被称为内存池,它可以分为两个部分:大对象池和小对象池。大对象池用于管理较大的 redisObject 对象,而小对象池用于管理较小的 redisObject 对象。内存池中的每个 redisObject 对象都有一个固定的大小,这样就可以避免内存碎片的产生。

空闲对象管理:Redis 中的对象池使用两个栈来管理空闲的 redisObject 对象,一个是大对象栈,另一个是小对象栈。当需要分配新的 redisObject 对象时,Redis 会先从对象池中查找是否有空闲对象,如果有,则直接分配给新的对象,如果没有,则从系统中分配新的内存空间。

当 redisObject 对象不再被使用时,对象池会将这个对象放回对象池中,以便下次再次使用。这个过程会先将对象的实际数据清空,然后将对象放入对应的空闲对象栈中,同时更新对象的 refcount 字段,将其设置为 0。当空闲对象栈中的对象数量达到一定的阈值时,对象池会释放一部分内存,以便回收内存空间。
这种设计是大多数编程语言都喜欢使用的一种思想。

2.4.Redis中的共享池如何管理共享字符串对象?

共享池是用于管理共享字符串对象的内存池。共享字符串对象是指多个字符串对象的值相同,可以共享同一个 redisObject 对象。Redis通过使用共享池来实现字符串对象的共享,从而提高 Redis 的内存使用效率。 共享池中存储了所有的共享字符串对象,每个共享字符串对象都有一个引用计数。当创建新的字符串对象时,Redis 会先在共享池中查找是否存在相同值的共享字符串对象。如果存在,则将新的字符串对象的指针指向共享字符串对象的地址,并将共享字符串对象的引用计数加 1。如果不存在,则创建新的字符串对象,并将其加入共享池中。 当某个字符串对象不再被使用时,Redis 会将其从共享池中删除,并将其引用计数减 1。如果引用计数变为 0,则表示该字符串对象不再被任何其他对象所引用,可以将其释放并回收其所占用的内存。

这块有个坑 共享池中的每个共享字符串对象都是只读的,就类似于JAVA语言的String对象。如果需要修改一个共享字符串对象的值,那么需要先将其从共享池中拷贝出来,创建一个新的字符串对象,并将其值修改为新的值。这样就可以避免修改共享字符串对象的值对其他对象造成影响。

2.5. 如何判断一个字符串对象是否在共享池中?

可以通过 Redis 的一些命令,如 object encodingobject idletimedebug object 等,来判断一个字符串对象是否在共享池中。如果字符串对象是共享字符串对象,那么 Redis 会返回特定的结果,可以通过这些结果来判断字符串对象是否在共享池中。

3. Redis从入门到精通系列文章

《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
《Redis从入门到精通【进阶篇】之持久化 AOF详解》
《Redis从入门到精通【进阶篇】之持久化RDB详解》
《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
Redis从入门到精通【进阶篇】之对象机制详解,Redis从入门到精通2023版,redis,数据库,后端,面试
大家好,我是冰点,今天的Redis从入门到精通【进阶篇】之对象机制详解,全部内容就是这些。如果你有疑问或见解可以在评论区留言。文章来源地址https://www.toymoban.com/news/detail-523122.html

到了这里,关于Redis从入门到精通【进阶篇】之对象机制详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解

    上个篇章回顾,我们上个章节,讲了Redis中的快表(QuickList),它是一种特殊的数据结构,用于存储一系列的连续节点,每个节点可以是一个整数或一个字节数组。快表是Redis中的底层数据结构之一,常用于存储有序集合(Sorted Set)等数据类型的底层实现。 那么本章讲解Red

    2024年02月09日
    浏览(40)
  • Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解

    前面的Redis从入门到精通的基础篇和进阶篇都是在使用层面和概念层面,本章节,我们了解一下redis的底层数据结构,上几个章节,我们讲了SDS,字典 。本章节我们聊一下ZipList。 压缩列表(ZipList)就是redis为了节约内存而设计开发的数据结构,并且作为列表键和哈希键的底层

    2024年02月08日
    浏览(80)
  • Redis从入门到精通【高阶篇】之底层数据结构整数集(IntSet)详解

    上个篇章回顾,我们上个章节我们学习了《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》,我们从源码层了解字典是一种以键值对(key-value)形式存储数据的数据结构。在 Redis 中,字典使用哈希表来实现。哈希表是一种以常数时间复杂度 O(1) 进行插入、删

    2024年02月09日
    浏览(30)
  • Redis从入门到精通!!

    ❤ 作者主页:李奕赫揍小邰的博客 ❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~* 🍊 记得点赞、收藏、评论⭐️⭐️⭐️ 📣 认真学习!!!🎉🎉   1.1 Redis的定义和特点 1.1.1 Redis的定义和特点 Redis是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和消息中间件。

    2024年02月11日
    浏览(31)
  • 【Redis进阶】一文搞懂Redisson的看门狗机制底层实现

    看门狗机制是Redission提供的一种自动延期机制,这个机制使得 Redission提供的分布式锁是可以自动续期的 。 看门狗机制提供的默认超时时间是30*1000毫秒,也就是30秒 如果一个线程获取锁后,运行程序到释放锁所花费的时间大于锁自动释放时间(也就是看门狗机制提供的超时时

    2024年02月16日
    浏览(31)
  • Redis从入门到精通(三:常用指令)

    前边我们介绍了redis存储的四种基本数据类型,并纵向介绍了这四种数据类型的各种指令操作,现在我们这个章节从横向来总结一下关于key的常用指令和数据库常用指令 删除指定key del key 获取key是否存在 exists key 获取key的类型 type key 排序 sort 改名 rename key newkey renamenx key new

    2024年02月07日
    浏览(28)
  • Redis高可用:哨兵机制(Redis Sentinel)详解

    目录 1.什么是哨兵机制(Redis Sentinel) 2.哨兵机制基本流程 3.哨兵获取主从服务器信息 4.多个哨兵进行通信 5.主观下线和客观下线 6.哨兵集群的选举 7.新主库的选出 8.故障的转移 9.基于pub/sub机制的客户端事件通知 Redis Sentinel,即Redis哨兵,在Redis 2.8版本开始引入。哨兵的核心

    2024年02月12日
    浏览(29)
  • 【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制

            无论先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议)。或者采用串行化,

    2024年02月10日
    浏览(50)
  • Redis系列(四):哨兵机制详解

    https://blog.zysicyj.top/ 前面我们说过,redis采用了读写分离的方式实现高可靠。后面我们说了,为了防止主节点压力过大,优化成了主-从-从模式 这里主从模式下涉及到的几个问题: 主库真的挂了吗? 我们应当选择哪个从库作为主库? 怎样让其他从库知道新的主库信息呢? 中

    2024年02月12日
    浏览(30)
  • redis持久化机制 & 事务详解

    目录 前言: 持久化机制 RDB(Redis DataBase) 手动触发  save bgsave 自动触发 RDB特点 AOF(append only file) 缓冲区刷新策略 重写机制 aof重写流程 混合持久化 事务 事务操作命令 WATCH WATCH实现原理     redis为了保证高可用引入了持久化机制,目的就是为了redis服务器重启时可以恢复

    2024年02月11日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包