第一部分:数据结构与对象
-
第二章 字符串 SDS
- SDS simple dynamic string
- 对c字符数组进行封装,支持len、free、buf。
- 解决了二进制安全问题
- 解决了频繁分配空间和计算长度等问题,优化了性能
- 末尾\0可复用string.h的部分函数
-
第三章 链表
- ListNode: *prev,*next, * value List: listNode *head/tail, len
- 双端、无环、多态(可保存不同值)
- 第四章 字典
- redis数据库和哈希键都使用字典存储。字典 ht[2]在rehash的时候使用
- 使用murmurHash算法计算hash :【需要了解一下】
- hash键冲突通过链表解决,会插入到头部。并没有使用树形结构,因此hash算法比较重要
- rehash的过程,创建一个新的hashtable,通过设置rehashidx,逐步将节点迁移到ht[1]中。
- 查询是先ht[0]/没有就查ht[1]
- 插入的时候ht[1]插入
- 更新的时候可以先查询再更新
- 删除操作:【书上没说,应该是ht[0]/ht[1]都尝试】
- 第五章跳跃表
- 调表的能力和双向链表
-
第六章 整数集合
-
有序存储,连续存储空间,为了减少存储空间,提升性能
- 只支持编码升级,不支持降级
-
第七章 压缩列表
- 长度字段是可变字节,可能存在连锁更新问题
- 对于整数、短字符处理有优势
- 第八章 对象
- 记录对象的信息,编码、元素等等,为一些指令提供高效支持,利用多态特性可以对编码转换问题提供更好支持。
- 内存回收:通过引用计数来判定对象是否回收
- 对象共享:共享相同对象,提升空间利用率。会初始化10000以内的整数字符串对象
-
总结
- 字符串、列表、哈希、集合、有序集合五中类型的对象。至少有2种编码方式。
- 链表:列表键
- 字典:哈希键
- 跳表:有序集合
- 整数集合:集合键
- 压缩列表:列表键、hash键
第二部分 单机数据库的实现
- 数据库
- 单机Redis存在多个数据库,集群中每个redis实例只会使用0号数据库
- 支持设置生存时间和过期时间,有专门的字段存放key的过期时间。过期字段有定时清理、惰性清理、定期清理三种策略,使用的是后两种。其中定期清理采用随机选择key进行判断和删除。
- RDB持久化
- 过期键不会被持久化
- 支持同步和异步持久化指令。满足条件时会进行持久化,存在一定可能的数据不一致问题。持久化支持防竞争条件,一个时间只能有一个指令在执行。dirty计数器用于判定是否需要进行持久化。
- bgsave通过创建子进程进行持久化,bgsave不解决持久化时增量数据问题
- AOF持久化
- 过期键不会被重写,过期键被持久化后不会影响一致性,因为del命令会被追加或者在还原数据库的时候,通过惰性策略会发现过期。
- AOF持久化策略支持always、everysec、no三种策略。安全和效率不可兼得
- AOF重写通过新建子进程来实现,同时通过buf区来缓存重写过程中的增量操作
- 事件
- ·需要处理文件事件和时间事件
- 文件事件与IO相关,处理命令请求、恢复、连接应答等事件,轮流处理
- 时间事件:分定时事件和周期事件。更新统计信息、过期key删除、关闭、清理失效客户端、AOF&RDB持久化、主从服务器定期同步、集群定期同步和连接测试
- 客户端
- ·包含伪客户端,这类客户端不需要套接字连接。普通客户端。
- 可配置需要身份验证。
- 空转时间过长的客户端会被关闭,但是存在订阅的客户端不会
- 服务器
- 前置:会验证客户端的指令、指令参数、身份验证、内存使用情况、
- 后置:慢查询日志、统计技术、AOF、主从复制
- 通过抽样判断一段时间的执行情况
第三部分 多级数据库实现
- 复制
- 复制指的是主从复制,可通过slaveof指令来复制一个主服务器数据
- 支持完整重同步、部分重同步。通过记录偏移量来判断从哪个位置进行增量同步,解决各种原因导致的暂时失联问题。但是部分重同步存在缓存大小限制,偏移量不在缓存中了只能进行完整同步。
- sentinel 哨兵
- 解决redis高可用性的方案。由一个或多个sentinel实例监视多个主、从服务状态。启动sentinel和redis是一套代码,不同的指令集。
- 连接服务器的有两个连接,一个命令连接、一个订阅连接
- 通过获取主服务器信息,会同时获得主服务器的从服务器信息。
- 通过发布订阅消息,sentinel也相当于向其他sentinel广播自己的信息,sentinel之间不会创建订阅连接
- sentinel通过配置会主观判断主服务器、从服务器、sentinel的在线状态。发现下线实例以后会询问其他sentinel的判断状态,当超过配置阈值数的sentinel认为相同实例下线时,会判定客观下线。会发起故障转移命令
- 需要选择领头sentinel来执行故障转移指令,选举方式为
- 发现客观下线的机器会要求其他sentinel将自己设置为领头sentinel,如果超过半数支持就会确定了领头sentinel,否则在一段时间后再次进行选举。每个sentinel会接受第一个收到的要求,拒绝之后的要求(先到先得)
- 故障转移:领头sentinel会从复制偏移量最大、优先级最高的从服务器中选择运行ID最小的从服务器作为新的主服务器。如果原来的主服务器上线了会被指定为从服务器
- 集群
- 集群机器通过分配槽点来建立完整集群,槽点个数为16384个
- 如果指定不属于当前服务器处理的槽点,会返回moved错误,指引客户端到正确的节点发送指令
- 计算槽点通过crc-16校验和来判断
- 重新分片
- 向向目标节点发送指令
- 向源节点发送指令,获取槽点的多个键值对
- 向目标节点发送迁移指令
- 重复3/4知道完整迁移
- 转移过程中,如果对转移槽点的键进行操作且键不存在时,会发送ASK错误,让客户端向目标节点发送指令
- ask错误会在请求先打开REDIS_AKING标识,该标识只在一次请求中有效
- 消息:MEET、PINT、PONG消息都会随机选择两个节点发送出去,接收者可以获取到其他节点信息
第四部分
- 发布与订阅
- 支持指定频道订阅和模式订阅
- 需要将消息发送到指定订阅者
- 如果一个频道无订阅者了要删除
- 事务
- MULTI、EXEC、来标记事务的开始和执行,一次可执行多个指令
- WATCH来监视指定key,但是如果在执行前watch的key被操作了,会失败,服务器会判断监视的client的标识是否正确。
- redis不支持回滚,即使错误也会把指令执行完
- 事务中存在非法指令,事务会被拒绝
- Lua脚本
- 支持eval、evalsha、script load,script flush,script exists、script kill指令
- 在服务器端会创建一个lua环境,替换其中的随机函数、排序函数。通过伪客户端来发送和redis的交互指令。会保护lua的全局环境,防止全局变量被修改。
- 主从同步的时候,会记录是否所有从服务器都同步了脚本,是则发送evalsha指令,否则发送eval 指令。
- 服务器上返回的sha1校验和也是lua函数名
- 排序
- 通过创建指针表,指针指向排序对象和排序依据
- 通过快排实现
- 支持limit、get、asc、desc指令,支持数值和字符串的排序
- 二进制位数组
- 位运算的实现
- 计算1的个数的三种算法
- 遍历计算
- 查表
- variable-precision swar算法
- 归并思想,对二进制进行分组,相邻的两组通过位移解决高低位不同问题,再通过加法得到每个组的1的个数
- 归并3次算出分组为2/4/8的1的个数,在通过一次乘法+右移获取4组上1的个数总和。非常漂亮的算法
- 计算非0二进制数的数量称为,计算汉明重量
- 慢查询日志
- 最近一段时间缓存中慢查询的信息
- 监视器
- 发送监视器指令,可以监控服务器的执行状态