第一章 引言
数据库的键总是一个字符串对象,键的值可以是字符串、列表、哈希、集合、有序集合。
第一部分 数据结构与对象
第二章 简单动态字符串
Redis自己构建了一种名为简单动态字符串(SDS)的抽象类型。包含字符串的键值对在底层是由SDS实现的。
2.1 SDS的定义
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
2.2 SDS与C字符串的区别
C语言使用N+1的字符数组标识长度N的字符串,字符数组的最后一个元素总是空字符‘\0’
2.2.1常数复杂度获取字符串长度
C字符串不记录长度,获取长度需要遍历字符串,复杂度O(N)。
SDS在len属性中记录了长度,获取长度复杂度O(1)。
2.2.2杜绝缓冲池区溢出
C字符串在拼接字符时可能会产生缓冲区溢出;SDS会先检查空间是否满足要求,空间不够先分配空间再做操作。
2.2.3 减少修改字符串时带来的内存重分配次数
增长或缩短C字符会对C字符串进行内存重分配:增长字符串会先扩展底层数组空间大小,缩短字符串释放字符串不用的空间。
SDS实现了空间预分配和惰性空间释放两种优化策略。
-
空间预分配:需要空间扩展时不仅会分配修改所需要的空间,还会为SDS分配额外的未使用的空间。SDS小于1MB会分配和len大小相等的空间。大于等于1MB会分配1MB的未使用空间。
-
惰性空间释放 用于优化SDS的字符串缩短操作:缩短字符串后并不立即回收,而是用free记录下来。
-
二进制安全 SDS api会以二进制方式来处理SDS存放在buf数组里的数据。
-
兼容部分C字符串函数 第三章 链表 当一个列表键包含了数量比较多的元素或者列表中包含的元素都是比较长的字符串时,reids会使用链表作为列表键的底层实现。 3.1链表和链表节点的实现
添加图片注释,不超过 140 字(可选)
链表特性:双端、无环、带表头表尾指针、带链表长度计数器、多态。 第四章 字典 是一种保存键值对的抽象数据结构,每个键都是唯一的,redis数据库就是使用字典来实现的,对数据库增删改查是在字典的操作之上的。 当一个哈希键包含的键值对比较多或者键值对中的元素都是比较长的字符串时,redis就会使用字典作为哈希键的底层实现。 4.1字典的实现 4.1.1哈希表 字典使用哈希表作为底层实现,一个哈希表里可以有多个哈希节点,每个哈希节点保存了字典的一个键值对。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
4.1.2哈希表节点 使用dictEntry结构表示
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
4.1.3字典
添加图片注释,不超过 140 字(可选)
ht属性是一个包含两个项的数组,数组的每个项都是一个dictht哈希表,一般情况下字典只用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
添加图片注释,不超过 140 字(可选)
4.2哈希算法 新的键值添加到字典时程序需要先根据键值对应的键计算出哈希值和索引值,再根据索引值将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。 4.3解决键冲突 当有两个或以上的键被分配到同一个索引上面,此时发生了冲突。发生冲突时将新节点放在表头位置。
添加图片注释,不超过 140 字(可选)
4.4rehash 为了让哈希表的负载因子维持在一个合理的范围内,当哈希表保存的键值对数量太多或者太少时需要对哈希表进行扩展或者收缩。 Rehash步骤:为ht[1]分配空间,将ht[0]中所有的键值对rehash到ht[1]上,迁移完成后将ht[1]设置为ht[0],并在ht[1]上创建空白哈希表,为下一次rehash做准备。 满足以下任意一个条件时程序会自动开始对哈希表执行扩展操作:1.服务器没有在执行bgsave命令或者bgrewriteaof命令,并且负载因子大于等于2. 服务器正在执行bgsave命令或者bgrewriteaof命令并且负载因子大于等于5。 4.5渐进式rehash Rehash需要分多次执行。 执行步骤1.为ht【1】分配空间 2.维持一个计索引计数器变量设置为0 3.rehash期间对字典进行添加查找删除或者更新操作时程序除了执行指定操作以外 还会顺带将ht【0】哈希表在计数器上的所有键值对rehash到ht【1】,rehash完成后将计数器属性加一。4.当ht【0】所有键值对rehash到ht【1】上时,将计数器置为-1. Rehash期间字典会同时使用ht【0】和ht【1】两个哈希表,ht【0】没找到会继续查找ht【1】,这期间新添加的字典值会被保存到ht【1】里面。 第五章 跳跃表 有序的数据结构,通过在每个节点中维持多个指向其他节点的指针达到快速访问的目的。 5.1跳跃表的实现
添加图片注释,不超过 140 字(可选)
5.1.1跳跃表节点 1.层:每次创建一个新的跳跃节点时,程序会随机生成一个1-32之间的值作为level数组大小(高度)。 2.前进指针:每个层有一个指向表尾方向的前进指针,用于从表头向表尾方向访问节点。 3.跨度:记录两个节点间的距离 4.后退指针:从表尾向表头方向访问节点 5.分值和成员:分值是一个double类型的浮点数,跳跃表中的所有节点按分值从小到大排列。成员对象是一个指针,指向字符串对象,字符串对象保存着SDS值。 5.1.2跳跃表 使用zskiplist持有这些节点,方便地对整个跳跃表进行处理,如快速访问跳跃表的表头和表尾节点,或者快速获取跳跃表节点的数量。 第六章 整数集合 6.1 整数集合的实现 用于保存整数值的集合抽象数据结构,集合中不会出现重复元素。
添加图片注释,不超过 140 字(可选)
整数集合每个元素都是Contents数组的一个数组项,各个项在数组中从小到大有序排列。 6.2升级 每当要添加新元素到整数集合里面,并且新元素的类型比整数集合现有所有的元素都长时,需要升级。 6.3升级的好处 6.3.1提升灵活性 可以随意将不同类型的整数添加到集合中,不必担心出现类型错误。 6.3.2节约内存 需要的时候才会进行升级。 6.4降级 不支持降级 第七章 压缩列表 当一个列表键包含少量列表项,每个列表项要么是小整数值,要么是比较短的字符串,redis会使用压缩列表来做底层实现。 7.1压缩列表的构成
添加图片注释,不超过 140 字(可选)
7.2压缩列表节点的构成 每个节点可以保存一个字节数组或者一个整数值,每个压缩列表节点由previous_entry_length、encoding、content三个部分组成 7.2.1 previous_entry_length 以字节为单位,记录了压缩列表中前一个节点的长度。可以计算出前一个节点的地址。 7.2.2 encoding 记录了content属性保存的数据类型以及长度 7.2.3 content 负责保存节点的值。 7.3连锁更新 添加、删除节点可能会引发连锁更新。 第八章 对象 8.1对象的类型与编码 Reids使用对象表示数据库中的键和值,创建一个键值对至少会创建两个对象,一个用作键一个用作值。每个对象都由一个reisObject结构表示。
添加图片注释,不超过 140 字(可选)
8.1.1 类型 Type记录了对象的类型。
添加图片注释,不超过 140 字(可选)
键总是字符串对象,值可以是字符串、列表、哈希、集合、有序集合。
添加图片注释,不超过 140 字(可选)
8.1.2编码和底层实现 对象的ptr指针指向对象的底层实现数据结构,这些数据结构由对象的encoding决定。 Object encoding可以查看一个数据库键的值对象编码。 8.2 字符串对象 编码可以是int、raw、embstr。 8.2.1编码的转换 Int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下会被转换为raw编码的字符串对象。 8.2.2字符串命令的实现 8.3列表对象 编码可以是ziplist或者linkedlist。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
8.3.1编码转换 当列表对象可以同时满足1.列表对象保存的所有字符串长度都小于64字节2.列表对象保存的元素数量都小于512个时,列表使用ziplist编码。其余情况使用linkedlist。 8.3.2列表命令的实现
添加图片注释,不超过 140 字(可选)
8.4 哈希对象 编码可以是ziplist或者hashtable。有新键值对加入哈希对象时,程序会先将保存了键的压缩列表节点推入压缩列表表尾,再将值推入表尾。 8.4.1编码转换 哈希对象保存的键和值字符串长度都小于64字节且数量小于512个时使用ziplist,否则使用hashtable。 8.5集合对象 编码可以是intset或者hashtable,intset集合对象使用整数作为底层实现,hashtable使用字典作为底层实现。
添加图片注释,不超过 140 字(可选)
8.5.1 编码的转换 当对象元素都是整数且元素数量不超过512个,使用intset编码,不满足时使用hashtable。 8.6 有序集合对象 编码1可以是ziplist或者skiplist。 Ziplist编码使用压缩列表来做底层实现,第一个节点保存元素的成员、第二个保存元素的分值,集合元素按分值从小到大排序。 Skiplist使用zset作为底层实现,同时包含一个字典和一个跳跃表。
添加图片注释,不超过 140 字(可选)
8.6.1编码的转换 有序结合保存元素数量少于128个且长度都小于64字节使用ziplist编码,否则使用skiplist编码。 8.7类型检查与命令多态 8.7.1类型检查的实现 通过redisObject结果的type来实现的。
添加图片注释,不超过 140 字(可选)
8.7.2多态命令的实现 会根据值对象的编码方式选择正确的命令来实现代码执行命令。如llen、del、expire。
添加图片注释,不超过 140 字(可选)
8.8内存回收 构建引用计数器来实现内存回收。
添加图片注释,不超过 140 字(可选)
8.9对象共享 多个键共享一个值对象:1.将数据库指针指向一个现有的值对象。2.将被共享的值对象的引用计数器加一。可以节约内存。
添加图片注释,不超过 140 字(可选)
8.10 对象的空转时长 RedisObject的lru属性记录了对象最后一次被命令程序访问的时间。 第二部分 单机数据库的实现 第九章 数据库 9.1服务器中的数据库 Reids默认创建16个数据库,db记录当前的目标数据库。
添加图片注释,不超过 140 字(可选)
9.2切换数据库 默认模板数据库为0号数据库,可以通过select命令切换数据库。 9.3 数据库键空间 9.3.1 添加新键 添加一新键值对到数据库就是将一个新键值对添加到字典里面。 9.3.2 删除键 删除数据库的一个键,就是删除对应的键值对对象。 9.3.3更新键 对键空间里面键所对应的值对象进行更新。 9.3.4对键取值 在键空间取出键所对应的值对象。 9.3.5 其他键空间操作 Flushdb:删除键空间所有键值对; Randomkey:在键空间中随机返回一个键; 9.3.6 读写键空间时的维护操作 对数据库进行读写时,不仅会对键空间执行指定的读写操作,还会额外执行一些额外的维护操作。 读取一个键后会更新键空间命中和不命中次数。 读取一个键会更新lru时间。 读取时发现键已过期会删除这个过期键。 被Watch命令监视的键,在修改后会标记为脏。 每次修改一个键后,会对脏键计数器的值增1. 如开起来数据库通知,在对键修改后会按配置发送相应的数据库通知。 9.4设置键的生存时间或过期时间 通过expire或者pexpire
添加图片注释,不超过 140 字(可选)
Expireat或者pexpireat设置过期时间。 Ttl、pttl返回键的剩余生存时间。 9.4.1 设置过期时间
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
三个命令都是由pexpireat实现的。
添加图片注释,不超过 140 字(可选)
9.4.2 保存过期时间 Expires字典保存了数据库中所有键的过期时间-过期字典。 过期字典的键是一个指针,指向键空间的某个键对象。 过期字典的值是一个long类型的整数保存了过期时间
添加图片注释,不超过 140 字(可选)
9.4.3 移除过期时间 Persist可以移除一个键的过期时间。 9.4.4 计算并返回剩余生存时间 TTL命令以秒返回键的剩余生存时间,而PTTL命令以毫秒为单位返回键的剩余生存时间。 9.4.5 过期键的判定 1.检查给定键是否存在于过期字典,存在则获取过期时间。 2.检查当前unix时间戳是否大于键的过期时间,是-》键已过期,否则-》未过期。 9.5过期键删除策略 9.5.1 定时删除 设置过期时间时创建一个定时器,在过期时间来临时立即执行删除操作。 缺点:占用cpu时间。 9.5.2 惰性删除 每次获取键时检查是否过期,过期则删除, 没有过期就返回该键。 缺点:占用大量内存。 9.5.3 定期删除 每隔一段时间对数据库执行一次检查,删除里面的过期键。 9.6 Redis的过期键删除策略 9.6.1 惰性删除策略的实现
添加图片注释,不超过 140 字(可选)
9.6.2 定期删除策略的实现 每次运行都从一定数量的数据库中取出一定数量的随机键进行检查,删除其中的过期键。 记录当前进度,下一次调用时接着上一次的进度进行处理。 检查一遍后,记录进度的变量置0,开始新一轮的检查。 9.7AOF、RDB和复制功能对过期键的处理 9.7.1生成RDB文件 执行save或bgsave命令创建一个新的rdb文件时,程序会对数据库中的键进行检查、已过期的键不会被保存到新创建的rdb文件中。 9.7.2 载入RDB文件 服务器以主服务器模式运行载入RDB文件时会忽略过期键。 以从服务器模式运行时,会保存所有键。 9.7.3 AOF文件写入 以AOF模式运行时,如果键过期还没被删除,那么AOF文件不会因为这个过期键产生影响。当过期键被删除后,程序会向AOF文件追加DEL命令。 9.7.4 AOF重写 已过期的键不会保存到重写后的AOF文件中。 9.7.5 复制 服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制。 主服务器删一个过期键时会显式的向所有从服务器发送一个DEL命令,让从服务器删除这个过期键。 从服务器执行读命令时,碰到过期键也不删除。 只有接收到主服务器的DEL命令后,才会删除过期键。 9.8数据库通知 9.8.1发送通知
添加图片注释,不超过 140 字(可选)
Type是当前想要发送的通知类型。当redis命令需要发送数据库通知时,该命令的实现函数会调用notifyKeyspaceEvent函数。 9.8.2发送通知的实现 检测type是不是允许发送的类型;检测服务器是否允许发送键空间通知;检测服务器是否允许发送键事件通知。 第十章 RDB持久化
添加图片注释,不超过 140 字(可选)
10.1 RDB文件的创建与载入 Save和bgsave可以生成rdb文件,save会阻塞redis服务器进程知道rdb文件创建完毕。Bgsave会派生出一个子进程创建rdb。
添加图片注释,不超过 140 字(可选)
10.1.1 save命令执行时的服务器状态 执行时服务器会被阻塞。 10.1.2·bgsave命令执行时的服务器状态 执行期间save、bgsave命令都会被拒绝。 10.1.3 rdb文件载入时的服务器状态 载入rdb文件期间,会一直处于阻塞状态。 10.2 自动间隔性保存 可以通过save选项设置多个保存条件只要有一个被满足就执行bgsave。 10.2.1 设置保存条件
添加图片注释,不超过 140 字(可选)
10.2.2 dirty计数器和lastsave属性 Dirty计数器记录上一次成功执行save或者bgsave命令后服务器对数据库的状态进行了多少次修改。 Lastsave属性记录了上一次成功执行save、bgsave命令的时间。 10.2.3 检查保存条件是否满足 ServerCron会100ms检查一次save选项设置的条件是否满足。 10.3 rdb文件结构
添加图片注释,不超过 140 字(可选)
Redis:表明是rdb文件 Db_version:版本号 Database:数据库和数据库中的键值对 EOF:标志着RDB文件结束 Check_sum:校验和 10.3.1 databases部分
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
10.3.2 key_value_pairs部分
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
10.3.3 value的编码 1.字符串对象
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
2.列表对象
添加图片注释,不超过 140 字(可选)
第一部分记录长度,其余代表列表的项。 3.集合对象
添加图片注释,不超过 140 字(可选)
第一部分代表集合大小,剩下代表集合的元素。 4.哈希对象
添加图片注释,不超过 140 字(可选)
第一部分代表大小,后面是键值对。
添加图片注释,不超过 140 字(可选)
5.有序集合
添加图片注释,不超过 140 字(可选)
第一部分代表大小,后面是集合的元素,元素分为成员和分值。 6.INTSET编码的集合 转为字符串对象再保存到rdb文件里面。 7.ZIPLIST编码的列表、哈希或者有序集合 压缩列表保存方法:1,先转成字符串2.存到rdb文件 10.4 分析rdb文件 10.4.1不包含任何键值对的rdb文件
添加图片注释,不超过 140 字(可选)
10.4.2包含字符串键的rdb文件
添加图片注释,不超过 140 字(可选)
10.4.3 包含带有过期时间的字符串键的rdb文件
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
10.4.4 包含一个集合键的rdb文件
添加图片注释,不超过 140 字(可选)
10.4.5 关于分析rdb文件的说明 第十一章 AOF持久化 AOF通过保存redis服务器所执行的写命令来记录数据库状态。
添加图片注释,不超过 140 字(可选)
11.1 AOF持久化的实现 11.1.1 命令追加 服务器执行完一个写命令后会追加到缓冲区的末尾。 11.1.2 AOF文件的写入与同步
添加图片注释,不超过 140 字(可选)
11.2 AOF文件的载入与数据还原
添加图片注释,不超过 140 字(可选)
11.3 AOF重写 11.3.1 AOF文件重写的实现 先从数据库读取现在的值,然后用一条命令去记录键值对代替之前记录这个键值对的多条命令。 11.3.2 AOF后台重写 重写aof文件期间无法处理客户端的请求。开启子进程写,并且创建缓冲区服务器在执行完写命令后会将这个命令发送AOF缓冲区和AOF重写缓冲区,来解决子进程重写时主进程数据改变导致不一致的问题。
添加图片注释,不超过 140 字(可选)
第12章 事件 12.1 文件事件 文件事件处理器使用I/O多路复用程序来监听多个套接字。 12.1.1 文件事件处理器的构成
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
12.1.2 I/O多路复用程序的实现
添加图片注释,不超过 140 字(可选)
12.1.3 事件的类型 分为AE_READABLE事件和AE_WRITABLE事件。两个同时出现优先处理读事件。 12.1.5 文件事件的处理器 1.连接应答处理器 Redis服务器进行初始化的时候,会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来。客户端连接服务器监听套接字时引发处理器执行。 2.命令请求处理器
添加图片注释,不超过 140 字(可选)
-
命令回复处理器
添加图片注释,不超过 140 字(可选)
-
一次完整的客户端与服务器连接事件案例
添加图片注释,不超过 140 字(可选)
12.2 时间事件
Redis时间事件分为两类定时事件、周期性事件。
12.2.1实现
事件放在无序链表中,每次遍历链表。
添加图片注释,不超过 140 字(可选)
serverCron函数:
添加图片注释,不超过 140 字(可选)
12.3 事件的调度与执行
添加图片注释,不超过 140 字(可选)
第13章 客户端
添加图片注释,不超过 140 字(可选)
13.1客户端属性
13.1.1 套接字描述符
Fd属性记录了客户端正在使用的套接字描述符。
13.1.2 名字
可以为客户端设置名字。
添加图片注释,不超过 140 字(可选)
13.1.3 标志
Flags记录了客户端的角色、客户端目前所处状态。
13.1.4 输入缓冲区
用户保存客户端发送的命令请求。
添加图片注释,不超过 140 字(可选)
13.1.5 命令与命令参数
服务器对命令请求的内容进行分析,将得出的命令及命令参数的个数分别保存到客户端状态的argv和argc属性。
图片上传失败
13.1.6 命令的实现函数
得出两个参数值后服务器在命令表中查找对应的命令实现函数。
13.1.7 输出缓冲区
得到的命令回复会存到输出缓冲区里,每个客户端有两个。一个大小固定用于存比较小的回复,一个可变存大的。
13.1.8 身份验证
Authenticated属性记录客户端是否通过了身份验证,值为0未通过,值为1表示通过。
13.1.9 时间
Ctime:记录创建客户端的时间
Lastinteraction:记录客户端空转时间
添加图片注释,不超过 140 字(可选)
:记录输出缓冲区第一次打到软性限制的时间。
13.2 客户端的创建与关闭
13.2.1 创建普通客户端
创建的客户端会被添加到链表尾
添加图片注释,不超过 140 字(可选)
第14章 服务器
14.1命令请求的执行过程
14.1.1 发送命令请求
客户端会将命令请求转换成协议格式,通过连接到服务器的套接字,将协议格式的命令请求发送给服务器。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
14.1.2读取命令请求
读取套接字中的命令存到客户端缓冲区,对缓冲区的请求进行分析,提取出命令参数保存到argc、argv中,调用命令执行器执行指令。
14.1.3 命令执行器(1):查找命令实现
根据客户端argv【0】参数,在命令表里查找相关命令并保存到客户端状态的cmd属性里面。
添加图片注释,不超过 140 字(可选)
14.1.4命令执行器(2):执行预备操作
14.1.5命令执行器(3):调用命令的实现函数
添加图片注释,不超过 140 字(可选)
14.1.6 命令执行器(4):执行后续的工作
14.1.7 将命令回复发送给客户端
14.1.8 客户端接收并打印命令回复
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
14.2 serverCron函数
14.2.1 更新服务器时间缓存
Unixtime和mstime被用作当前时间的缓存,100ms更新一次。
14.2.2 更新LRU时钟
Lru保存了对象最后一次被命令访问的时间。
14.2.3 更新服务器每秒执行命令的次数
通过抽样估算出请求数量。
14.2.4 更新服务器内存峰值记录
内存数量比属性记录的大就更新。
14.2.5 处理SIGTERM信号
服务器接到该信号会关闭
14.2.6管理客户端资源
ClientsCron会检查连接是否超时、缓冲区大小是否合适
14.2.9 检查持久化操作的运行状态
添加图片注释,不超过 140 字(可选)
14.2.10 将AOF缓冲区的内容写入AOF文件
14.3 初始化服务器
14.3.1 初始化服务器状态结构
创建一个struct redisServer类型的实例变量server作为服务器的状态。
14.3.2 载入配置选项
载入配置参数和配置文件(redis.conf)对server变量相关属性进行修改。
14.3.3 初始化服务器数据结构
创建其他数据结构,进行一些重要设置操作。
14.3.4还原数据库状态
完成server的初始化后,服务器需要载入rdb或者aof文件还原数据库状态。
14.3.5 执行事件循环
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
第三部分 多机数据库的实现
第15章 复制
Slave of 会让一台服务器复制另一台服务器。
添加图片注释,不超过 140 字(可选)
15.1 旧版复制功能的实现
15.1.1 同步
发送slaveof命令后,将从服务器的数据库状态更新至主服务器所在数据库状态。
从服务器对主服务器的同步操作需要通过向主服务发送SYNC命令来完成。1.从服务器向主服务器发送SYNC命令2. 收到命令后 执行bgsave命令,在后台生成rdb文件,使用一个缓冲区记录从现在开始的所有写命令。3.bgsave执行完毕会将rdb文件发送给从服务器,从服务器载入该文件更新数据库状态4.主服务器将缓冲区里的写命令发送给从服务器,从服务器执行写命令将自己的数据库状态更新至主服务器当前所处状态。
添加图片注释,不超过 140 字(可选)
15.1.2 命令传播
主服务器执行命令导致状态不一致后会向从服务器发送相同的命令使状态一致。
15.2 旧版复制功能的缺陷
复制分为初次复制和断线后重复制。
旧版在断线后 主服务器会生成新的rdb文件给从服务器同步。
添加图片注释,不超过 140 字(可选)
15.3 新版复制功能的实现
新版使用PSYNC代替SYNC,具有完整重同步和部分重同步。
完整重同步用于初次复制。部分重同步用于处理断线后重复制情况,主服务器将从服务器断线期间的写命令发给从服务器,从服务器接收并执行这些写命令。
添加图片注释,不超过 140 字(可选)
15.4 部分重同步的实现
15.4.1 复制偏移量
主服务器每次向服务器传播N个字节的数据时,就将自己的复制偏移量加上N。从服务器也一样。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
15.4.2 复制积压缓冲区
由主服务器维护的固定长度先进先出队列。主服务进行命令传播时不仅将写命令发送所有从服务器,还会讲命令入队到复制积压缓冲区里。
添加图片注释,不超过 140 字(可选)
偏移量之后的数据仍然存在与复制积压缓冲区里面那么主服务器会进行部分重同步操作。若不在复制积压缓冲区那么主服务器将对从服务器执行完整重同步操作。
15.4.3 服务器运行ID
每个redis服务器都有自己的运行ID,如果从服务器保存的运行ID与主服务器相同说明断线之前复制的就是这个主服务器。相反不是当前连接的这个主服务器。
15.5 PSYNC命令的实现
调用方法有两种:
1.从未复制过任何主服务器,主动请求完整重同步
2.复制过某个服务器会根据命令参数判断该对从服务器执行哪种同步操作。
添加图片注释,不超过 140 字(可选)
15.6 复制的实现
Slaveof可以让从服务器去复制主服务器。
15.6.1步骤1:设置主服务器的地址和端口
添加图片注释,不超过 140 字(可选)
15.6.2 步骤2:建立套接字连接
添加图片注释,不超过 140 字(可选)
15.6.3 步骤3:发送PING命令
从服务器成为主服务器的客户端后会发送PING命令,1.检查状态是否正常。2.检查主服务器是否能正常处理命令请求。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
15.6.4 步骤4:身份验证
若需要身份验证,从服务会向主服务器发送一条AUTH命令。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
15.6.5 步骤5:发送端口信息
添加图片注释,不超过 140 字(可选)
15.6.6 步骤6:同步
添加图片注释,不超过 140 字(可选)
15.6.7 步骤7:命令传播
完成同步后主从服务器进入命令传播阶段,主服务器一直将自己执行的写命令发送给从服务器,从服务器一直接收并执行主服务器发送来的写命令就可以保持一致。
15.7 心跳检测
命令传播阶段,从服务器每秒一次向主服务器发送命令,检测主从服务器的网络连接状态,辅助实现min-slaves选项,检测命令丢失。
15.7.1 检测主从服务器的网络连接状态
主从服务器通过发送接收replconf ack命令检查两者之间网络连接是否正常。
15.7.2 辅助实现min-slaves选项
添加图片注释,不超过 140 字(可选)
这两个选项防止主服务器在不安全的情况执行写命令。上面这个表示从服务器数量小于3或三个延迟都大于等于10秒主服务器就拒绝执行写命令。
15.7.3 检测命令丢失
当发现偏移量不一致,主服务器会重新发送丢失的命令更新从服务器状态。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
第16章 Sentinel
Sentinel系统可以监控多个主服务器和从服务器,并在主服务器下线时自动将某个从服务器升级成主服务器。
16.1 启动并初始化Sentinel
添加图片注释,不超过 140 字(可选)
16.1.1 初始化服务器
Sentinel不使用数据库。
16.1.2 使用Sentinel专用代码
将部分redis使用的代码替换成Sentinel专用代码。
16.1.3 初始化Sentinel状态
16.1.4 初始化Sentinel状态的masters属性
Masters字典记录了所有被Sentinel监视的主服务器的相关信息。
添加图片注释,不超过 140 字(可选)
16.1.5 创建连向主服务器的网络连接
Sentinel会创建两个连向主服务器的异步网络连接:一个是命令连接另一个是订阅连接。
添加图片注释,不超过 140 字(可选)
16.2 获取主服务器信息
默认10s一次向被监视的主服务发送INFO命令,分析命令回复来获取主服务器的信息,主服务本身的信息包括(运行ID和role记录的服务器角色)和主服务器属下所有从服务器的信息。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
16.3 获取从服务器信息
默认10s一次向从服务器发送INFO命令。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
16.4 向主服务器和从服务器发送信息
默认2s一次向所有被监视的主服务器和从服务器发送以下格式的命令。
添加图片注释,不超过 140 字(可选)
16.5 接收来自主服务器和从服务器的频道信息
Sentinel与主从服务器建立连接之后会订阅连接。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
16.5.1 更新Sentinel字典
Sentinels字典的键是Sentinel的名字(IP+端口)字典的键对应Sentinel的实力结构
一个Sentinel接收到其他Sentinel发来的信息时会从信息中提取出源Sentinel的ip、端口号、运行ID和配置纪元;源Sentinel正在监视的主服务器的名字、IP、端口号、配置纪元。目标Sentinel会在自己的Sentinel状态的masters字典查找对应主服务器并更新。
添加图片注释,不超过 140 字(可选)
16.5.2 创建连向其他sentinel的命令连接
添加图片注释,不超过 140 字(可选)
16.6 检测主观下线状态
默认sentinel会每秒一次向所有与他创建了命令链接的实例发送PING命令确认是否在线。
添加图片注释,不超过 140 字(可选)
16.7检测客观下线状态
当一个sentinel将主服务器判断为下线后会向其他监视这一主服务器的其他sentinel进行询问,看他们是否也认为主服务器已经进入了下线状态,当手机到足够数量的已下线判断后才会将该服务器判断为下线。
16.7.1发送
添加图片注释,不超过 140 字(可选)
命令
询问其他sentinel是否同意下线。
16.7.2 接收
添加图片注释,不超过 140 字(可选)
命令
接收到另一个sentinel发来的该命令时,先解析各个参数判断是否下线后给予回复。
16.7.3 接收
添加图片注释,不超过 140 字(可选)
命令的回复
根据命令回复统计其他Sentinel同意主服务器已下线的数量,达到一定数量时将主服务器flags属性的SRI_O_DOWN标识打开。
16.8 选举领头Sentinel
当主服务器被判断为客观下线时监视这个主服务器的Sentinel会进行协商选举出一个领头Sentinel,由领头Sentinel对下线服务器进行故障转移。
16.9 故障转移
1.在已下线主服务器属下所有的从服务器中挑选一个作为主服务器。
2. 让从服务器改为复制新的主服务器。
3.旧的主服务器重新上线时会成为新主服务器的从服务器。
16.9.1 选出新的主服务器
挑选出一个状态良好、数据完整的从服务器,向这个从服务器发送slaveof no one命令,将这个从服务器转换为主服务器。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
16.9.2 修改从服务器的复制目标
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
16.9.3 将旧的主服务器变为从服务器
添加图片注释,不超过 140 字(可选)
第十七章 集群
17.1 节点
一个redis集群由多个节点组成
添加图片注释,不超过 140 字(可选)
17.1.1 启动节点
添加图片注释,不超过 140 字(可选)
17.1.2 集群数据结构
每个节点都会用一个clusterNode来保存自己的状态,并为其他几点都创建一个相应的clusterNode结构以记录其他节点的状态。每个节点都保存着一个clusterState结构,记录零零当前节点的视角下集群所处的状态。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.1.3 CLUSTER MEET命令的实现
添加图片注释,不超过 140 字(可选)
17.2 槽指派
集群通过分片的方式保存数据库中的键值对,整个数据库被分为16384个槽,当所有槽都有节点在处理时集群处于上线状态。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.2.1 记录节点的槽指派信息
添加图片注释,不超过 140 字(可选)
Slots数组记录了节点负责处理哪些槽。
添加图片注释,不超过 140 字(可选)
17.2.2 传播节点的槽指派信息
节点会将自己的slots数组通过消息发送给集群中的其他节点告知其他节点自己目前负责处理哪些槽。
添加图片注释,不超过 140 字(可选)
17.2.3 记录集群所有槽的指派信息
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
ClusterState.slots数组记录了集群所有槽的指派信息,clusterNode.slots数组只记录了clusterNode结构所代表的节点的槽指派信息。ClusterState以槽为维度,clusterNode以节点为维度。
17.2.4 CLUSTER ADDSLOTS 命令的实现
指派槽给节点的命令
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.3在集群中执行命令
客户端发送与键有关的命令时,节点会先计算出键属于哪个槽,检查这个槽是否指派给了自己。若支配给自己就执行,否则移向正确的节点再执行。
添加图片注释,不超过 140 字(可选)
17.3.1 计算键属于哪个槽
添加图片注释,不超过 140 字(可选)
17.3.2 判断槽是否由当前节点负责处理
添加图片注释,不超过 140 字(可选)
17.3.3 MOVED错误
当节点发现键所在的槽并非自己处理会向客户端返回一个MOVED错误指引客户端转向至负责处理的节点。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.3.4 节点数据库的实现
节点只能使用0号数据库,单价redis没有这个限制。
添加图片注释,不超过 140 字(可选)
17.4 重新分片
可以将任意数量已经指派给某个节点的槽改为指派给另一个节点并且相关槽所属的键值对也会从源节点被移动到目标节点。分片操作可以在线进行。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.5 ASK错误
添加图片注释,不超过 140 字(可选)
17.5.1 CLUSTER SETSLOT IMPORTING命令的实现
Importing_slots_from数组记录了当前节点正在从其他节点导入的槽。
添加图片注释,不超过 140 字(可选)
17.5.2 CLUSTER SETSLOT MIGRATING命令的实现
Migrating_slots_to记录了当前节点正在迁移至其他节点的槽。
添加图片注释,不超过 140 字(可选)
17.5.3 ASK错误
收到key的请求会先在自己数据库里找,找不到会检查是否迁移。
17.5.4 ASKING 命令
Asking命令唯一要做的就是打开发送该命令的客户端的redis_asking标识。
添加图片注释,不超过 140 字(可选)
17.5.5ask错误和moved错误的区别
Moved错误代表槽的负责权已经从一个节点转移到了另一个节点,收到moved错误后直接将命令请求发送至指向的节点。
收到Ask错误之后,客户端只会在接下来的一次命令请求中将关于槽的命令请求转发至ask错误指示的节点。
17.6 复制与故障转移
Reids集群主节点用于处理槽,从节点复制主节点。主节点下线后从从节点中选出一个当新主节点。
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
添加图片注释,不超过 140 字(可选)
17.6.1 设置从节点
添加图片注释,不超过 140 字(可选)
17.6.2 故障检测
每个节点都会定期向集群中的其他节点发送PING消息,检测对方是否在线,若没有按时返回PONG消息,那么会被标记疑似下线。一个集群超半数都认为某个节点疑似下线,那么这个节点会被标记为已下线。
17.6.3 故障转移
下线主节点的从节点中选出一个节点成为新的主节点,新节点将槽指派给自己,向集群广播一条PONG消息,让其他节点知道这个节点变为主节点,新节点开始接收处理自己负责处理的槽有关的命令请求。
17.6.4 选举新的主节点
集群的配置纪元是一个自增计数器初始值为0,当某个节点进行故障转移时配置纪元会+1,集群里主节点都有投票机会,第一个向主节点要求投票的从节点获得主节点的投票,从节点发现主节点已下线时会向集群广播想这个从节点投票,每个参与选举的从节点统计自己收到了多少消息超过半数就成为主节点,否则重新选举。
17.7 消息
17.7.1 消息头
消息头包含消息正文和发送者的信息。
17.7.2 MEET、PING、PONG消息的实现
Redis通过Gossip协议来交换各自不同节点的状态信息。
A发送的消息包含B、C。 D接收到后会更新对BC的认知。
17.7.3 FALI消息的实现
主节点A将主节点B标记为已下线时,A会向集群广播一条B的FAIL消息,接收到的节点会将B标记为已下线。
17.7.4 PUBLISH消息的实现
第四部分 独立功能的实现
第18章 发布与订阅
18.1 频道的订阅与退订
18.1.1 订阅频道
Subscribe命令实现,若频道有人订阅,把客户端加入到链表末尾。无订阅者要在字典中为频道创建一个键,将键的值设置为空链表,将客户端添加到链表。
18.1.2 退订频道
Unsubscribe命令,根据退订频道的名字在字典中找到相应的链表,从订阅者链表中删除客户端的信息,若删除后变为空链表在字典中删除相应的键。
18.2 模式的订阅与退订
18.2.1 订阅模式
新建一个pubsubpattern结构添加到链表尾。
18.2.2 退订模式
Punsubscribe,从链表中去掉节点
18.3 发送消息
18.3.1 将消息发送给频道订阅者
在字典中找到订阅者名单将消息发送。
18.3.2 将消息发送给模式订阅者
遍历链表查找和频道相匹配的模式发送消息。