Redis的设计与实现阅读笔记

目录

Redis

简单的动态字符串SDS:

链表:

字典:

跳表

整数集合​​​

压缩列表:

实际为低概率事件,条件很难达到,时间复杂度趋近于O(N)。

对象

单机数据库

数据库

RDB持久化:

自动间隔性保护:​​​

RDB文件结构                                                                                       

AOF持久化

事件

客户端:

多机数据库的实现:

复制:

Sentinel(哨兵系统)

集群

 



redis是Nosql数据库,是一个key-value存储系统。可用于缓存,事件发布或订阅,高速队列等场景。该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。


Redis

简单的动态字符串SDS:

与c字符串的区别

获取字符串复杂度:

C字符串遍历,O(N),动态字符串返回len,O(1)

 

杜绝缓冲区溢出:(c字符串不会检查内存是否足够)

 

减少字符串修改带来的内存分配次数:

c字符串

SDS

 

二进制安全

C字符串中不能出现空字符(\0用来标记结束),所以只能存储文本数据,而SDS用len属性标记结束。可以保存所有的二进制数据。

兼容部分c风格字符串函数

       保存了c风格字符串\0标记,兼容部分函数strcasscmp等

 

链表:

双端,无环,有头有尾有长度,多态(void*指针操作)。

 

链表的多态:函数指针进行复制,释放,对比等操作。

字典:

 

Rehash

渐进式Hash

跳表

整数集合

整数集合:元素唯一,从小到大排序。

升级:当向一个集合里加入长度更大的元素时,先扩大数组长度,然后从后往前类型转换升级元素在赋值到后面对应元素,最后插入新元素。每个元素都得类型转换(O(N);

升级的好处:灵活,c里面unint32_tunint16_t不可以放到同一类型数组里面,Redis里面可以;节约内存:当unint64_t里面一直存unint16_t的数据时,内部元素全为unint16_t,直到存储更长类型进行升级。升级之后不能降级。

压缩列表:

压缩列表的构成:

压缩列表节点构成:保存一个字节数组或者整数。

previous_entry_length:保存前一个节点的长度,前一个字节小于254则1个字节表示,否则5个字节表示。知道一个节点的起始指针可以倒序遍历。

encoding:记录content的数据类型和长度,1字节2字节或5字节(字节数组编码或整数编码)。

content:保存节点的值,可以是字节数组或者整数。

连锁更新:若原本节点大小都小于254,一个字节的previous_entry_length保存上一个节点长度,上一个节点插入一个大于254的节点,则此节点的previous_entry_length属性得变更为5个字节,可能导致本节点也大于254,从而导致后面节点也重新分配…连锁更新。(条件:连续节点大小在250-253)。小节点在两个大节点中间,删除小节点,后续节点也可能发生连锁更新。时间复杂度为O(N2),每个节点更新一次N,N个节点都要更新。

实际为低概率事件,条件很难达到,时间复杂度趋近于O(N)。

对象

对象结构:

类型,编码(每种类型至少使用两种编码)

字符串对象:

字符串编码三种类型int 、raw、embstr。

若保存的字符串大于32位则为raw编码,底层使用SDS(简单动态字符串)。

若保存的字符串小于32位则embstr编码,则还是SDS只是内存一次申请一次释放更高效。

编码转换:int类型加入非数字转换为字符串类型,embstr没有修改函数,只读;修改只能转化为raw然后进行修改;

列表对象:

两种编码ziplist 和 linkedlist对象。

linkedlist对象链表保存的是字符串对象,字符串对象是5种类型中唯一能被对象嵌套的对象

编码转换:

哈希对象:

ziplisthashtable两种编码方式。

ziplist底层使用压缩列表

hashtable底层使用字典。字典的键和值都是字符串对象。

编码转换:

集合对象:

inset和hashtable

inset使用正数类型集合作为底层,hashtable使用字典。键为字符串对象,值为NULL。

编码转换:

有序集合:

ziplistskiplist

skiplist底层使用zset是包含字典和跳表的两种数据结构集合。

跳表可以保持范围查询的性能,字典可以保证查找成员分值的性能

编码转换:

 

基于引用计数的内存回收/对象共享

对象共享:

共享对象首先要验证两个对象是否相等,相等才可以共享,但是验证需要开销,所以redis只能共享只包含整数值的字符串对象

空转时长:

单机数据库

数据库

服务器中的数据库

保存在redisServer的redisDb指针的数组里,每个元素代表一个数据库,dbnum代表数据库个数,默认16个数据库。

切换数据库

服务器内部,客户端状态属性记录当前客户的目标数据库。SELECT修改redisCilent.db指针来改变数据库。

数据库的键空间:

Redis是一个键值对数据库服务器,每个数据库都是redisDb结构,字典dict保存所有键值对,称为键值空间。

增删查改(更新):SET DEL GET

设置键的生存时间或过期时间

保存过期时间:

键空间的键和过期字典的键都是同一个键,不存在空间浪费。

过期键删除策略:

redis采用惰性删除和定期删除策略,平衡内存和CPU占用时间。

RDB持久化:

Redis是内存数据库,数据库存放在内存中,丢电丢失,必须保存到磁盘进行持久化;RDB持久化将数据库状态保存到磁盘里面,防止丢失;生成RDB文件是一个压缩二进制文件,该文件可以还原数据库。

RDB文件的创建:SAVE BGSAVE命令。

SAVE命令:阻塞服务器,直到RDB创建完毕,阻塞期间服务器不能处理任何命令。SAVE执行期间,客户端请求会被拒绝。

BGSAVE命令开启一个子进程进行创建RDB,父进程继续服务器的操作不会阻塞。BGSAVE执行期间:

SAVE请求会被拒绝,防止父进程(SAVE)和子进程同时调用rdbSave函数产生竞争。

BGSAVE命令会被拒绝:同时执行两个BGSAVE也会产生竞争。

BGREWRITEAOF:不能同时执行,不会竞争但是两个进程进行磁盘写入操作低效;

BGSAVE执行期间,BGREWRITEAOF会被延迟执行;BGREWRITEAOF执行期间BGSAVE会被拒绝。

RDB文件载入:载入是在服务器启动的时候进行自动载入的,没有命令,启动时检测到RDB文件就自动载入,文件载入期间服务器被阻塞;AOF的更新频率比RDB高,若服务器开启了AOF持久化,优先使用AOF文件;AOF关闭才会用RDB文件。

自动间隔性保护:

RDB文件结构                                                                                       

RDB文件分为5部分

database结构:

AOF持久化

AOF文件的载入:

AOF重写

AOF文件会随着对数据库的操作越来越大,浪费空间,需要重写AOF代替旧的AOF文件,从而节省空间。

BGREWRITEAOF

redis服务器为单线程操作,防止BGREWRITEAOF时阻塞,开辟子进程进行重写,重写期间不阻塞,服务器进程继续执行客户端请求,同时此期间执行后的请求会被追加在AOF缓冲区,将执行后的写命令追加到AOF重写缓冲区,当子进程AOF重写完之后,发送信号给服务器进程,触发信号处理函数:(只有此期间服务器进程阻塞)将AOF重写缓冲区的内容写入到新AOF文件,然后更改新的AOF文件名覆盖旧的AOF文件。

事件

文件事件

 

时间事件(定时事件,周期性事件)

事件调度:

客户端:

多机数据库的实现:

复制:

旧版复制:同步阶段+命令传播阶段

       同步:SYNC

       命令传播:(同步后继续保持主从一致)

两种复制类型:

断线后重复制:会重新生成RDB文件,进行从头复制,消耗CPU服务器和网络资源等,SYNC是一个低效的命令。

 

新版复制:PSYNC

部分同步的实现:

主从服务器偏移量(比较主从服务器偏移量可以确定主从服务器是否一致)

 

复制积压缓冲区:(维护一个固定长度的队列)

 

当A断开之后向主服务器发送PSYNC并报告自己的偏移量10086

主服务器收到之后检查复制积压缓冲区存在10086偏移量缓存,发送+CONTINUE

主服务器会将积压缓冲区中10086之后的发给A,使其达到一致。

服务运行ID:

PSYNC实现:

复制的实现

设置主服务器端口->建立套接字链接->发送ping命令(检查网络)->身份验证(授权)->发送端口信息->同步->命令传播

 

 

心跳检测:

 

Sentinel(哨兵系统)

Sentinel是Redis高可用性解决方案;监视任意多个主服务器及其从服务器;并在主服务器下线时,将一个从服务器提升为主服务器,同时监控下线的服务器,下次上线时在使其称为从服务器。

启动并初始化Sentinel:

Sentinel本质上是一个在特殊模式下运行的Redis服务器,但是初始化过程和其他Redis服务器略有不同;Sentinel不适用数据库,所以不需要载入RDB或AOF文件。

Sentinel启动第二步:将一部分Redis服务器使用的代码替换成Sentinel专用代码。

初始化Sentinel的状态:初始化一个sentinelState结构体,保存了服务器中所有和Sentinel有关的功能的状态;

初始化Master属性:

创建向主服务器的网络链接:

获取主服务器信息

Sentinel默认每10秒通过命令连接向被监视的主服务器发送INFO命令,通过解析INFO命令获取主服务器的信息;

获取从服务器信息:

当有新的从服务器加入,Sentinel不仅会给它创建实例结构还会创建新的命令连接和订阅连接;命令连接会每个10秒进行INFO信息交换。

向主服务器和从服务器发送信息:

接收来自主服务器和从服务器的频道信息

如果信息中的运行ID和自身ID相同则抛弃,否则是其他监视同一服务器的sentinel发送的,进行更新。

Sentine与主服务器之间,需要接收主服务器频道信息,所以有订阅链接。Sentinel之间没有。

检查主观下线状态:

Sentine每隔1秒向所有与之命令链接的实例发送PING命令,通过返回的PING 命令判断实例是否在线。

客观下线:

选举头领Sentinel:

当一个主服务器被判定客观下线后,所有监视这个服务器的Sentinel会选举一个头领Sentinel对下线的主服务器进行故障转移操作。

故障转移

集群

节点:

握手:

集群里面的数据结构:

节点状态clusterNode:给自己和其他节点分别创建clusterNode结构保存节点状态;

clusterLink属性:保存节点自身所需的相关信息,如套接字,缓冲区等。

 

clusterState:每个节点都会保存这样的结构,记录集群的状态(集群线上线下,纪元等)。

节点握手过程:(握手之后A会通过Gossip协议告知其他节点和B进行握手)

 

槽指派:

槽信息的记录:clusterNode结构里面通过slots数据记录槽的指派信息(0/1),numslots标记指派数量。

 

槽信息记录:clusterStateslots方便查阅某个槽是否空闲,指派到哪个节点; clusterNodeslots方便查找某个节点槽信息等。

 

CLUSTER ADDSLOTS:进行槽指派。

集群中命令的执行:

重新分片:

 

ASKING:

重新分片的过程中,属于被迁移的槽的一部分键值对在源节点,一部分在目的节点(迁了一半有命令到来)

 

复制与故障转移:

故障检测:集群中每个节点定时都会向其他节点发送PING消息,收到规定时间内返回PONG消息。收不到PONG则标记节点为疑似下线(PFAIL)。集群中各个节点互通消息确定各个节点的状态,若某一节点的状态半数以上标记为PFAIL则判定为下线(FAIL)状态,并且广播告知其他节点该节点为FAIL。

故障转移:

消息:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值