Redis | 第一部分:数据结构与对象 上篇《Redis设计与实现》
前言
参考资料:《Redis设计与实现 第二版》;
本篇笔记按照书里的脉络,将知识点分为四个部分。其中第一部分数据结构与对象分为上中下篇,上篇包括:SDS、链表和字典;中篇包括跳跃表、整数集合和压缩列表;下篇为对象;
1. 简单动态字符串
- Redis构建一种名为简单动态字符串(SDS)的抽象类型,并将SDS作为Redis的默认字符串表示;
- 除了用作字符串值外,SDS还被用作缓冲区buffer,如:AOF模块中的AOF缓冲区、客户端状态中的输入缓冲区;
1.1 SDS的定义
- SDS的定义在
sds.h/sdshdr结构:struct sdshdr {
//记录buf数组中已使用字节数量
//等于SDS所保存字符串长度
int len; //记录buf数组中未使用字节数量
int free; //字节数组,保存字符串
char buf[];
}

1.2 空间预分配与惰性空间释放
- SDS相比C字符串的优点:
- 在常数时间复杂度内获取字符串长度;
- 杜绝缓冲区溢出(空间预分配);
- 减少修改字符串时带来的内存重分配次数(空间预分配);
- 可保存二进制(使用len值判断字符串是否结束而不是
\0); - 兼容部分C字符串函数(在字符串末尾保留空字符
\0)
- 通过未使用空间
free,SDS实现空间预分配和惰性空间释放:- 空间预分配:用于SDS字符串增长。修改后小于1MB,则 len = free;反之分配1MB额外空间;
- 惰性空间释放:用于SDS字符串缩短。即在有需要时才回收内存;
1.3 SDS的API
| 函数 | 作用 | 时间复杂度 |
|---|---|---|
| sdsnew | 创建一个包含给定C字符串的SDS | O(N),N为给定C字符串的长度 |
| sdsempty | 创建一个不包含任何内容的空SDS | O(1) |
| sdsfree | 释放给定的SDS | O(N),N为被释放SDS的长度 |
| sdslen | 返回SDS的已使用空间字节数 | O(1),通过读取SDS的len属性获得 |
| sdsavail | 返回SDS的未使用空间字节数 | O(1),通过读取free属性获得 |
| sdsdup | 创建一个给定SDS的副本(copy) | O(N),N为给定SDS的长度 |
| sdsclear | 清空SDS保存的字符串内容 | O(1),因为惰性空间释放策略 |
| sdscat | 将给定C字符串拼接到SDS字符串的末尾 | O(N),N为被拼接字符串的长度 |
| sdscatsds | 将给定SDS字符串拼接到另一个SDS字符串的末尾 | O(N),N为被拼接SDS字符串的长度 |
| sdscpy | 将给定的C字符串复制到SDS里面,覆盖原有字符串 | O(N),N为被复制的C字符串长度 |
| sdsgrowzero | 用空字符串将SDS扩展至给定长度 | O(N),N为扩展新增的字节数 |
| sdsrange | 保留SDS给定区间内的数据,不在区间内的数据会被覆盖或清除 | O(N),N为扩展新增的字节数 |
| sdstrim | 接受一个SDS和一个C字符串作为参数,从SDS中移除所有在C字符串出现过的字符 | O(N2),N为给定C字符串的长度 |
| sdscmp | 对比两个SDS字符串是否相同 | O(N),N为两个SDS中较短的那个SDS的长度 |
2. 链表
- C语言没有内置链表,所以Redis构建自己的链表;
- 链表在Redis里的应用:发布与订阅、慢查询、监视器、Redis服务器保存多个客户端、列表键底层等、构建客户端输出缓冲区(output buffer);
2.1 链表与节点的定义
链表节点的定义与实现在
adlist.h/listNode结构里;typedef struct listNode {
//前置节点
struct listNode *prev;
//后置节点
struct listNode *next;
//节点的值
void *value;
} listNode;
链表的定义在
adlist.h/list中:typedef struct list {
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//链表所包含的节点数量
unsigned long len;
//节点值复制函数
void *(*dup)(void *ptr);
//节点值释放函数
void (*free)(void *ptr);
//节点值对比函数
int (*match)(void *ptr, void *key);
} list;

2.2 链表的API
| 函数 | 作用 | 时间复杂度 |
|---|---|---|
| listSetDupMethod | 将给定的函数设置为链表的节点值复制函数 | O(1),复制函数可以通过链表的dup属性直接获得 |
| listGetDupMethod | 返回链表当前正在使用的节点值复制函数 | O(1) |
| listSetFreeMethod | 将给定的函数设置为链表的节点值释放函数 | O(1),释放函数可以通过链表的free属性直接获得 |
| listGetFree | 返回链表当前正在使用的节点值释放函数 | O(1) |
| listSetMatchMethod | 将给定的函数设置为链表的节点值对比函数 | O(1),对比函数可以通过链表的match属性直接获得 |
| listGetMatchMethod | 返回链表当前正在使用的节点值对比函数 | O(1) |
| listLength | 返回链表的长度 | O(1),链表长度可以通过链表的len属性直接获得 |
| listFirst | 返回链表的表头节点 | O(1),表头节点可以通过链表的head属性直接获得 |
| listLast | 返回链表的表尾节点 | O(1),表尾节点可以通过链表的tail属性直接获得 |
| listPrevNode | 返回给定节点的前置节点 | O(1),前置节点可以通过节点的prev属性直接获得 |
| listNextNode | 返回给定节点的后置节点 | O(1),前置节点可以通过节点的next属性直接获得 |
| listNodeValue | 返回给定节点的目前正在保存的值 | O(1),节点值可以通过节点的value属性直接获得 |
| listCreate | 创建一个不包含任何节点的新链表 | O(1) |
| listAddNodeHead | 将一个包含给定值的新节点添加到给定链表的表头 | O(1) |
| listAddNodeTail | 将一个包含给定值的新节点添加到给定链表的表尾 | O(1) |
| listInsertNode | 将一个包含给定值的新节点添加到给定节点的之前或之后 | O(1) |
| listSearchKey | 查找并返回链表中包含给定值的节点 | O(N),N为链表长度 |
| listIndex | 返回链表在给定索引上的节点 | O(N),N为链表长度 |
| listDelNode | 从链表中删除给定节点 | O(N),N为链表长度 |
| listRotate | 将链表的表尾节点弹出,然后将被弹出的节点插入到链表的表头,成为新的表头节点 | O(1) |
| listDup | 复制一个给定链表的副本 | O(N),N为链表长度 |
| listRelease | 释放给定链表,以及链表中的所有节点 | O(N),N为链表长度 |
3. 字典
- 字典,又称符号表、关联数组、映射,用于保存键值对;
- Redis自己构建字典;
- 字典在Redis里的应用:Redis数据库底层、哈希键的底层实现等;
- Redis的字典使用哈希表作为底层实现;
3.1 哈希表与哈希节点
字典所使用的哈希表的定义,在
dict.h/dictht结构中:typedef struct dictht {
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size; //哈希表大小掩码,用于计算索引值
//总是等于size-1
unsigned long sizemask; //该哈希表已有节点的数量
unsigned long used; } dictht;
table是一个数组,数组的每个元素都是指向dict.h/dictEntry结构的指针;
哈希表节点的定义,在
dict.h/dictEntry结构;typedef struct dictEntry {
//键
void *key;
//值
union{
void *val;
uint64_t u64;
int64_t s64;
} v;
//指向下个哈希表节点,形成链表
struct dictEntry *next;
} dictEntry;
- next值的作用:将多个哈希值相同的键值对连接,解决键冲突问题(collision);

3.2 字典
字典的定义,在
dict.h/dict结构:typedef struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash 索引
//当 rehash 不在进行时,值为-1
int trehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;
- type和privdata属性:针对不同类型键值对,为创建多态字典而设置;
- type属性:是一个指向dictType的指针,Redis为用途不同的字典设置不同的dictType结构体,进而设置不同的类型特定函数;
- privdata属性:保存了需要传给类型特定函数的可选参数;
- ht[2]属性:每一项是dictht哈希表,一般字典只用ht[0]哈希表。对ht[0]进行rehash时使用ht[1];
- trehashidx属性:记录当前rehash的进度;

3.3 哈希算法
Redis计算哈希值与索引值的方法:
# 使用字典设置哈希函数,计算key的哈希值
hash = dict -> type -> hashFunction(key) # 使用哈希表的sizemask属性和哈希值,计算索引值
# 根据情况不同,ht[x]可以是ht[0]或者ht[1]
index = hash & dict -> ht[x].sizemask
当字典被用作数据库底层实现,或哈希键底层实现时,Redis使用
MurmurHash2算法计算键的哈希值;
3.4 解决键冲突
- 键冲突:有两个或以上的键被分配到哈希表数组的同一个索引;
- Redis使用链地址法解决键冲突问题;
- 链地址法:
dictEntry哈希节点里有个next属性,可以用其将索引值相同的节点连成链表;- 出于速度考虑,将新节点添加到链表表头,O(1);
3.5 rehash
- 通过执行rehash(重新散列)来扩展和收缩哈希表;
- rehash的步骤:
- 1)为
ht[1]分配空间,若扩展,则ht[1].size为第一个大于等于ht[0].used*2的 2n。若收缩,则ht[1].size为第一个大于等于ht[0].used的 2n; - 2)将
ht[0]中的所有键值对rehash到ht[1]上; - 3)迁移完后,释放
ht[0],将ht[1]设置为ht[0],创建一个空白哈希表ht[1];
- 1)为
- 哈希表扩展与收缩的时机:
- 负载因子的计算:
load_factor = ht[0].used / ht[0].size; - 扩展:服务器没有执行
BGSAVE和BGREWRITEAOF命令,并且负载因子大于等于1; - 扩展:服务器正在执行
BGSAVE和BGREWRITEAOF命令,并且负载因子大于等于5;- 避免在执行该命令(子进程存在期间)时进行扩展操作,避免不必要的内存写入操作;
- 收缩:负载因子小于0.1;
- 负载因子的计算:

3.6 渐进式rehash
- 当键值对成万上亿时,需要分多次、渐进式完成rehash;
- 渐进式rehash的步骤:
- 1)为
ht[1]分配空间; - 2)将字典的索引计数器变量
rehashidx设置为0,表示rehash正式开始; - 3)rehash期间,每个对字典操作完成后,将
rehashidx++; - 4)当
ht[0]中的所有键值对rehash到ht[1]后,rehashidx设置为 -1;
- 1)为
- 渐进式hash期间:
- 查找操作先查
ht[0],再查ht[1]; - 新增操作只在
ht[1]新增,保证ht[0]只减不增;
- 查找操作先查

3.7 字典的API
| 函数 | 作用 | 时间复杂度 |
|---|---|---|
| dictCreate | 创建一个新字典 | O(1) |
| dictAdd | 将给定的键值对添加到字典里 | O(1) |
| dictReplace | 将给定键值对添加到字典里,如果键已存在,则会用新值替换旧值 | O(1) |
| dictFetchValue | 返回给定键的值 | O(1) |
| dictGetRandomKey | 从字典中随机返回一个键值对 | O(1) |
| dictDelete | 从字典中删除给定键所对应的键值对 | O(1) |
| dictRelease | 释放字典,以及字典包含的键值对 | O(N),N为字典包含的键值对数量 |
最后
新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

Redis | 第一部分:数据结构与对象 上篇《Redis设计与实现》的更多相关文章
- [redis读书笔记] 第一部分 数据结构与对象 对象类型
- 从前面redis的基本数据结构来看,可以看出,redis都是在基本结构(string)的基础上,封装了一层统计的结构(SDS),这样让对基本结构的访问能够更快更准确,提高可控制度. - redis ...
- Redis笔记(1)数据结构与对象
1.前言 此系列博客记录redis设计与实现一书的笔记,提取书本中的知识点,省略相关说明,方便查阅. 2.基本数据结构 2.1 简单动态字符串SDS(simple dynamic string) 结构 ...
- Redis 的底层数据结构(对象)
目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层.最常用的数据结构,相信你也掌握的不错. 但 redis 实际存储键值对的时候,是基于对 ...
- 左手Mongodb右手Redis 第一章,进入Mongodb和Redis的世界
---恢复内容开始--- 1,为什么要使用非关系型数据库,关系型数据库咋滴,不能用嘛? 存在即合理,非关系型数据库的出现,那说明关系型数据库不适用了. 非关系型数据库(NOSQL)-->Not ...
- .net core工具组件系列之Redis—— 第一篇:Windows环境配置Redis(5.x以上版本)以及部署为Windows服务
Cygwin工具编译Redis Redis6.x版本是未编译版本(官方很调皮,所以没办法,咱只好帮他们编译一下了),所以咱们先下载一个Cygwin,用它来对Redis进行编译. Cygwin下载地址: ...
- [REDIS 读书笔记]第一部分 数据结构与对象 跳跃表
下面是跳跃表的基本原理,REDIS的实现大致相同 跳跃表的一个特点是,插入NODE是通过随机的方式来决定level的,比较奇特 下面是skipList的一个介绍,转载来的,源地址:http://ken ...
- [redis读书笔记] 第一部分 数据结构与对象 对象特性
一 类型检查和多态 类型检查,即有的命令是只针对特定类型的,如果类型不对,就会报错,此处的类型,是指的键类型,即robj.type.下面为有类型检查的命令: 对于某一种类型,redis下底层的实 ...
- [redis读书笔记] 第一部分 数据结构与对象 字典
三 字典 字典是Hash对象的底层实现,比如用HSET创建一个HASH的对象,底层可能就是用一个字典实现的键值对. 字典的实现主要设计下面三个结构: /* * 哈希表节点 */ typedef str ...
- [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串
本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...
随机推荐
- kubelet源码分析——监控Pod变更
前言 前文介绍Pod无论是启动时还是关闭时,处理是由kubelet的主循环syncLoop开始执行逻辑,而syncLoop的入参是一条传递变更Pod的通道,显然syncLoop往后的逻辑属于消费者一方 ...
- uniapp内嵌H5页面和uniapp页面相互传值
最近项目有一个需求 -- 做一个百人抽奖的模块,要求展示百人的头像并且不断变化排列组合 先展示一部分的用户头像,然后每增加一个用户就增加一个头像在百人排列里面 我整一个gif图来展示一下 大概就是这种 ...
- keepalived 安装和配置解析
Keepalived的特性 配置文件简单:配置文件比较简单,可通过简单配置实现高可用功能 稳定性强:keepalived是一个类似于layer3, 4 & 7交换机制的软件,具 ...
- 12种 vo2dto 方法,就 BeanUtils.copyProperties 压测最拉胯!【快双11了,别用错喽】
作者:小傅哥 博客:https://bugstack.cn 原文:https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw 沉淀.分享.成长,让自己和他人都能 ...
- ApsNetCore打造一个“最安全”的api接口
Authentication,Authorization 如果公司交给你一个任务让你写一个api接口,那么我们应该如何设计这个api接口来保证这个接口是对外看起来"高大上",&qu ...
- SphereEx 获数百万美元天使融资,接力 ShardingSphere 开启 Database Plus 新篇章
5月14日,数据前沿技术领域初创公司 SphereEx 获得来自红杉中国种子基金.初心资本的数百万美元天使轮融资. SphereEx是一家致力于构建新型分布式数据基础设施的公司,秉承开源.共享.生态. ...
- 2021-06-27 & 2021-06-28 集训题解
西克 题目传送门 Description Solution 跟 2021年省选A卷D2T1 一模一样,懒得讲了 不过这个题似乎有点卡空间,所以卡不过去 Code #include <bits/s ...
- 洛谷3320 SDOI2015寻宝游戏(set+dfs序)(反向迭代器的注意事项!)
被\(STL\)坑害了一个晚上,真的菜的没救了啊. 准确的说是一个叫\(reverse\ iterator\)的东西,就是我们经常用的\(rbegin()\) 有一个非常重要的性质 在反向迭代器中,+ ...
- pycharm安装pika提示CondaHTTPError: HTTP 000 CONNECTION FAILED for url <https://repo.anaconda.com>
1. 问题描述: pycharm安装第三方库时提示CondaHTTPError: HTTP 000 CONNECTION FAILED. 2. 错误原因:默认镜像源访问速度过慢,会导致超时从而导致更新 ...
- JBOSS未授权访问漏洞利用
1. 环境搭建 https://www.cnblogs.com/chengNo1/p/14297387.html 搭建好vulhub平台后 进入对应漏洞目录 cd vulhub/jboss/CVE-2 ...