1、文档介绍html
本文仅做为本人读书笔记使用,不对其中内容作解释,记录以本人能够看懂为标准redis
原书连接:https://redisbook.readthedocs.io/en/latest/index.html算法
该书以简明的方式主要介绍了Redis内部的运行机制,从数据结构到服务器构造,值得推荐数据库
第一部分 内部数据结构数组
Redis内存所使用的数据结构与算法缓存
1、SDS(Simple Dynamic String) 简单动态字符串安全
1. SDS做用服务器
2. Redis中的字符串数据结构
Redis内的字符串不只包含\0结尾的字符串,还包含简单的字节数组,还包括其余格式的数据等内容app
Redi使用SDS类替换C语言默认字符串考虑到两点:高效、二进制安全(程序不对字符串里面保存的数据进行任何假设)
3. SDS实现
typedef char * sds;
struct sdshdr { len = 11; free = 0; buf = "hello world\0"; // buf 的实际长度为 len + 1 };
sds 是 char* 的一个别名
结构体里包含了三个属性:len、free、buffer三个属性
经过len属性能够O(1)的进行长度计算;经过buf分配额外的空间,并使用free记录未使用空间的大小,sds可让执行追加操做所需的内存重分配次数大大减小;在char *实现中,追加只能经过重分配内存实现
于此同时对SDS的操做必须正确处理len与free属性
4. 如何减小内存重分配次数
建立:当调用set命令建立SDSHDR时,BUF很少申请,恰好给够所需的,free=0
追加:当再次追加的时候,若是free的长度大于所需,就直接append进去,否则的话,会给二倍的内存,可是若是大于SDSHDR容许的MAX_Preallocation,最大预分配,不会翻倍,会再给一个MAX_Preallocation
释放时间:当键值被删除时预分配空间会被删除;当重启Redis时,预分配的空间也会被释放,每一个SDS对应的SDSHDR不存在预分配空间,BUFF大小等于所需空间
SDS是Redis对应的字符串表示,SDSHDR是对应的存储类型
2、双端链表
1. 双端链表做用
2. 双端链表的实现
双端链表是由两部分组成的,list与listNode
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;
listNode的value值的类型是void *,方法返回值的类型也是void *,表明对值得类型不作限制
3. 迭代器
typedef struct listIter { // 下一节点 listNode *next; // 迭代方向 int direction; } listIter;
迭代器内保存一个listNode,而且指明迭代的方向
3、字典
1. 字典的做用
Redis是一个键值对数据库,数据库中的键值对由字典保存,每一个数据库都有一个字典,这个字典称为键空间(Key Space),当用户添加一个键值对到数据库中时,不管键值对是什么类型,程序就会将该键值对添加到键空间
hash类型键的底层实现除了字典外就是压缩列表
2. 字典的实现
字典的实现方式有不少种,例如链表与数组,优势:简单 缺点:只适用于元素很少的状况
哈希表, 优势:高效简单
平衡树, 优势:稳定,排序操做更高效 缺点:实现更复杂
Redis采用哈希表实现字典,哈希表的子结构是dictEntry
dictEntry的实现
/* * 哈希表节点 */ typedef struct dictEntry { // 键 void *key; // 值 union { void *val; uint64_t u64; int64_t s64; } v; //union关键字 三种中只能包含其中一种 // 链日后继节点 struct dictEntry *next; } dictEntry;
next指针指向另外一个dircEntry节点,之因此存在链是由于有的键值可能会哈希成同一值,因而采用链地址法来处理哈希值碰撞问题,当不一样的键拥有相同的哈希值时,哈希表就将这些键连接起来
dircht(dirc hash table)的实现
/* * 哈希表 */ typedef struct dictht { // 哈希表节点指针数组(俗称桶,bucket) dictEntry **table; // 指针数组的大小 unsigned long size; // 指针数组的长度掩码,用于计算索引值 unsigned long sizemask; // 哈希表现有的节点数量 unsigned long used; } dictht;
**table是一个数组,俗称桶(bucket),每个值对应一个dictEntry结构的指针
size表明数组大小,sizemask意思不清楚,used就是哈希表现有的键的数量
字典的定义
/* * 字典 * * 每一个字典使用两个哈希表,用于实现渐进式 rehash */ typedef struct dict { // 特定于类型的处理函数 dictType *type; // 类型处理函数的私有数据 void *privdata; // 哈希表(2 个) dictht ht[2]; // 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行 int rehashidx; // 当前正在运做的安全迭代器数量 int iterators; } dict;
字典的实现使用了两个hash table,0号哈希表是字典主要使用的哈希表,1号哈希表只有当程序对0号哈希表进行rehash的时候才会使用
3. rehash
rehash:当键很是多,远大于table数组长度时,数组内的每一个值将退化成一条链,hash Table的优点将不复存在,因而须要进行rehash操做,对hash table进行扩容,将比率尽可能维持在1:1左右
rehash触发的条件有两种:1. 键与数组长度比率ratio>=1 && dict_can_resize为真
2. ratio>=dict_force_resize_ratio dict_force_resize_ratio是强制改变大小的比率
当数据库执行后台持久化任务时,为了最大化利用系统的copy on write机制,程序会暂时将dict_can_resize置为假,避免执行天然resize,总而言之就是为了效率
4. 字典的收缩
与rehash相反
收缩操做是程序手动执行的,扩展操做是自动执行的,收缩程序决定填充率是多少的时候来执行收缩程序
对哈希表的扩展和收缩都是分屡次、渐进式的进行的
4、跳跃表
跳跃表是一个有层次的链表,增删改查的时间复杂度都是O(logN)
跳跃表解释:http://blog.jobbole.com/111731/
2019-03-07 11:13:52 有时间再看吧 得有输出才行啊 干点活吧
跳跃表是为了提升链表的增删改查,对于一个链表来说,选出一些领导者在上一层,因而在增删改查的时候,首先在上一层进行选择区间以后再下沉到下一层,提升了效率
1. 跳跃表的实现
跳跃表的实现
typedef struct zskiplist { // 头节点,尾节点 struct zskiplistNode *header, *tail; // 节点数量 unsigned long length; // 目前表内节点的最大层数 int level; } zskiplist;
跳跃表节点(层)的实现
typedef struct zskiplistNode { // member 对象 robj *obj; // 分值 double score; // 后退指针 struct zskiplistNode *backward; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 这个层跨越的节点数量 unsigned int span; } level[]; } zskiplistNode;
Q&A:
为何Redis要使用C而不是C++的STL容器?
猜测:多是基于内存或者效率的考虑吧
SDS如何实现二进制安全的?
跳跃表与平衡树比较?