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 ...
随机推荐
- 微信小程序 css overflow :hidden 子元素不生效
原css .item .right { width: 70%; } .item .right .name { font-size: 32rpx; font-family ...
- 生成Funnel漏斗图
-----------第一步----------- import jsonlist_p = [{"action":"浏览商品","PV":5 ...
- 关于css在html的三种使用方式
关于css在html的三种使用方式 1.内联样式(直接在html里面使用style) eg:<h1 style="color:skyblue">这是一个测试标题< ...
- 3.1命名空间的using声明
目前为止,我们用到的库函数基本上都属于命名空间std,而程序也显式地将这一点标示了出来.列如,std::cin表示从标准输入中读取内容.此处使用作用域操作符(::)的含义是:编译器应从操作符左侧名字所 ...
- springBoot的全局异常处理
GlobalException.java package com.bank.util; import com.bank.exception.ContentEmpyException; import c ...
- unity 开发小技巧之C#篇 ----持续更新
1.ToString()方法是可以重写的 2.Start()方法可以主动调用 3.接口可以直接使用 jiekou?.Invoke() 来调用所有继承该接口的方法 4.Json 列表和服务器需要的数据不 ...
- adb命令1
adb是什么 adb的全称为Android Debug Bridge,就是起到调试桥的作用.它就是一个命令行窗口,用于通过电脑端与模拟器或者是设备之间的交互. adb有什么用 借助adb工具,我们可以 ...
- 在Vim编辑器中查找选定文本
按照任意选定文本查找: 1. 进入visiual模式选定文本, 按 y 键复制: 2. 按 / 键进入查找模式: 3. 按 Ctrl + r 打开vim寄存器: 4. 按 " 键将寄存器内容 ...
- webgl 系列 —— 绘制猫
其他章节请看: webgl 系列 绘制猫 上文我们了解了如何绘制渐变彩色三角形,明白了图形装配.光栅化,以及片元着色器计算片元的颜色. 现在如果让你绘制如下一只猫.难道绘制很多三角形,然后指定它们的颜 ...
- 大数据面试——HDFS
一.Hadoop1.0 与 Hadoop2.0的区别