Redis读书笔记(一)
Redis数据结构
1 简单动态字符串
Simple dynamic string 的实现
// sds.h/sdshdr
struct sdshdr {
int len; //记录buf数组中已使用的字节数, 不包括结尾空字符'\0'
int free; //记录buf数组中未使用的字节数
char buf[]; //字节数组, 保存字符串
};
简单动态字符串SDS与C字符串的区别
- 获取字符串长度的时间复杂度
- C字符串: O(N), 需要遍历字符串
- SDS: O(1)
- 缓存区溢出问题
- C字符串修改时需要为目标字符串分配足够的内存,否则会产生缓存区溢出
- SDS需要修改时,会检查SDS的 free 空间是否满足要求,如果不满足会自动扩展空间
- 减少内存重分配次数
- C字符串每修改一次都需要重新分配内存
- SDS通过空间预分配和惰性空间释放优化内存分配次数
空间预分配
- 如果SDS修改之后的长度(len)小于1MB,那么程序分配同样大小的未使用空间(free)
- 如果SDS修改之后的长度(len)大于等于1MB,那么程序会分配1MB的未使用空间(free)
惰性空间释放
- 当SDS缩短时,程序并不会立即使用内存重分配回收缩短的字节,而是使用 free 属性将这些字节的数量记录
- 需要时可以使用sdsfree释放未使用空间,所以不会造成内存浪费
2 链表
应用场景:列表键(List)的底层实现之一、发布与订阅、慢查询、监视器等。
链表的实现
// 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;
Redis链表特点
- 双端链表
- 无环:头节点的prev和尾节点的next指针都指向NULL
- 链表节点使用 void* 保存节点值,所以链表可以保存不同类型的值
3 字典
应用场景:哈希键(Hash)的底层实现之一,Redis数据库。
字典的实现
//哈希表
typedef struct dictht {
dictEntry **table; //哈希表数组
unsigned long size; //哈希表大小
unsigned long sizemask; //总等于size-1, 用于计算索引值
unsigned long used; //已有节点数量
} dictht;
//哈希表节点
typedef struct dictEntry {
// 键
void *key;
//值
union{
void *val;
uint64_t u64;
int64_t s64;
} v;
struct dictEntry *next; //指向下一个哈希表节点,形成链表
} dictEntry;
//dictType
typedef struct dictType {
//计算哈希值
unsigned int *hashFunction(const void *key);
//复制键的函数
void **keyDup(void *privdata, const void *key);
//...
} dictType;
//Redis字典结构
typedef struct dict {
dictType *type; //保存了一簇用于操作特定类型键值对的函数
void *privdata; //需要传给特定类型函数的可选参数
dictht ht[2]; //哈希表
int rehashidx; //rehash索引
}
哈希冲突
当有两个或以上数量的键被分配到哈希表数组的同一个索引上面时,则称发生了冲突。
Redis的哈希表使用链地址法解决键冲突,每个哈希表节点都有一个next指针,多个被分配到同一索引的节点形成一个单向链表。出于速度考虑,程序总数将新节点添加到链表表头的位置,时间复杂度O(1)。
Rehash重新散列
为了让哈希表的负载因子维持在一个合理的范围内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩(通过rehash操作实现)。
\]
Rehash操作步骤
为字典ht[1]哈希表分配空间,大小取决于要执行的操作和ht[0].used属性值。
- 如果是扩展操作,ht[1]的大小为第一个大于等于ht[0].used*2的\(2^n\) (2的n次方幂)
- 如果是收缩操作,ht[1]的大小为第一个大于等于ht[0].used的\(2^n\)
将保存在ht[0]的所有键值对rehash到ht[1]上面:重新计算key的哈希值和索引值,然后将键值对放到ht[1]对应的位置上(这个过程是渐进式的,不是一步完成)。
当ht[0]包含的所有键值对都迁移到ht[1]后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],并新创建一个空白哈希表,为下一次rehash做准备。
渐进式Rehash期间的哈希表操作
- 删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行。
- 新增(add)只会在ht[1]哈希表上进行,ht[0]不再进行任何添加操作。
4 跳跃表
应用场景:有序集合键(zset)底层实现之一、集群节点用作内部数据结构。
跳跃表的实现
//跳跃表节点
typedef struct zskiplistNode {
//层,每个层带有两个属性:前进指针和跨度
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span;
} level[];
//后退指针
struct zskiplistNode *backward;
//分值,节点按分值从小到达排列
double score;
//成员对象
robj *pbj;
} zskiplistNode;
//跳跃表
typedef struct zskiplist {
/**
* header、tail: 头尾节点
* length: 表中节点的数量(不包括头节点)
* level: 层数最大节点的层数(不包括头节点)
*/
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
}
特性:跳跃表中的节点按照score大小进行排序,当score相同时,节点按照成员对象的大小进行排序。
5 整数集合
应用场景:集合键的底层实现之一(Set)。
- 当一个集合只包含整数值元素,且这个集合元素数量不多时,Redis会使用整数集合作为集合键的底层实现。
整数集合的实现
// intset.h/intset结构
typedef struct intset {
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//集合元素数组
int8_t contents[];
}
整数集合特性
- contents数组的真正类型取决于encoding属性值。如果encoding属性的值为INTSET_ENC_INT16,则contents就是一个int16_t类型的数组;如果encoding属性的值为INTSET_ENC_INT32,则contents就是一个int32_t类型的数组等待。
- 当新元素添加到整数集合时,并且新元素的类型比整数集合现有元素都要长,整数集合需要先升级upgrade。
整数集合升级步骤
- 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间
- 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放到正确的位置上
- 将新元素添加至数组
6 压缩列表
应用场景:列表键(List)和哈希键(Hash)的底层实现之一。
- 当一个列表键只包含少量列表项,并且每个列表项要么是小整数值,要么就是短字符串,那么Redis会使用ziplist来做列表键的底层实现。
- 当一个哈希键只包含少量键值对,并且每个键值对的key和value都是小整数值或短字符串,那么Redis会使用ziplist来做哈希键的底层实现。
压缩列表的构成
属性 | 类型 | 长度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4字节 | 记录整个压缩列表占用的内存字节数。 |
zltail | uint32_t | 4字节 | 记录压缩列表表尾节点距离起始地址有多少字节。 |
zllen | uint16_t | 2字节 | 记录整个压缩列表包含的节点数量: 当属性值小于65535时,表示的就是整个压缩列表节点的数量; 当属性值等于65535时,节点的真实数量需要遍历才能得到。 |
entry1 | 列表节点 | 不定 | 压缩列表包含的各个节点,节点的长度由保存的内容决定。 |
entry2 | |||
... | |||
entryN | |||
zlend | uint8_t | 1字节 | 特殊值0xFF,用于标记压缩列表的末端。 |
压缩列表节点Entry的构成
属性 | 长度 | 用途 |
---|---|---|
previous_entry_length | 1字节或5字节 | 记录了压缩列表前一个节点的长度。 |
encoding | 1字节、2字节或5字节 | 记录了当前节点的content属性所保存数据的类型以及长度。 |
content | 由encoding决定 | 保存节点的值,可以是字节数组或者整数。 |
压缩列表特性
- 添加新节点或者删除旧节点,可能会引发连锁更新操作,但是出现的几率非常低。
Redis读书笔记(一)的更多相关文章
- [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串
本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...
- redis读书笔记
1.redis两种存储机制(持久化) Redis的存储机制分为:Snapshot和AOF 都先将内存存储在内存中. (1)Snapshot当数据累计到一定的阈值,就会触发dump将数据一次性写入到数据 ...
- [REDIS 读书笔记]第一部分 数据结构与对象 跳跃表
下面是跳跃表的基本原理,REDIS的实现大致相同 跳跃表的一个特点是,插入NODE是通过随机的方式来决定level的,比较奇特 下面是skipList的一个介绍,转载来的,源地址:http://ken ...
- [redis读书笔记] 第二部分 集群
1. 一个集群会包含多个节点(一个节点就是一个reid是服务器),CLUST MEET <ip><port>可以添加一个node到集群,命令执行后,两个node之间就会进行握手 ...
- [redis读书笔记] 第三部分 多机数据库的实现 复制
另外一篇写的很好很深入的文章:http://www.tuicool.com/articles/fAnYFb : RDB持久化 http://www.tuicool.com/articles/F3Eri ...
- [redis读书笔记] 第二部分 单机数据库 RDB持久化
内存中的rdb是会存为文件以做到RDB持久化的.RDB文件时一个二进制文件. 一 载入与存储 文件的载入是在server启动时进行的(rdbload()),因为AOF的更新频率比RDB高,所以如果AO ...
- [redis读书笔记] 第二部分 单机数据库 数据库实现
一 数据库基本实现/命令下发的实现 redis.c里,大家能看到redisCommandTable[] 的实现,列出了支持的所有命令.大部分的入参为redisClient *c,当一条REDIS命令下 ...
- [redis读书笔记] 第一部分 数据结构与对象 对象特性
一 类型检查和多态 类型检查,即有的命令是只针对特定类型的,如果类型不对,就会报错,此处的类型,是指的键类型,即robj.type.下面为有类型检查的命令: 对于某一种类型,redis下底层的实 ...
- [redis读书笔记] 第一部分 数据结构与对象 对象类型
- 从前面redis的基本数据结构来看,可以看出,redis都是在基本结构(string)的基础上,封装了一层统计的结构(SDS),这样让对基本结构的访问能够更快更准确,提高可控制度. - redis ...
- [redis读书笔记] 第一部分 数据结构与对象 字典
三 字典 字典是Hash对象的底层实现,比如用HSET创建一个HASH的对象,底层可能就是用一个字典实现的键值对. 字典的实现主要设计下面三个结构: /* * 哈希表节点 */ typedef str ...
随机推荐
- java学习问题
1.nacos Connection refused: connect 由于配置文件配置错误引起的.我的nacos是部署在另一台linux服务器的,yml具体配置如下:
- MD5加密汇总
1 #region MD5 2 /// <summary> 3 /// 16位MD5加密 4 /// </summary> 5 /// <param name=" ...
- jmeter的三种参数化方式
一.通过添加前置处理器(用户参数) 1. 在http层级下添加--前置处理器--用户参数 2.可以修改名称,每次迭代更新一次(一定要勾选上),这样才会每次迭代变量值也更新 ,点击下面添加用户(多次测试 ...
- OSIDP-单处理器调度-09
处理器调度的类型 处理器调度的目的是为了满足系统的目标,将进程分配到处理器上执行. 系统并发度:正等待处理器处理的进程个数.(这里的表述和08里面的不同,以这里为准.主要是懒得改,见谅= =) 长程调 ...
- 通过前端导出excel表格
1. 在前端HTML上绘制想要导出的表格(包含后端获取的数据) <div class="exportExcel" id="exportOutTable" ...
- Navicat 通过ssh链接远程数据库
首先需要下载一个Navicat数据库管理工具,有了Navicat工具需要完成一下步骤就可以实现本地链接远程数据库了 一.打开Navicat,点击连接按钮,找到MySQL并点击 二.点击"常规 ...
- Hadoop之HDFS优缺点、设计原理、框架
如需大数据开发整套视频(hadoop\hive\hbase\flume\sqoop\kafka\zookeeper\presto\spark):请联系QQ:1974983704 Hadoop的前世今 ...
- 转帖:弹性布局(display:flex;)属性详解
它之所以被称为 Flexbox ,是因为它能够扩展和收缩 flex 容器内的元素,以最大限度地填充可用空间.与以前布局方式(如 table 布局和浮动元素内嵌块元素)相比,Flexbox 是一个更强大 ...
- 2019徐州网络赛 M Longest subsequence 序列自动机
题目链接https://nanti.jisuanke.com/t/41395 题意:给两个字符串,s和t,在s中求字典序严格大于t的最长子序列. 思路:分类讨论即可.先建个s的序列自动机. 1 如果有 ...
- 灵感宝盒图谱全新改版!代码实验室开启报名丨RTE NG-Lab 双周报
前言 RTE NG-Lab 计划已经推出一段时间了,计划目前包含灵感宝盒(Idea Box).代码实验室(Code Lab).独立开发者孵化器(NGLab Incubator)三个项目.我们希望借助这 ...