Redis设计

文章目录

第一部分:编码

一、简单动态字符串(SDS)

简单字符串:Simple Dynamic String(SDS)

用处:

  • 保存数据库中的字符串值
  • 当作缓冲区:AOF缓冲区、客户端状态的输入缓冲区

1.1 SDS定义(结构体)

sds.h/sdshdr

struct sdshdr{
	// 记录buf数组中已使用的字节的数量
	// SDS保存的字符串的长度(不包括结尾的'\0')
	int len;
	
	// 记录buf数组中未使用字节的数量
	int free;
	
	// 字节数组,用户保存字符串
	char buf[];
}

示例:
在这里插入图片描述

  • free属性值为5:SDS为buf数组分配了5字节未使用空间
  • len属性值为5:SDS保存了5字节长的字符串

1.2 SDS 与C字符串的区别

1.2.1 常数获取字符串

  • 因为C字符串并不记录自身长度,每次必须遍历整个字符串进行计数,时间复杂度O(n)
  • SDS的len属性记录了SDS本身的长度,获取长度的复杂度为O(1)(设置和更新SDS长度是由SDS的API在执行时自动完成的)

1.2.2 杜绝缓冲区溢出

C字符串的添加操作:

假设程序里有两个内存紧邻的C字符串S1和S2,分别保存了“Redis”和“MongoDB”

在这里插入图片描述

如果程序猿决定执行strcat(s1,“ Cluster”),但是忘了在执行合并之前为S1分配足够的内存,那么在strcat函数执行后,s1的数据将溢出覆盖到S2所在的空间,导致S2的内容被意外的修改:

在这里插入图片描述

SDS的添加操作:

SDS在执行sdscat(将c字符串拼接到SDS保存的字符串后面)前会先判断剩余空间free够不够,如果不够会先扩展字符串的空间,再执行拼接操作

在这里插入图片描述

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

C字符串操作的缺陷:

​ 每次对C字符串执行添加(strcat)或截断操作(strim)都需要重新分配内存空间;在SDS中通过未使用空间(即free属性记录的值)实现空间预分配惰性空间释放两种优化策略。

  • 空间预分配:(SDS进行空间扩展的时候)

    • 如果扩展后SDS的长度(即len)小于1MB,那么程序分配和len属性同样大小的未使用空间free,此时len和free一样长,比如图2-10的free和len都为13字节。
    • 如果扩展后SDS的长度(即len)大于等于1MB,那么程序分配1MB的未使用空间free

    图2-10执行sdscat添加“ cluster”,重新分配内存后未使用空间free和len都为13字节,此时如果再执行sdscat添加操作,sdscat(s," aaa")则不用重新分配内存,因为free未使用空间有13字节满足“aaa”3个字节。

  • 惰性空间释放:(优化SDS字符串缩短操作)

    • SDS的API需要缩短SDS保存的字符串时,程序不需要立即重新分配内存来回收缩短后多出来的字节,而是使用free属性记录起来留着使用。

    比如SDS执行sdstrim(s,“XY”)移除SDS中所有的“X”和"Y",SDS并没有立即回收多余的8字节,如果将来要执行添加操作,则可以直接添加,减少内存重分配次数。

    在这里插入图片描述

1.2.4 二进制安全

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XxShgDCu-1593449631672)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200628234425808.png)]

  • C字符串通过’\0‘来判断字符串结尾,对于含有’\0’的特殊数据格式,只能读出Redis
  • SDS API使用len属性值判断字符串是否结束,不会过滤掉中间的’\0’,数据在输入时是什么样,被读取时就是什么样

1.2.5 兼容部分C字符串函数

SDS遵循C字符串以’\0‘结尾的惯例,分配空间时总会多分配一个字节的空间保存’\0‘,可以重用部分<String.h>库定义的函数

1.2.6 区别总结

在这里插入图片描述

1.3 SDS常用API

在这里插入图片描述

总结

  • Redis只会使用C字符串作为字面量,在大多数情况下,Redis使用SDS(SimpleDynamic String,简单动态字符串)作为字符串表示。

  • 比起C字符串,SDS具有以下优点:

    1. 常数复杂度获取字符串长度。
    2. 杜绝缓冲区溢出。
    3. 减少修改字符串长度时所需的内存重分配次数。
    4. 二进制安全。
    5. 兼容部分C字符串函数。

二、链表(双向链表)

用途:列表键的实现之一

2.1 链表的结构

链表节点的结构体
在这里插入图片描述

链表的结构体(双向链表)

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

2.2 链表的特点

  1. 双端:pre和next指针
  2. 带表头指针和表尾指针
  3. 无环:表头节点的pre和表尾的next都指向null
  4. 带链表长度计数器:list结构体里的len属性记录了链表的长度

2.3 链表和链表节点的API

在这里插入图片描述
在这里插入图片描述

总结

  • 链表被广泛用于实现Redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。
  • 每个链表节点由一个listNode结构来表示,每个节点都有一个指向前置节点和后置节点的指针,所以Redis的链表实现是双端链表。
  • 每个链表使用一个list结构来表示,这个结构带有表头节点指针、表尾节点指针,以及链表长度等信息。
  • 因为链表表头节点的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。
  • 通过为链表设置不同的类型特定函数,Redis的链表可以用于保存各种不同类型的值。

三、字典

用途:

  • Redis数据库:对数据库的增删改查都是建立在对字典的操作上
  • 哈希键的底层实现之一

3.1 字典的结构体

3.1.1 哈希表节点

dict.h/dictEntry结构定义(类似HashMap里的Entry
在这里插入图片描述

  • key:保存键值对中的键
  • v:保存键值对的值,可以是一个指针,或者uint64_t整数或者int64_t整数
  • next:指向下一个哈希表节点的指针

3.1.2 哈希表

dict.h/dictht结构定义

  • table:哈希表节点数组,数组的每个元素都是指向dictEntry结构体的指针(类似HashMap的table)
  • size:哈希表的大小
  • used:哈希表已有节点(键值对)的数量

3.1.3 字典

dict.h/dict

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iNArPonP-1593450258328)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629002542757.png)]

  • ht是两个哈希表的数组,字典只使用ht[0],rehash时使用ht[1]
  • type属性如图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LCo3kvyd-1593450258336)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629003049556.png)]

3.1.4 字典结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FilGl1Nn-1593450258349)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629002804231.png)]

3.2 Hash算法

  1. 先利用字典的hash函数根据key计算出hash值:使用dict->type->hashFunction(key)
  2. 根据hash值计算索引值:将hash值与sizemask进行与运算(即hash & (size-1),与hashMap原理相同

3.3 Hash冲突解决

Redis使用链地址法解决Hash冲突,与HashMap相同,但是Redis为了考虑速度,将冲突的键值对插入到链表的表头位置复杂度为O(1)

3.4 rehash

rehash:执行扩展或伸缩过程中,给ht[1]分配空间,将ht[0]的键值对重新计算哈希值和索引值,放到ht[1]的指定位置

rehash过程

  1. ht[1]分配的空间大小取决于当前键值对数量(即ht[0].used)
    • 如果执行扩展操作:ht[1]大小为第一个大于等于2*ht[0].used的2^n(2的n次幂),例如当前为12,12*2=24,最接近24的2的幂次方是32
    • 如果执行收缩操作:ht[1]大小为第一个大于等于ht[0].used的2^n(2的n次幂)
  2. 将保存在ht[0]的所有键值对rehash到ht[1]上面
  3. ht[0]的所有键值对都rehash到ht[1]后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]创建一个空的hash表(table为null),为下一次rehash使用。

负载因子:ht[0].used / ht[0].size

满足以下条件时,会自动对hash表执行扩展操作

  1. 服务器目前没有执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于1

  2. 服务器目前正在执行BGSAVE或BGREWRITEAOF命令,并且哈希表的负载因子大于等于5

    注意:BGSAVE或BGREWRITEAOF命令执行需要创建子进程,服务器会提高扩展所需的负载因子来避免执行扩展操作,避免不必要的内存写入操作,最大限度节约内存。

满足以下条件时,会自动对hash表执行收缩操作

  1. 哈希表的负载因子小于0.1

3.5 渐进式rehash

因为哈希表的键值对可能成千上万,如果一次性rehash到新的哈希表,由于庞大的计算量导致redis服务器在一段时间内停止服务,造成性能影响,所以redis是渐进式、分多次的将ht[0]里的键值对慢慢的rehash到ht[1]

  1. 为ht[1]分配空间,字典同时拥有ht[0]和ht[1]两个哈希表
  2. 字典中位置变量rehashindex,值设为0表示正在rehash
  3. rehash期间,每次对字典的添加、删除、查找、更新时,除了执行指定操作外,顺便将ht[0]的键值对rehash到ht[1]上,每次rehash完成之后,rehashindex值加一。
    • 删除、查找、更新操作在ht[0]和ht[1]两个哈希表上进行,比如查找会现在ht[0]查找,如果找到除了返回键值对外,由于查找时计算了hash值可以顺便映射到ht[1]上,没找到再去ht[1]查找
    • 增加操作只会在ht[1]执行,不会在ht[0],这样保证了ht[0]的键值对只增不减,随着rehash不断执行最终变成空表
  4. 随着操作不断进行,ht[0]的所有键值对都会rehash到ht[1]上,此时将rehashindex设为-1表示rehash完成

3.6 字典API

在这里插入图片描述

总结

  • 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。
  • Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。
  • 当字典被用作数据库的底层实现,或者哈希键的底层实现时,Redis使用MurmurHash2算法来计算键的哈希值。
  • 哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表。
  • 在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对rehash到新哈希表里面,并且这个rehash过程并不是一次性地完成的,而是渐进式地完成的。

四、跳跃表

用途:有序集合键的底层实现之一。

支持平均复杂度O(logN),最坏复杂度O(N)的节点查找

4.1跳跃表的结构体

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u32NcDFj-1593450454712)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629112310227.png)]

跳跃表节点的结构体(redis.h/zskiplistNode)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pgPV0KkR-1593450454714)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629112133144.png)]

  • 层(level):level数组每个元素包含一个指向其他节点的指针forward和跨度span,可以加快访问节点的速度。(每一层类似在链表之上再建立的多层索引),每次创建节点的时候都随机生成一个[1,32]之间的数作为level数组的大小,这个大小就是层的高度
  • 前进指针(forward):用于从表头向表尾访问节点
  • 跨度:记录两个节点的距离,指向NULL的前进指针的跨度都为0
  • 后退指针(backword):用于从表尾向表头访问节点,每个节点只有一个后退指针,只能退前一个节点
  • 分值(score):double类型的浮点数,跳跃表的节点按照分值从小到大排序
  • 成员对象(obj):是一个指针,指向一个字符串对象,字符串对象保存着一个SDS值

注意:同一个跳跃表中,各个节点保存的节点对象必须是唯一的,分值可以相同,分值相同的按照字典序大小进行排序

跳跃表的结构体

typedef struct zskiplist{
	// 表头节点和表尾节点
	struct zskiplistNode *header,*tail;
	
	// 标中节点的数量
	unsigned long length;
	
	// 表中层数最大的节点的层数
	int level;
	
}zskiplist;
  • header:指向表头指针
  • tail:指向表尾指针,定位表尾的复杂度为O(1)
  • length:记录节点的数量,获取长度的复杂度为O(1)
  • level:O(1)复杂度获取跳跃表中层最高的节点的层数(注意:表头节点的高度不计算在内

4.2 跳跃表API

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZNaOWNV-1593450454727)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629115051144.png)]

总结

  • 跳跃表是有序集合的底层实现之一。
  • Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
  • 每个跳跃表节点的层高都是1至32之间的随机数。
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。
  • 跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序。

五、整数集合

用途:集合键的底层实现之一

5.1 整数集合结构体

intset.h/intset

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lmDw4hi4-1593450454730)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629131819531.png)]

  • contents:该数组的每个元素都是整数集合的元素,按值大小从小到大排序,并且不包含重复项
  • length:记录了整数集合包含的元素数量,也是contents数组的长度
  • encoding:决定contents数组的类型,contents的定义为int8_t,但实际取决于encoding的值:
    • int16_t:最小值-32768,最大值32767
    • int32_t:最小值-2147483648,最大值2^32-1
    • int64_t:最小值-264,最大值244-1

5.2 升级

升级定义:当将一个新元素添加到整数集合里,并且新元素的类型比原来整数集合里的类型都要长时,整数集合需要进行升级,然后才能将新元素添加到整数集合里。

升级过程:

  1. ​ 根据新元素的类型,扩展整数集合数组contents的大小,并为新元素分配空间
  2. ​ 将底层数组的所有元素类型转换为与新元素相同的类型,并将转换的元素放到正确的位上保持有序性。
  3. ​ 将新元素添加到底层数组里面:
    • 如果新元素比数组的所有元素都小(负数),则插在数组第0位
    • 如果新元素比数组的所有元素都大(整数),则插在数组第length-1位

升级示例:

在这里插入图片描述

将int32_t类型的65535添加进去,需要重新分配空间。(32x4=128,还需要128-16x3 =80位)

在这里插入图片描述

升级的好处:

  1. 提高灵活性:
    • C语言存放int16、int32、int64分别用不同类型的数组保存不同的元素类型
    • 整数集合通过自动升级数组来适应元素,所以可以随意的将int16、int32、int64添加到集合中,而不必担心出现类型错误
  2. 节约内存:要让一个数组保存int16、int32、int64三种类型的值,最简单就是申明为int64类型的数组,可以全部兼容,但是这样如果都是int16或int32类型的元素,将会浪费内存空间,整数集合的升级能同时保存三种不同类型的元素,只有在需要的时候才会对类型升级,确保节约内存

5.3 降级

整数集合不支持降级操作,如果将集合从int16升级到int32后,将int32类型的元素删光只剩int16,整数集合的编码仍然会维持int32

5.4 整数集合API

在这里插入图片描述

总结

  • 整数集合是集合键的底层实现之一。
  • 整数集合的底层实现为数组,这个数组以有序、无重复的方式保存集合元素,在有需要时,程序会根据新添加元素的类型,改变这个数组的类型。
  • 升级操作为整数集合带来了操作上的灵活性,并且尽可能地节约了内存。
  • 整数集合只支持升级操作,不支持降级操作。

六、压缩列表

用途:列表键哈希键的底层实现之一

特点:从表尾往表头遍历

6.1 压缩列表结构组成

在这里插入图片描述

示例:

在这里插入图片描述

压缩列表节点的组成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6Bot4pd-1593450454742)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629140950128.png)]

  • previous_entry_length:以字节为单位,记录了压缩列表前一个节点的长度

    • 如果前一字节的长度小于254字节,则previous_entry_length属性的长度为1字节(8位可以表示254以内的数字)
    • 如果前一字节的长度大于等于254字节,则则previous_entry_length属性的长度为5字节,其中第一个字节会被设置为0xFE(十进制254),剩余四个字节表示前一个节点的长度
  • encoding:纪律节点的content属性保留的数据类型及长度:

    • 1字节、2字节、5字节:值的最高位为00、01、10的是字节数组的编码,表示content保存的是字节数组,数组的长度由编码去掉最高的两位之后的二进制表示

    • 1字节,最高位11开头的是整数编码,整数值的类型由编码去掉最高两位后的二进制表示

  • content:负责保存节点的值,可以是一个字符数组或者一个整数值,类型和长度由encoding确定

    字符数组要求:

    • ​ 长度小于等于(2^6-1)字节的字符数组
    • ​ 长度小于等于(2^14-1)字节的字符数组
    • ​ 长度小于等于(2^32-1)字节的字符数组

    整数值要求:

    • 4位长,介于0-12之间的无符号整数
    • 1字节长的有符号整数
    • 3字节长的有符号整数
    • int16_t类型的整数
    • int32_t类型的整数
    • int64_t类型的整数

示例1:
在这里插入图片描述

  • 编码最高的两位00表示保存的是一个字节的字符数组
  • 后六位001011表示字节数组的长度是11
  • content属性的值为“hello world”

示例2:
在这里插入图片描述

  • 编码的11表示保存的是整数
  • 后6位为0表示整数是int16类型
  • content属性的10086是节点的值

6.2 连锁更新

特例:有一个压缩列表,所有节点(即e1到eN节点)的大小都是250-253字节的,这是记录前一个节点的previous_entry_length的大小只要一个字节

在以上例子发生一下情况会产生链所更新:

  1. 将一个大于254字节的节点插入到表头:新节点的大小大于254字节,则e1需要用一个5字节的previous_entry_length记录,由于e1原来的长度是250-253字节,这时候e1的previous_entry_length改为5字节,增加了4字节,将会大于等于254字节,导致e2的previous_entry_length也需要更新,产生连锁反应。
  2. 删除节点:e1前面有一个small节点小于254字节,small前面有个big节点,大于254字节。这时e1的previous_entry_length记录的是small节点的大小只需一个字节,small节点删除后,e1的previous_entry_length记录的是big节点的大小超过254字节,需要换成五字节的previous_entry_length,导致连锁更新。

注意:

  • 连锁更新最坏情况需要对压缩列表进行N次空间重分配,每次空间分配的最坏复杂度为O(N),所以连锁更新的最坏复杂度为O(N^2)
  • 真正造成连锁更新的可能性很低
    • 首先,压缩列表里恰好you好多个长度介于250-253字节之间的节点才会发生连锁更新,但是这种情况不多见
    • 其次,即使出现连锁更新,只要被更新的节点数量不多,救不会对性能造成影响。

6.3 压缩列表API

在这里插入图片描述

总结

  • 压缩列表是一种为节约内存而开发的顺序型数据结构。
  • 压缩列表被用作列表键和哈希键的底层实现之一。
  • 压缩列表可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
  • 添加新节点到压缩列表,或者从压缩列表中删除节点,可能会引发连锁更新操作,但这种操作出现的几率并不高。

第二部分:对象实现

Redis对象

Redis对象的组成

Redis数据库创建一个键值对时,会创建两个对象:一个对象用作键值对的键(字符串对象),另一个对象用作键值对的值

redisObject对象:

typedef struct redisObject{
	// 类型
	unsigned type:4;
	
	// 编码
	unsigned encoding:4;
	
	// 指向底层实现数据结构的指针
	void *ptr;
	
	//引用计数(内存回收使用)
	int refcount;
	
	// 记录对象最后一次被命令程序访问的时间
	unsigned lru:22;
	
	// ...
	
} robj;

  • 类型:表示字符串、列表、哈希、集合、有序集合物种类型。称呼“字符串键”表示键对应的值是字符串对象,“列表键”表示键对应的值对象是列表对象。TYPE命令返回的是值对应的类型
  • ptr:根据encoding属性,ptr指向对象的底层数据结构实现(即SDS、字典、双端链表、跳跃表、整数集合、压缩列表等),可以使用OBJECT ENCODING命令查看对象的编码

在这里插入图片描述

五种数据类型对应的编码

一、字符串对象

字符串对象是五种类型对象中会被其他四种类型对象嵌套使用的

1.1 int编码

如果字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,并将字符串对象的编码设置为int

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tk4oUmRo-1593450863698)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629152536711.png)]

1.2 raw编码(SDS)

要求:

  • 对象保存的是一个字符串值
  • 字符串值的长度大于32字节

结果:

  • 使用简单动态字符串SDS保存字符串值
  • 编码设置为raw

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTlELSPH-1593450863700)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629153002581.png)]

1.3 embstr编码(SDS)

embstr编码是一种专门保存短字符串的优化编码方式

要求:

  • 对象保存的是一个字符串值
  • 字符串值的长度小于等于32字节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHRhZEQp-1593450863711)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629153922457.png)]

embstr与raw编码的区别:

  • raw编码通过两次内存分配分别创建redisObject和sdshdr,而embstr则通过一次内存分配来分配一块连续的内存空间给redisObject和sdshdr

embstr的好处:

  • embstr编码将创建字符串对象的内存分配次数从raw编码的两次降为1次
  • 释放embstr编码的字符串对象只要1次,raw编码需要两次
  • embstr编码的所有字符串对象的数据都在连续一块内存里,比raw编码更好的利用缓存带来的优势

注意:

  • long double类型的浮点数在redis中是作为字符串保存的
  • 取出是会先将字符串转化为浮点数,运算后再转化为字符串保存

1.4 编码的转换

  • 当对int编码的字符串执行append操作,将会把对象的编码转化为raw编码,再执行append操作
  • 对embstr编码的对象执行任何修改操作,程序会将对象的embstr编码转化为raw编码,再执行修改命令。
    • 因为redis没有为embstr编码的字符串编写任何相应的修改程序,所以embstr编码的字符串对象实际上是只读的,执行

1.5 字符串命令的实现

二、列表对象

2.1 ziplist编码(底层压缩列表)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6c73c5A5-1593450863712)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629155810611.png)]

2.2 linkedlist编码(底层双端列表)

每个双端链表节点都保存了一个字符串对象,字符串对象里保存了列表元素(即链表节点的value嵌套指向了一个SDS,里面的字符串数组保存了节点的值)

字符串对象是五种类型对象中会被其他四种类型对象嵌套使用的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRZiEm8A-1593450863713)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629161527634.png)]

2.3 编码转换

使用ziplist编码:

  • 列表对象保存的所有字符串元素的长度小于64字节
  • 列表对象保存的元素数量小于512个

(上限值可以修改,参考list-max-ziplist-value和list-max-ziplist-entries两个选项说明)

不能满足以上两个条件将使用linkedlist编码

2.4 列表命令

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S2i813v1-1593450863716)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629161048444.png)]

三、哈希对象

3.1 ziplist编码(底层压缩列表)

  • 先把键对象添加到链表末尾,再将值对象添加到列表末尾:键和值对象所在的节点是紧挨的,键节点在前,值节点在后
  • 先添加的键值对会在偏表头方向,后添加的键值对会在偏表尾的方向

在这里插入图片描述

3.2 hashtable编码(底层字典实现)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BuDIiLR-1593450863720)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629162008790.png)]

3.3 编码转换

使用ziplist编码(压缩列表)

  • 哈希对象所保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象所保存的键值对数量小于512个

(上限值可以修改,参考hash-max-ziplist-value和hash-max-ziplist-entries两个选项说明)

以上任意一个条件不被满足时,就换编码转换,将压缩列表里的所有键值对转移并保存到字典里

3.4 哈希命令的实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1zC82cIS-1593450863722)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629162515655.png)]

四、集合对象

4.1 intset编码(底层整数集合)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yOrO7NtW-1593450863724)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629162801501.png)]

4.2 hashtable编码(底层字典)

注意:字典的值全部设为NULL

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JzR5U1At-1593450863726)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629162902017.png)]

4.3 编码转换

使用intset编码:

  • 集合对象保存的所有元素都是整数值
  • 集合对象保存的元素个数不超过512个

(第二个条件上限值可以修改,参考set-max-intset-entries选项说明)

只要有一个条件不被满足时,就会转化为hashtable编码

4.4 集合命令实现

在这里插入图片描述

五、有序集合

  • 有序集合的每个成员都是字符串对象
  • 有序集合的每个分值都是double类型的浮点数

5.1 ziplist编码(压缩列表)

  • 使用压缩列表紧挨的两个节点保存有序集合元素,第一个节点保存元素的成员,第二个节点保存元素的分值(score)
  • 压缩列表内的元素按照分值大小从小到达排序

在这里插入图片描述

5.2 skiplist编码(跳跃表和字典)

跳跃表

  • 跳跃表节点的object成员保存有序集合的元素
  • 跳跃表节点的score保存元素的分值
  • 通过跳跃表可以对有序集合进行范围型操作,比如ZRANGE、ZRANK

字典

  • 成员到分值的映射,字典的键是有序集合元素的成员,值是有序集合元素的score
  • 通过字典可以用O(1)复杂度查找给定成员的分值,比如ZSCORE命令就是根据字典实现的

注意:

  • 有序集合虽然同时使用跳跃表和字典来保存有序集合元素,但**这两种数据结构都会通过指针共享相同元素的成员和分值

在这里插入图片描述
在这里插入图片描述
(为了展示方便,字典和跳跃表重复展示了每个元素的成员和分值,但在实际中会通过指针共享不会数据重复)

5.3 编码转换

使用ziplist编码:

  • 有序集合保存的所有元素成员的长度都小于64字节
  • 有序集合保存的数量小于128个

(上限值可以修改,参考zset-max-ziplist-value和zset-max-ziplist-entries两个选项说明)

以上条件只要有一个不满足,就是编码转换使用skiplist

5.4 有序集合命令的实现

在这里插入图片描述

总结

  • Redis数据库中的每个键值对的键和值都是一个对象。

  • Redis共有字符串、列表、哈希、集合、有序集合五种类型的对象,每种类型的对象至少都有两种或以上的编码方式,不同的编码可以在不同的使用场景上优化对象的使用效率。

第三部分:类型与内存

一、类型检查与多态命令

1.1 类型检查

可以对任何类型的键执行:

  • DEL命令
  • EXPIRE命令
  • RENAME命令
  • TYPE命令
  • OBJECT命令
  • 。。。

特定类型的键命令:

  • 字符串键:SET、GET、APPEND、STRLEN等命令
  • 哈希键:HDEL、HSET、HGET、HLEN等命令
  • 列表键:RPUSH、LPOP、LINSERT、LLEN等命令
  • 集合键:SADD、SPOP、SINTER、SCARD等命令
  • 有序集合键:ZADD、ZCARD、ZRANK、ZSCORE等命令

类型检查的流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FJ66pUil-1593450863736)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629170836676.png)]

1.2 多态命令的实现

对一个键执行LLEN命令,服务器除了要确保执行命令的键是列表键外,还得根据键的值对象使用的编码来选择正确的LLEN命令:

  • 如果列表对象的编码为ziplist,程序将使用ziplistLen函数来返回列表的长度;
  • 如果列表对象的编码为linkedlist,程序将使用listLength函数来返回双端链表的长度;

多态:

  • 一个命令可以用来处理不同类型的键,比如DEL、EXPIRE
  • 一个命令可以处理多种不同的编码,比如LLEN

在这里插入图片描述

二、内存回收

Redis使用引用计数实现内存回收机制:根据对象的引用技术信息,在适当的时候自动释放对象并进行内存回收

在这里插入图片描述
在这里插入图片描述

三、对象共享

共享对象机制可以节约内存

键A和键B共享整数值100的字符串

在这里插入图片描述

  • Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。
  • 创建共享字符串对象的数量可以通过修改redis.h/REDIS_SHARED_INTEGERS常量来修改

Redis为什么不讲字符串设为共享变量:

  • 设为共享变量前首先需要检查共享变量和键想要创建的目标对象是否相同
  • 如果共享对象保存的是整数值的字符串,验证操作的复杂度只要O(1)
  • 如果共享对象保存的是字符串值的字符串,那么验证操作的复杂度需要O(n)
  • 综上所述,对整数值的字符串进行共享使用的时间比较少

四、对象的空转时间

  • lru:记录了对象最后一次被命令程序访问的时间
  • lru可以通过OBJECT IDLETIME命令(该命令不会修改lru属性)打印,空转时长是将当前时间减去LRU时间计算的

在这里插入图片描述

lru作用:

  • 如果服务器占用的内存超过了maxmemory选项设置的上限值,并且Redis用于内存回收的算法是volatitle-lru或者allkeys-lru,则空转时间较高的那部分键会优先被服务器释放,回收内存。

总结

  • 服务器在执行某些命令之前,会先检查给定键的类型能否执行指定的命令,而检查一个键的类型就是检查键的值对象的类型。
  • Redis的对象系统带有引用计数实现的内存回收机制,当一个对象不再被使用时,该对象所占用的内存就会被自动释放。Redis会共享值为0到9999的字符串对象。
  • 对象会记录自己的最后一次被访问的时间,这个时间可以用于计算对象的空转时间。

第四部分:单机数据库

一、数据库

1.1 数据库结构

redis.h/redisDb

//数据库结构体
typedef struct redisDb{
	// ...
	
	// 数据库键空间,保存着数据库中的所有键值对
	dict *dict;
	
	// 过期字典,保存具有过期时间的键值对
	dict *expires;
	
	// ...
	
} redisDb;

redis.h/redisServer

//Redis服务端结构体
struct redisServer{

	// 1个数组,保存着服务器中的所有数据库
	redisDb *db;
	
	// 服务器的数据库数量(默认16个)
	int dbnum;
	
	// 记录RDB触发条件的save配置数组
	struct saveparam *saveparams
	
	//上一次RDB后数据库的改动次数
	long long dirty;
	
	
	// 上一次执行RDB的时间
	time_t lastsave;
	
	// AOF缓冲区
	sds aof_buf;
	
	// ...
	
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UsPoF11g-1593450863745)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629183429724.png)]

//Redis客户端结构体
typedef struct redisClient{
	
	// 记录客户端当前正在使用的数据库
	redisDb *db;
	
	// ...
	
}redisClient;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Tbt5MXC-1593450863746)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629183658093.png)]

SELECT命令:

  • 功能:切换数据库
  • 原理:通过更改redisClient中的db属性,让它指向不同的数据库

1.2 数据库键空间

​		[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbX6maj7-1593450863747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629184150263.png)]

  • 键空间的键也是数据库的键,每个键都是一个字符串对象
  • 键空间的值也是数据库的值,每个值可以是字符串对象、列表对象、哈希对象、集合对象、有序集合对象
  • 总结:数据库的键空间是一个字典,对数据库的添加、删除、获取某个键值对实际上都是通过键空间字典操作实现的
    • 对于清空整个数据库的FLUSHDB命令,就是通过删除键空间中的所有键值对实现的
    • 返回数据库键数量的DBSIZE,就是通过返回键空间中的键值对数量实现的
    • 随机返回数据库中某个键的命令RANDOMKEY,就是通过在键空间中随机返回一个键来实现的
    • 类似的还有EXISTS、RENAME、KEYS等命令都是通过对键空间操作实现的

读写键空间时的维护

  • 读取一个键后,服务器会根据键是否存在来更新命中(hit)次数和不命中(miss)次数
  • 读取一个键后,服务器会更新键的LRU时间,用于计算键的闲置时间
  • 读取键发现过期,会删除过期键
  • 修改一个键后,都会对脏(dirty)键计算器的值增一

1.3 过期键

1.3.1 设置键的过期时间

  • EXPIRE :设置键的过期时间时间为ttl秒
  • PEXPIRE :设置键的过期时间为ttl毫秒
  • EXPIREAT :将key的过期时间设置为timestap所指定的秒数时间戳
  • PEXPIREAT :将key的过期时间设置为timestap所指定的毫秒数时间戳
  • SETEX:可以在设置一个字符串键的同时设置过期时间(和EXPIRE原理一样)

注意:EXPIRE、PEXPIRE、EXPIREDAT三个命令最终都是转换成PEXPIREDAT命令来执行

在这里插入图片描述

1.3.2 过期键的保存

redisBd结构体中的expires字典(过期字典)保存了数据库所有键的过期时间:

  • 过期字典的键是一个指针,指向键空间的某个键对象
  • 过期字典的值是一个long long类型的整数,这个值保存了键所指向的键空间中的某个键对象的过期时间(毫秒精度的UNIX时间戳

1.3.3 移除过期时间

PERSIST命令:

  • 功能:移除一个键的过期时间
  • 实现方式:在过期字典中查找指定的键,并解除键和值(过期时间)在过期字典中的关联

1.3.4 计算键的过期时间

  • TTL命令:以秒为单位返回键的剩余时间
  • PTTL命令:以毫秒为单位返回键的剩余时间

过期时间计算:

  1. 查找过期字典中键对应的过期时间戳
  2. 过期时间戳 减去 当前的时间戳
    • 如果减去的结果大于等于0:说明该键未过期
    • 如果减去的结果小于0:说明该键过期

1.3.5 过期键的删除策略

  1. 定时删除:通过使用定时器,键一过期就删除;
    • 对CPU时间不友好,如果存在大量过期键将会影响服务器的响应时间和吞吐量
    • 创建定时器需要用到Redis事件中的时间时间,而当前时间事件的实现方式是无序列表,查找一个时间事件的复杂度是O(N)
  2. 惰性删除:过期后不立即删除,等下一次访问时删除
    • 如果有很多过期键,且永远不会被访问则不会被删除,会造成类似内存泄漏的结果
  3. 定期删除:轮询16个数据库的过期字典,默认随机抽取20个键,如果过期的键超过了25%,则重新抽取该数据库过期字典中20个键判断是否过期;如果过期的键没有超过25%,则对下一个数据库的过期字典进行抽取。

Redis使用的时惰性删除定期删除

惰性删除的实现:

  • 由db.c/expireIfNeeded函数判断键是否过期

在这里插入图片描述

定期删除的实现:

  • redis.c/serverCron执行时会调用redis.c/activeExpireCycle遍历服务器中的各个数据库,从数据库的expires字典中随机抽查一部分键的过期时间,并删除过期键
  • 全局变量current_db记录当前activeExpireCycle函数检查的进度到哪个数据库

1.3.6 RDB、AOF对过期键的处理

  • 生成RDB文件:

​ 执行SAVA或者BGSAVE命令生成新RDB文件时,程序会对数据库中的键进行检查,已过期的键不会保存到新的RDB文件

  • 载入RDB文件:

    • 如果服务器以主节点运行:载入RDB文件时,会对过期键进行检查,确保过期的键不会被加载到数据库
    • 如果服务器以从节点运行:载入RDB文件时,不论键是否过期,全部加载到数据库,因为主从服务器会进行数据同步,从服务器的过期键会被清空,所以过期键载入从服务器的数据库没有影响
  • AOF文件写入:

​ 当服务器以AOF持久化运行,数据库的某个键过期但还没有被惰性删除或者定期删除,AOF文件不会因此产生影响,当被惰性或定期删除后,AOF文件会追加一条DEL命令来显示地删除该键

  • AOF重写:

​ 执行AOF重写过程,程序会对数据库进行检查,将已过期的键不会被保存到重写后的AOF文件

​ 比如:数据库由k1,k2,k3三个键,k2过期,那么重写时,程序只会对k1、k3重写,而k2会被忽略

  • 复制:
    • 主服务器在删除一个过期键后,会显示的向从服务器发送DEL命令,通知从服务器删除过期键
    • 从服务器在执行客户端发送的命令时,即使碰到过期键也不会将它删除,而是像处理未过期的键一样来处理它
    • 从服务器只有接收到主服务器发送的DEL命令才会删除过期键

总结

  • Redis服务器的所有数据库都保存在redisServer.db数组中,而数据库的数量则由redisServer.dbnum属性保存。
  • 客户端通过修改目标数据库指针,让它指向redisServer.db数组中的不同元素来切换不同的数据库。
  • 数据库主要由dict和expires两个字典构成,其中dict字典负责保存键值对,而expires字典则负责保存键的过期时间。
  • 因为数据库由字典构成,所以对数据库的操作都是建立在字典操作之上的。
  • 数据库的键总是一个字符串对象,而值则可以是任意一种Redis对象类型,包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象,分别对应字符串键、哈希表键、集合键、列表键和有序集合键。
  • expires字典的键指向数据库中的某个键,而值则记录了数据库键的过期时间,过期时间是一个以毫秒为单位的UNIX时间戳。
  • Redis使用惰性删除和定期删除两种策略来删除过期的键:惰性删除策略只在碰到过期键时才进行删除操作,定期删除策略则每隔一段时间主动查找并删除过期键。
  • 执行SAVE命令或者BGSAVE命令所产生的新RDB文件不会包含已经过期的键。❑执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已经过期的键。
  • 当一个过期键被删除之后,服务器会追加一条DEL命令到现有AOF文件的末尾,显式地删除过期键。
  • 从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。
  • 当Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知

二、RDB持久化

  • RDB是生成一个二进制文件保存在硬盘,是当前数据库的快照

2.1 RDB三种方式:

  • SAVE命令:阻塞进程,直到RDB文件创建完毕为止,不推荐使用
  • BGSAVE命令:创建子进程负责创建RDB文件
    • BGSAVE执行时,客户端的SAVE命令会被拒绝执行,阻止SAVE和BGSAVE同时执行是为了避免父进程和子进程同时调用rdbSave操作,防止产生竞争条件
    • BGSAVE执行时,客户端发送的BGSAVE命令会被拒绝执行,因为同时执行两个BGSAVE也会产生竞争条件
    • BGSAVE和BGREWRITEAOF两个命令不能同时执行:这两个命令没有什么冲突,两个命令实际都是两个子进程执行,不能同时执行是处于性能考虑,这两个子进程都在执行大量的磁盘写入操作
      • 如果BGSAVE正在执行,则BGREWRITEAOF则会延迟到BGSAVE完成再执行
      • 如果BGREWRITEAOF正在执行,客户端发送的BGSAVE命令会被拒绝执行
  • SAVE配置:使用的是BGSAVE命令

RDB载入工作:rdb.c/rdbreLoad函数完成

注意:如果服务器开启了AOF持久化功能,会优先使用AOF文件来还原数据库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pN7mihh3-1593450863750)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629202001659.png)]

2.2 设置RDB执行条件

配置文件设置save选项

  • save 900 1

  • save 300 10

  • save 60 10000

  • 服务器程序会根据配置文件初始化redisServer的saveparams数组

  • 数组的每一个元素saveparam是一个save配置,触发RDB的条件

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.3 dirty计数器和lastsave属性

  • ditry计数器:long long类型,记录离上一次执行SAVE命令或BGSAVE命令后,服务器对数据库状态执行了几次修改(写入、更新、删除等操作)
  • lastsave属性:是一个UNIX时间戳,记录上一次成功执行SAVE命令或BGSAVE命令的时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BsOlNdIi-1593450863757)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629215829659.png)]

2.4 检查RBD执行条件是否满足

Redis周期性操作函数serverCron默认每个100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否满足,如果满足则执行BGSAVE命令:

  1. 遍历所有save保存条件
  2. 计算离上一次执行RDB有多少秒:用现在的时间戳减去lastsave(上一次执行RDB的时间)
  3. 如果数据库状态的修改次数dirty大于等于配置条件的修改次数 而且 距上次执行RDB的时间 大于 配置条件的时间,则执行BGSAVE操作,执行完dirty重置为0,lastsave更新为现在的时间

2.5 RDB文件结构

大写表示常量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVX7tfwv-1593450863759)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629220901199.png)]

  • REDIS:长度5字节,保存着“REDIS”五个字符,载入文件时,通过这五个字符快速检查载入的是否RDB文件
  • db_version:长度4字节,值是一个字符串表示的整数,记录RDB文件的版本号,比如“0006“就代表RDB文件的版本为第六版
  • databases:包含0个或多个数据库,以及各个数据库中的键值对数据
    • 如果服务器的数据库状态为空(所有数据库都是空),那么这个部分也是为空,长度为0字节
    • 如果服务器的数据库状态为非空(至少一个数据库非空),那么这个部分也非空,根据数据库保存的键值对的数量、类型、内容不同,这个部分的长度也不同
  • EOF:长度为1字节的常量,这个常量标志RDB文件正文内容结束。读入程序遇到EOF就知道所有数据库的键值对dd偶已经载入完毕。
  • check_sum:8字节长的无符号整数,保存校验和,是程序通过REDIS、db_version、databases、EOF四个部分计算出,载入的时候所计算的校验和与check_sum记录的校验和进行比较判断RDB文件是否出错或者损坏

databases部分:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qs732jOH-1593450863760)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629223224406.png)]

  • SELECTDB:1字节长度的常量,它直到接下来将要读取一个数据库的号码db_number
  • db_number:保存一个数据库号码(共16个号码),这个部分的长度可以实1字节、2字节、5字节。根据该字段调用SELECT命令切换数据库使之后读入的键值对可以载入到正确的数据库中。
  • key_value_paires:保存数据库中的所有键值对,如果键值对带有过期时间,那么过期时间也会和键值对保存在一起。

(包含0号数据库和3号数据库)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nT5Mf8oB-1593450863761)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629223949002.png)]

key_value_paires

保存一个或以上数量的键值对

在这里插入图片描述

  • TYPE:记录value的类型,长度为1字节,程序根据TYPE的值决定如何读入和解释value的数据。
    • REDIS_RDB_TYPE_STRING
    • REDIS_RDB_TYPE_LIST
    • REDIS_RDB_TYPE_SET
    • REDIS_RDB_TYPE_ZSET
    • REDIS_RDB_TYPE_HASH
    • REDIS_RDB_TYPE_LIST_ZIPLIST
    • REDIS_RDB_TYPE_SET_INTSET
    • REDIS_RDB_TYPE_ZSET_ZIPLIST
    • REDIS_RDB_TYPE_HASH_ZIPLIST
  • key:字符串对象
  • value:类型是REDIS_RDB_TYPE_STRING,见下文
  • EXPIRETIME_MS:1字节常量,告知读入程序接下来读入的将是一个以毫秒为单位的过期时间
  • ms:8字节无符号整数,键值对的过期时间,以毫秒为单位的UNIX时间戳

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ch2VleVf-1593450863766)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629225630804.png)]

value编码

  • 字符串对象:如果Type是REDIS_RDB_TYPE_STRING,value可以是REDIS_ENCODING_INT或者REDIS_ENCODING_RAW

    REDIS_ENCODING_INT

    • ENCODING的值可以是REDIS_RDB_ENC_INT8、REDIS_RDB_ENC_INT16、REDIS_RDB_ENC_INT32

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2LQpKocw-1593450863767)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629231043581.png)]

    REDIS_ENCODING_RAW:

    • 字符串长度小于等于20字节:字符串原样保存
    • 字符串大于20字节:字符串会被压缩后保存
    • 注意:以上两个需要在打开RDB文件压缩的功能才可以,参考配置文件rdbcompression选项的说明

    在这里插入图片描述

​ REDIS_RDB_ENC_LZF:该常量表示字符串被LZF算法压缩了,读入程序会根据compressed_len(压缩后的长 度)、origin_len(压缩前的长度)进行解压缩

在这里插入图片描述

  • 列表对象:如果Type是REDIS_RDB_TYPE_LIST,那么value保存的是REDIS_ENCODING_LINKEDLIST编码的列表对象:

    在这里插入图片描述

  • 集合对象:如果Type是REDIS_RDB_TYPE_SET,那么value保存的是一个REDIS_ENCODING_HT编码的集合对象:
    在这里插入图片描述

  • 哈希表对象:如果Type是REDIS_RDB_TYPE_HASH

    在这里插入图片描述

  • 有序集合对象:如果Type是REDIS_RDB_TYPE_ZSET

    在这里插入图片描述

2.6 分析RDB文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D5Xgwlo1-1593450863777)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200629233911364.png)]
在这里插入图片描述

总结

  • ❑RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据。
  • ❑SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。
  • ❑BGSAVE令由子进程执行保存操作,所以该命令不会阻塞服务器。
  • ❑服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行BGSAVE命令。
  • ❑RDB文件是一个经过压缩的二进制文件,由多个部分组成。
  • ❑对于不同类型的键值对,RDB文件会使用不同的方式来保存它们。

三、AOF持久化

Appedn Only File:通过保存Redis服务器所执行的写命令来记录数据库状态

3.1 AOF持久化实现

3.1.1 命令追加:

AOF持久化打开时,服务器执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区

在这里插入图片描述

3.1.2 AOF文件的写入和同步

  • Redisd的服务器进程是一个事件循环,其中文件事件负责接收客户端的命令请求以及向客户端回复,而时间事件则负责像serverCorn函数这样需要定时运行的函数

  • 服务器每结束一次事件循环之前,都会调用flushAppendOnlyFile函数考虑是否将AOF缓存区的内容写到AOF文件

  • flushAppendOnlyFile行为由appendfsync的值决定

三种策略对比:

  • always(每次):每次写入操作都将AOF缓冲区的所有内容写道AOF文件,并且同步AOF文件,效率最慢,数据几乎0误差,最多只会丢失一个事件循环中所产生命令的数据
  • everysec(每秒):每秒将AOF缓冲区的内容写到AOF文件,性能较高,最多丢失1秒钟的数据
  • no(系统控制):由操作系统控制何时对AOF文件进行同步

在这里插入图片描述

3.2 AOF文件的载入与还原

AOF文件还原数据库过程:

  1. 创建一个不带网络连接的伪客户端(因为Redis命令只能在客户端上下文中执行)
  2. 从AOF文件中分析读取出一条写命令
  3. 使用伪客户端执行被读出的写命令
  4. 一直执行步骤2和步骤3,知道AOF文件的所有命令都处理完。

3.2 AOF重写

随着服务器的运行,AOF的文件会越来越大,创建一个新的AOF文件对旧的AOF文件的冗余命令进行重写

AOF重功能的实现原理:

  • 为了减少AOF文件命令的数量,首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录的多个键值对的多条命令。
  • **注意:**为了避免客户端输入缓冲区溢出,重写在处理列表、哈希表、集合、有序集合等含有多个元素的键时,会先检查键所包含的元素数量,如果数量超过了redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD常量的值,那么重写程序将使用多条命令来记录键的值,而不单单使用一条命令

Redis将AOF重写程序放到子进程执行:

  • 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求。
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

AOF重写缓冲区:

  • 在服务器创建子进程后开始使用
  • 当Redis执行完一个写命令后,会同时将命令发送到AOF缓冲区和AOF重写缓冲区
  • 当子进程完成AOF重写工作后,向父进程发送一个信号,父进程收到信号后执行信号处理函数:
    • 将AOF重写缓冲区的所有内容写到新AOF文件,这是新AOF文件保存的数据库状态和当前的数据库状态一致
    • 对新AOF文件进行改名,原子的覆盖现有的AOF文件,完成新旧AOF文件的替换。

在这里插入图片描述

总结

  • AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
  • AOF文件中的所有命令都以Redis命令请求协议的格式保存。
  • 命令请求会先保存到AOF缓冲区里面,之后再定期写入并同步到AOF文件
  • appendfsync选项的不同值对AOF持久化功能的安全性以及Redis服务器的性能有很大的影响。
  • 服务器只要载入并重新执行保存在AOF文件中的命令,就可以还原数据库本来的状态。
  • AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。
  • AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作。
  • 在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。
展开阅读全文

Redis 服务器管理(集群主从复制及高可用)

08-30
Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。     本课程主要讲解以下内容: 1. Redis的基本使用 2. Redis数据库的数据类型 3. Redis数据库数据管理 4. Redis的主从复制 5. Redis数据库的持久性 6. Redis的高可靠性和集群 7. Redis的优化和性能测试 8. Redis服务器的维护和管理 9. Redis服务器的常见问题排错  

C#/.Net企业级系统架构设计实战精讲教程

12-03
课程通过实际项目融入常用开发技术架构,讲授风格独特,提供详细上课日志及答疑,赠送配套的项目架构源码注释详细清晰且表达通俗,均能直接在实际项目中应用,正真的物超所值,价格实惠 任务作业: 综合运用《C#/.Net企业级系统架构设计实战精讲教程》课程所学知识技能设计一个学生成绩管理系统的架构。要求: 1.系统基于MVC的三层架构,各层单独建不同的解决方案文件夹。 2.采用Model First开发方式,设计架构时只需要设计学生表(TbStudent)和课程表(TbCourse)。学生表必须有的字段是ID、stuName、age;课程表必须有的字段是ID、courseName、content。 3.数据访问层采用Entity Framework或NHibernate来实现,必须封装对上述表的增删改查方法。 4.必须依赖接口编程,也就是必须要有数据访问层的接口层、业务逻辑层的接口层等接口层。层层之间必须减少依赖,可以通过简单工厂或抽象工厂。 5.至少采用简单工厂、抽象工厂、Spring.Net等技术中的2种来减少层与层之间的依赖等。 6.封装出DbSession类,让它拥有所有Dal层实例和SaveChanges方法。 7.设计出数据访问层及业务逻辑层主要类的T4模板,以便实体增加时自动生成相应的类。 8.表现层要设计相关的控制器和视图来验证设计的系统架构代码的正确性,必须含有验证增删改查的方法。 9.开发平台一定要是Visual Studio平台,采用C#开发语言,数据库为SQL Server。 10.提交整个系统架构的源文件及生成的数据库文件。 (注意: 作业需写在CSDN博客中,请把作业链接贴在评论区,老师会定期逐个批改~~)

Git 实用技巧

11-24
这几年越来越多的开发团队使用了Git,掌握Git的使用已经越来越重要,已经是一个开发者必备的一项技能;但很多人在刚开始学习Git的时候会遇到很多疑问,比如之前使用过SVN的开发者想不通Git提交代码为什么需要先commit然后再去push,而不是一条命令一次性搞定; 更多的开发者对Git已经入门,不过在遇到一些代码冲突、需要恢复Git代码时候就不知所措,这个时候哪些对 Git掌握得比较好的少数人,就像团队中的神一样,在队友遇到 Git 相关的问题的时候用各种流利的操作来帮助队友于水火。 我去年刚加入新团队,发现一些同事对Git的常规操作没太大问题,但对Git的理解还是比较生疏,比如说分支和分支之间的关联关系、合并代码时候的冲突解决、提交代码前未拉取新代码导致冲突问题的处理等,我在协助处理这些问题的时候也记录各种问题的解决办法,希望整理后通过教程帮助到更多对Git操作进阶的开发者。 本期教程学习方法分为“掌握基础——稳步进阶——熟悉协作”三个层次。从掌握基础的 Git的推送和拉取开始,以案例进行演示,分析每一个步骤的操作方式和原理,从理解Git 工具的操作到学会代码存储结构、演示不同场景下Git遇到问题的不同处理方案。循序渐进让同学们掌握Git工具在团队协作中的整体协作流程。 在教程中会通过大量案例进行分析,案例会模拟在工作中遇到的问题,从最基础的代码提交和拉取、代码冲突解决、代码仓库的数据维护、Git服务端搭建等。为了让同学们容易理解,对Git简单易懂,文章中详细记录了详细的操作步骤,提供大量演示截图和解析。在教程的最后部分,会从提升团队整体效率的角度对Git工具进行讲解,包括规范操作、Gitlab的搭建、钩子事件的应用等。 为了让同学们可以利用碎片化时间来灵活学习,在教程文章中大程度降低了上下文的依赖,让大家可以在工作之余进行学习与实战,并同时掌握里面涉及的Git不常见操作的相关知识,理解Git工具在工作遇到的问题解决思路和方法,相信一定会对大家的前端技能进阶大有帮助。
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值