Redis数据结构原理
Redis没有直接使用C语言传统的字符串表示,而使自己构建了一种名为简单动态字符串的抽象SDS
C 字符串 | SDS |
---|---|
获取字符串长度的复杂度为 O(N) 。 | 获取字符串长度的复杂度为 O(1) 。 |
API 是不安全的,可能会造成缓冲区溢出。 | API 是安全的,不会造成缓冲区溢出。 |
修改字符串长度 N 次必然需要执行 N 次内存重分配。 |
修改字符串长度 N 次最多需要执行 N 次内存重分配。 |
只能保存文本数据。 | 可以保存文本或者二进制数据。 |
可以使用所有 库中的函数。 | 可以使用一部分 库中的函数。 |
Redis 的链表实现的特性可以总结如下:
-
双端: 链表节点带有
prev
和next
指针, 获取某个节点的前置节点和后置节点的复杂度都是 O(1) 。 -
无环: 表头节点的
prev
指针和表尾节点的next
指针都指向NULL
, 对链表的访问以NULL
为终点。 -
带表头指针和表尾指针: 通过
list
结构的head
指针和tail
指针, 程序获取链表的表头节点和表尾节点的复杂度为 O(1) 。 -
带链表长度计数器: 程序使用
list
结构的len
属性来对list
持有的链表节点进行计数, 程序获取链表中节点数量的复杂度为 O(1) 。 -
多态: 链表节点使用
void*
指针来保存节点值, 并且可以通过list
结构的dup
、free
、match
三个属性为节点值设置类型特定函数, 所以链表可以用于保存各种不同类型的值。
字典dict
rehash
一般字典都会有ht[0] ht[1]两个字典,而ht1就是rehash时用的
步骤:
-
判断是收缩还是扩容,确定ht1的size
-
将ht0的数据迁移到ht1
-
释放ht0,ht1成为ht0,新建ht1
当满足以下条件时,哈希表会进行扩展和收缩
-
服务器目前没有执行BGSIVE或者BGREWRITEAOF命令,并且哈希表的负载因子>=1
-
服务器目前正在执行这两条命令之一,且负载因子>=5
-
负载因子=ht[0].used / ht[0].size
-
负载因子小于0.1 进行收缩
原因:处于对执行BGSIVE或者BGREWRITEAOF命令时期时,redis会创建服务器进程的子进程,一般采用写时复制的策略优化子进程效率,所以子进程存在时会尽量避免rehash,因此提高了此时所需的负载因子
其次:rehash是渐进rehash
也就是说redis实际执行rehash时是采用每次对字典增删改查时顺便rehash,一些操作过后,字典rehash完成。当然了,在此期间,比如对字典的查找是在ht0和ht1上都进行查找
跳跃表
跳跃表是一种有序数据结构,支持Ologn的查找,最坏On。还可以通过顺序性操作批量处理节点
-
header | tail 头尾指针
-
level 跳跃表层数最大的节点(排除表头节点)
-
每层 记录了这一跳的前进指针以及跨度这两个属性,显然层数越多跳跃表的查找遍历就越快,跨度用来统计走过的距离,方便统计Rank
-
后退制作backward BW标记了向前一个结点的倒退指针,以实现表尾到表头的遍历操作
-
分值和对象,value从小到大构成跳跃表,对象保存一个SDS,value相同时按SDS字典序排
遍历时从高层往下查,碰到的前进指针跨度是1是往下走,知道碰到null证明到头了
整数集合
整数集合是集合间的底层实现之一,当set只有整数元素并且数量不多时,会采用整数集合
升级,但不支持降级
每当新添加的整数类型比现有类型长时,会进行升级操作,这样的动态策略可以节省内存
压缩列表
列表键和哈希键的底层实现之一
AOF
与rdb保存数据库键值对不同,AOF持久化通过保存Redis执行的写命令持久化
AOF持久化的实现
可以分为命令 追加append 文件写入 文件同步sync三个步骤
命令追加
比如SET KEY VALUE执行后,这条命令会最佳到服务器状态的aof_buf
缓冲区的末尾
AOF文件的写入与同步以及appendfsync选项
Redis 服务器进程就是一个时间循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件负责执行serverCron函数这样需要定时运行的函数,
fsync和fdatasync同步函数
现代操作系统中用户调用write函数,通常先把数据放到内存缓冲区中,等到缓冲区满了或到了指定时间才会写入,这样虽然提高效率但也有停机时的不安全性,使用这两个同步函数强制操作系统将缓冲区的数据写入磁盘
appendfsync选项的值得效果
always时,最安全但也最慢,他最多只丢失一个事件循环中的命令数据
everysec时,每秒都有负责这个的子线程进行同步,够快且最多丢失一秒的追加命令
no时,同样每个事件循环都把缓冲区数据写到aof,但aof文件的同步由操作系统决定,因为一般不需要同步,所以这种模式的aof文件写入速度很快,但一旦发生丢失数据则会丢失上次同步aof后的所有数据
AOF重写
利用命令的合并和覆盖,创建一个新的aof文件替代原有的aof,新的aof只执行持久化必须的一部分的命令体积小很多
重写伪代码p150