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,一经查实,立即删除!

相关文章

函数sscanf小结

1.sscanf用于处理固定格式的字符串&#xff0c;包含在头文件<cstdio>中&#xff0c;函数原型为&#xff1a; int sscanf(const char *buffer,const char*format,[]argument ]...); 其中:buffer代表着要存储的数据&#xff0c;format 代表格式控制字符串&#xff0c;arg…

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

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

多益网络 视频面试面试总结20180816

1.首先是自我介绍&#xff1a;因为等了半个小时&#xff0c;所以有点儿紧张&#xff0c;只说了一下自己的学校&#xff0c;爱好和兴趣&#xff1b; 2.介绍了一个自己的最成功的项目&#xff1a;我介绍了一个关于GPS导航的项目&#xff0c;介绍了项目的内容和项目的一些工作&am…

redis源码剖析(七)—— Redis 数据结构dict.c

文章目录dict.hdict.cdict.h //定义错误相关的码 #define DICT_OK 0 #define DICT_ERR 1//实际存放数据的地方 typedef struct dictEntry {void *key;void *val;struct dictEntry *next; } dictEntry;//哈希表的定义 typedef struct dict {//指向实际的哈希表记录(用数组开链的…

简述linux中动态库和静态库的制作调用流程

假设现在有这些文件&#xff1a;sub.c add.c div.c mul.c mainc head.h&#xff08;前4个.C文件的头文件&#xff09; 1.静态库制作流程 gcc -c sub.c add.c div.c mul.c -->生成 .o目标文件文件 ar rcs libmycal.a *.o …

redis源码剖析(八)—— 当你启动Redis的时候,Redis做了什么

文章目录启动过程初始化server结构体main函数会调用initServer函数初始化服务器状态载入持久化文件&#xff0c;还原数据库开始监听事件流程图启动过程 初始化server结构体从配置文件夹在加载参数初始化服务器载入持久化文件开始监听事件 初始化server结构体 服务器的运行ID…

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不能在…

redis源码剖析(十一)—— Redis字符串相关函数实现

文章目录初始化字符串字符串基本操作字符串拼接操作other获取指定范围里的字符串将字符串中的所有字符均转为小写的形式将字符串中所有字符均转为大写的形式字符串比较other#define SDS_ABORT_ON_OOM#include "sds.h" #include <stdio.h> #include <stdlib.…

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;所有的…

C/C++的优点和缺点

1.C/C语言的优点 C语言是面向过程的语言&#xff0c;常用来编写操作系统。C语言是从C语言发展过来的&#xff0c;是一门面向对象的语言&#xff0c;它继承了C语言的优势&#xff0c;同时也添加了三个主要的内容&#xff1a;Oriented-Object class,Template,STL. 1)C/C可以潜入…

C/C++命令行参数那点事

int main(int argc, char *argv[ ]); 1.命令行参数&#xff1a;在命令行中给定的参数&#xff1b; 2.命令行参数在对函数main的调用时&#xff0c;主要有两个参数送到main,一个是argc(argument count),命令行参数的个数&#xff0c;另外一个是argv,命令行参数的数组,命令行参…

mysql row_id为什么是6字节?为什么是8字节

mysql row_id是几个字节&#xff1f; row_id InnoDB表中在没有默认主键的情况下会生成一个6字节空间的自动增长主键 row_id是整型还是字符型&#xff1f; 源代码中 row_id 是 ib_uint64_t 这是 8字节 uint64_t 是整形 为什么是6个字节&#xff1f; P.S. Base64编码说明 B…

linux中的man文档结构

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

伪随机数和真随机数

伪随机数小项目 猜数字游戏 //C语言 猜数字游戏 https://blog.csdn.net/csdn_kou/article/details/79785709 C语言之随机数生成超详解 https://blog.csdn.net/csdn_kou/article/details/79788815 在上面的文章中&#xff0c;使用固定函数就一直是生成固定的随机结果&#…

linux中的IO函数

1)open函数&#xff1a;以特定的方式打开一个文件&#xff1b; 头文件&#xff1a;sys/type.h sys/stat.h fcntl.h 返回值&#xff1a;错误则返回-1&#xff0c;正确则返回文件描述符&#xff08;int类型&#xff0c;范围为3~1023,文件的标号&#xff09; 函数原型&#xff…

ps -ef和ps aux

ps -ef和ps aux ps -ef unix风格 -e 列出所有进程 -f 完整格式 UID PID PPID C STIME TTY TIME CMD root 1 0 0 8月27 ? 00:25:08 /usr/lib/systemd/systemd --switched-root --system --deserialize 22 root 2 0 0 8月…

Linux中screen的用法

screen 查看当前有多少窗口 [rootpython ~]# screen -ls There are screens on:20706.khz (Attached)20679.khz (Attached)20453.khz (Attached)20143.khz (Detached)16993.pts-2.python (Attached) 5 Sockets in /var/run/screen/S-root.新建一…

linux文件操作相关函数

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

linux查看硬盘是不是ssd固态硬盘

linux查看硬盘是不是ssd固态硬盘 sdb是ssd、sr0是SATA [root 01 ~]# cat /sys/block/sdb/queue/rotational 0 [root 01 ~]# cat /sys/block/sr0/queue/rotational 1