redis源码剖析(五)—— 字符串,列表,哈希,集合,有序集合

文章目录

    • 对象
    • REDIS_STRING (字符串)
    • REDIS_LIST 列表
    • REDIS_SET (集合)
    • REDIS_ZSET (有序集合)
    • REDIS_HASH (hash表)
    • int refcount(引用计数器)
    • unsigned lru:REDIS_LRU_BITS

对象

对于 Redis 来说使用了 redisObject 来对所有的对象进行了封装:

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;

我们先关注两个参数

type 和 encoding :

/* Object types */
// 对象类型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
/* 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. */
// 对象编码
#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  /* E  dncoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

所以通过这段代码我们可以知道 Redis 支持的数据类型如下:

type	类型
REDIS_STRING	字符串
REDIS_LIST	列表
REDIS_SET	集合
REDIS_ZSET	有序集合
REDIS_HASH	哈希表

Redis 的 Object 通过 ptr 指向具体的底层数据。Redis 的底层数据:

编码	类型
REDIS_ENCODING_RAW	SDS 实现的动态字符串对象
REDIS_ENCODING_INT	整数实现的动态字符串对象
REDIS_ENCODING_HT	字典实现的 hash 对象
REDIS_ENCODING_ZIPMAP	压缩map实现对对象,(3.0)版本未使用
REDIS_ENCODING_LINKEDLIST	双向链表实现的对象
REDIS_ENCODING_ZIPLIST	压缩列表实现的对象
REDIS_ENCODING_INTSET	整数集合实现的对象
REDIS_ENCODING_SKIPLIST	跳跃表实现的对象
REDIS_ENCODING_EMBSTR	使用 embstr 实现的动态字符串的对象

PS:下文会解释 RAW 和 EMBSTR 的区别。

我就按照类型的顺序看看 Redis 是怎么利用底层的数据结构实现不同的对象类型的。

REDIS_STRING (字符串)

Redis 的字符串 String,主要由 int、raw 和 emstr 底层数据实现的。 Redis 遵循以下的原则来决定使用底层数据结构的使用。

  • 如果数据是可以用 long 表示的整数,那就直接使用将ptr 的类型设置为long。将RedisObject 的 encoding 设置为 REDIS_ENCODING_INT。
  • 如果是一个字符串,那就需要考察字符串的字节数。如果字节数小于 39 就是使用 emstr,encoding 就使用 REDIS_ENCODING_EMBSTR,底层依然是我们之前介绍的 SDS 。
  • 如果字符串的长度超过 39 那就使用 raw,encoding 就是 REDIS_ENCODING_RAW。

问题来了:

  1. 为什么是 39 个字符?
    我们所String对象是由一个 RedisObject 和 sdshdr 组成的。所以我们如下公式在在64位的系统中,一个 emstr 最大占用 64bite。
    RedisObject(16b) + sds header(8b) + emstr + “\0”(1b) <= 64
    简单的 四则运算 emstr <= 39。
  2. 一直都是 39 么?
    在 3.2 的版本的时候,作者对 sdshdr 做了修改,从 39 改成了 44。为什么?
    之前我们说过一个 sdshdr 包含三个参数,len、free 还有 buf,在3.2之前 len 和 free 的数据类型都是 unsigned int。 这个就是为什么上面的公式 sds header 是 8个字节了。新版本的 sdshdr 变成了 sdshdr8, sdshdr16 和 sdshdr32还有 sdshdr64。优化的地方就在于如果 buf 小,使用更小位数的数据类型来描述 len 和 free 减少他们占用的内存,同时增加了一个char flags。emstr使用了最小的 sdshdr8。 这个时候 sds header 就变成了(len(1b) + free(1b) + flags(1b)) 3个字节, 比之前的实现少了5个字节。 所以新版本的 emstr 的最大字节变成了 44。 还是那句话 Redis 对内存真是 “斤斤计较”
  3. SDS 是动态的为什么要区分 emstr 和 raw?
    区别在于生产 raw 的时候,会有两步操作,分别产生 redisObject 和 sdshdr。而 emstr 一次成型,同时生成 redisObject 和 sdshdr 。就是为了高效。同时注意 emstr 是不可变的。
  4. 他们之间是什么关系?
    如果不能用 long 表示的数据,double 也是使用 raw 或者 emstr 来保存的。
    按照 Redis 的套路这三个底层数据在条件满足的是是会发生装换的。REDIS_ENCODING_INT 的数据如果不是整数了,那就会变成 raw 或者 emstr。emstr 发生了变化就会变成 raw。

REDIS_LIST 列表

Reids 的列表,底层是一个 ziplist 或者 linkedlist。

当列表对象保存的字符串元素的长度都小于64字节。
保存的元素数量小于512个。
两个条件都满足使用ziplist编码,两个条件任意一个不满足时,ziplist会变为linkedlist。

3.2 以后使用 quicklist 保存。这个数据结构之前没有讲解过。

实际上 quicklist 是 ziplist 和双向链表结合的产物。我们这样理解,每个双向链表的节点上是一个ziplist。之所以这么设计,应该是空间和时间之间的取舍或者一个折中的方案。 具体的实现我会在以后的文章里面具体分析。

REDIS_SET (集合)

Redis 的集合底层是一个 intset 或者 一个字典(hashtable)。

这个比较容易理解:

当集合都是整数且不超过512个的时候,就使用intset。
剩下都是用字典。
使用字典的时候,字典的每一个 key 就是集合的一个元素,对应的 value 就是一个 null。

REDIS_ZSET (有序集合)

Redis 的有序集合使用 ziplist 或者 skiplist 实现的。

元素小于 128 个
每个元素长度 小于 64 字节。
同时满足以上条件使用ziplist,否则使用skiplist。

对于 ziplist 的实现,redis 使用相邻的两个 entity 分别保存对象以及对象的排序因子。这样对于插入和查询的复杂度都是 O(n) 的。直接看图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oaNn00zx-1573628572107)(media/15663772797131/15663782632938.jpg)]

元素开发工程师,排序的因子就是月薪。(好吧php是世界上最好的语言)。

对于skiplist 的实现:

typedef struct zset{zskiplist *zsl;dict *dict}zset;

skiplist 的有序链表的实现不只是只有一个 skiplist ,还有一个字典存储对象的key 和 排序因子的映射,这个是为了保证按照key 查询的时候时间负责度为 O(1)。同时有序性依赖 skiplist 维护。大家可以看我之前的教程。所以直接看图:

在这里插入图片描述

REDIS_HASH (hash表)

Redis 的 hash 表 使用 ziplist 和 字典 实现的。

键值对的键和值都小于 64 个字节
键值对的数量小于 512。
都满足的时候使用 ziplist,否则使用字典。

ziplist 的实现类似,类似 zset 的实现。两个entity成对出现。一个存储key,另一个存储 velue。

ziplist
还是可以使用上面使用过的图。这个时候 entity 不用排序。key 是职位名称,velue 是对应的月薪。(好吧php还是世界上最好的语言)。与zset实现的区别就是查询是 O(n) 的,插入直接往tail后面插入就行时间复杂度O(1)。

使用字典实现一个 hash表。好像没有什么可以多说的。

int refcount(引用计数器)

这个参数是引用计数。Redis 自己管理内存,所以就使用了最简单的内存管理方式–引用计数。

创建对象的时候计数器为1
每被一个地方引用,计数器加一
每被取消引用,计数器减一
计数器为0的时候,就说明没有地方需要这个对象了。内存就会被 Redis 回收。

unsigned lru:REDIS_LRU_BITS

这个参数记录了对象的最后一次活跃时间。

如果 Redis 开启了淘汰策略,且淘汰的方式是 LRU 的时候,这个参数就派上了用场。Redis 会优先回收 lru 最久的对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/382368.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis源码剖析(六)—— Redis 数据库、键过期的实现

文章目录数据库的实现数据库读写操作键的过期实现数据库的实现 我们先看代码 server.h/redisServer struct redisServer{...//保存 db 的数组redisDb *db;//db 的数量int dbnum;... }再看redisDb的代码&#xff1a; typedef struct redisDb {dict *dict; /*…

linux中错误总结归纳

1.使用gcc编译C文件&#xff0c;C文件在for循环语句中出现变量定义 编译器提示错误&#xff1a;“for”loop initial declarations are only allowed in C99 mode. note:use option -stdc99or-stdgnu99 to compile; 原因&#xff1a;gcc的标准是基于c89的&#xff0c;c89不能在…

makefile内容小结

makefile中每个功能主要分为三部分&#xff1a;目标&#xff0c;依赖条件和命令语句 1.支持对比更新的Makefile写法&#xff08;只会编译文件时.o文件和.c文件时间不一致的文件&#xff09; 2.使用makefile自动变量和自定义变量的makefile写法 其中&#xff1a;这三个符号为ma…

事务隔离级别动图演示

事务的基本要素&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09; 事务开始后所有操作&#xff0c;要么全部做完&#xff0c;要么全部不做&#xff0c;不可能停滞在中间环节。事务执行过程中出错&#xff0c;会回滚到事务开始前的状态&#xff0c;所有的…

linux中的man文档结构

使用命令 man chapter章节号查找的内容

linux文件操作相关函数

&#xff08;1&#xff09;stat函数&#xff1a;显示文件的相关信息&#xff08;类似于 ls -l的感觉&#xff09; 头文件及函数原型&#xff1a; 函数参数:path:文件的路径&#xff0c;buf是指待写入的文件信息&#xff0c;fd:表示文件描述符&#xff1b; stat,fstat,lstat三者…

linux目录操作函数

&#xff08;1&#xff09;chdir函数&#xff1a;修改当前进程的路径 函数头文件及原型&#xff1a; 参数&#xff1a;要修改的文件路径或文件描述符&#xff08;一般是当前空闲最小的&#xff09; 返回值&#xff1a;成功&#xff08;0&#xff09;&#xff0c;失败&#xff0…

linux中的dup和fcntl的用法

&#xff08;1&#xff09;dup函数&#xff1a;文件描述符的拷贝 函数头文件及函数原型&#xff1a; 参数&#xff1a;原来的文件描述符&#xff0c;新的文件描述符&#xff0c;标志位&#xff1b; 返回值&#xff1a;成功&#xff08;返回新的文件描述符&#xff09;失败&…

使用dup2实现重定向到屏幕终端遇到问题

参考博客&#xff1a;https://blog.csdn.net/qq_26093511/article/details/53255970 参考博客&#xff1a;https://blog.csdn.net/db199410/article/details/52335450#commentBox 1.要解决的问题 通过复制文件描述符来实现文件重定向问题。 2.实现代码 2.代码输出 按照原来的…

linux操作系统之exec函数族

当我们想在进程中执行另外一个函数或程序时&#xff0c;可以使用exec函数。进程调用exec函数&#xff0c;则该进程中用户空间所有代码和数据会完全被新程序替换&#xff0c;但是不会创建新进程&#xff0c;因此进程id不会发生改变。 函数族的头文件&#xff1a;unistd.h l:li…

linux操作系统之子进程回收函数wait和waitpid函数小结

一个进程在终止时会关闭所有的文件描述符&#xff0c;释放用户空间分配的内存&#xff0c;但是它的PCB还保留着&#xff0c;内核在其中还保留着进程的一些信息&#xff1a;如果正常终止&#xff0c;则保留着退出状态&#xff1b;如果异常终止则保存着导致进程种植的信号。 在父…

linux操作系统进程间通信IPC之管道pipe及FIFO

linux环境下,各进程相互独立&#xff0c;如果想要交换两个进程之间的数据&#xff0c;需要通过内核&#xff0c;在内存中提供一个缓存区&#xff0c;一个进程往缓存区中写数据&#xff0c;一个往缓存区读数据&#xff0c;内核提供的这种机制称为进程间通信&#xff08;IPC&…

linux操作系统进程间通信IPC之共享存储映射

&#xff08;1&#xff09;文件存储映射I/O&#xff08;Memory-mapped I/O&#xff09; 一个磁盘文件与存储空间中的一个缓存区相对应&#xff0c;这样可以在不适合read/write函数的情况下&#xff0c;使用地址&#xff08;指针&#xff09;完成I/O操作。具体实现通过内核指定一…

linux操作系统之信号

&#xff08;1&#xff09;信号的概念 信号的特点&#xff1a;简单&#xff0c;不能携带大量信息&#xff0c;满足某种特定条件才触发。 信号的机制&#xff1b;“软中断”&#xff0c;通过软件方式实现&#xff0c;具有很强的延时性。每个进程收到的信号&#xff0c;都由内核负…

linux操作系统信号捕捉函数之sigaction用法小结

&#xff08;1&#xff09;sigaction函数&#xff1a;注册一个信号捕捉函数&#xff08;不参与捕捉信号&#xff0c;信号由内核捕捉&#xff09;&#xff0c;并修改原来的信号处理动作 &#xff08;2&#xff09;函数原型及头文件 头文件&#xff1a;#include<signal.h>…

redis源码剖析(十五)——客户端思维导图整理

redis源码剖析&#xff08;十五&#xff09;——客户端执行逻辑结构整理 加载略慢

linux操作系统信号捕捉函数之回调函数小结

&#xff08;1&#xff09;signal 信号捕捉函数&#xff1a;注册一个信号捕捉函数&#xff08;不参与捕捉&#xff0c;那是内核的事情&#xff09; 函数实现&#xff1a; typedef void(*sighandler_t)(int); //声明了一个函数指针&#xff08;代表着一类函数&#xff1a;参…

linux操作系统之竞态条件(时序竞态)

&#xff08;1&#xff09;时序竞态&#xff1a;前后两次运行同一个程序&#xff0c;出现的结果不同。 &#xff08;2&#xff09;pause函数&#xff1a;使用该函数会造成进程主动挂起&#xff0c;并等待信号唤醒&#xff0c;调用该系统调用的进程会处于阻塞状态&#xff08;主…

linux操作系统之全局异步IO及可重入/不可重入函数

&#xff08;1&#xff09;全局变量异步I/O实现父子进程交替数数 1&#xff09;信号捕捉函数 2&#xff09;main函数实现信号交替 3&#xff09;程序实现 1》创建子进程&#xff0c;父进程等待1s&#xff0c;等待子进程完成捕捉函数注册&#xff08;捕捉信号SIGUSR1&#xff09…