《Redis设计与实现》简读
目录
《Redis设计与实现》核心要点简读
一、数据结构与对象
1. 简单动态字符串(SDS)
-
优势:
- 记录字符串长度,获取长度复杂度O(1)
- 记录已分配内存空间,避免缓冲区溢出
- 空间预分配和惰性释放机制
- 二进制安全,不以
\0判断结束 - 兼容C字符串函数(以
\0结尾)
-
空间预分配与惰性释放:
- 增长时:长度<1M分配2倍空间;长度≥1M分配+1M空间
- 缩短时:不立即释放多余内存,记录在
free属性中 - 最佳实践:修改相同键的值时,尽量保持长度相近,避免频繁内存重分配
2. 链表
- 特点:
- 双向链表,获取前后节点复杂度O(1)
- 无环,头尾指针指向NULL
- 记录头尾节点,获取头尾节点复杂度O(1)
- 记录链表长度,获取长度复杂度O(1)
- 可存储多种类型数据
3. 字典
-
实现机制:
- 链地址法解决哈希冲突(相同索引时添加到链表头)
- 两个哈希表
ht[0]和ht[1](ht[1]用于rehash) - 渐进式rehash:逐步将
ht[0]数据迁移到ht[1] - 负载因子 = 已保存节点数/哈希表大小
-
rehash步骤:
- 扩展(无BGSAVE/BGREWRITEAOF且负载≥1,或有BGSAVE/BGREWRITEAOF且负载≥5):为
ht[1]分配≥当前节点数×2的内存 - 收缩(负载<0.1):为
ht[1]分配≥当前节点数的内存 - 逐步迁移
ht[0]到ht[1] - 释放
ht[0],ht[1]变为ht[0]
- 扩展(无BGSAVE/BGREWRITEAOF且负载≥1,或有BGSAVE/BGREWRITEAOF且负载≥5):为
Redis哈希算法:MurmurHash2
4. 跳跃表
- 用途:有序集合底层实现之一
- 特点:
- 节点按分值排序,分值相同时按对象大小排序
- 每个节点可保存字节数组或整数值
5. 整数集合
-
支持类型:
int16_t(-32768至32767)int32_t(-2147483648至2147483647)int64_t(-9223372036854775808至9223372036854775807)
-
升级机制:
- 根据新元素类型扩展底层数组
- 转换现有元素类型并保持有序
- 添加新元素(小于所有元素放索引0,大于所有元素放索引length-1)
-
最佳实践:向同一整数集合添加相同类型的整数,避免升级操作
6. 压缩链表
- 用途:列表键和哈希键的底层实现之一
- 特点:添加/删除节点可能造成连锁更新,最坏时间复杂度O(N²)
7. 对象与编码
Redis支持5种对象类型,每种类型有不同编码方式:
| 类型 | 编码 | 说明 |
|---|---|---|
| REDIS_STRING | REDIS_ENCODING_INT | 整数值实现的字符串对象 |
| REDIS_STRING | REDIS_ENCODING_EMBSTR | 小于32字节字符串的简单动态字符串实现 |
| REDIS_STRING | REDIS_ENCODING_RAW | 大于32字节字符串的简单动态字符串实现 |
| REDIS_LIST | REDIS_ENCODING_ZIPLIST | 元素长度<64字节且数量<513(默认) |
| REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 双向链表实现 |
| REDIS_HASH | REDIS_ENCODING_ZIPLIST | 元素长度<64字节且数量<513(默认) |
| REDIS_HASH | REDIS_ENCODING_HT | 字典实现 |
| REDIS_SET | REDIS_ENCODING_INTSET | 整数值且数量<513(默认) |
| REDIS_SET | REDIS_ENCODING_HT | 字典实现 |
| REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 元素长度<64字节且数量<128(默认) |
| REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 跳跃表+字典实现 |
查看对象编码命令:
TYPE key OBJECT ENCODING key
8. 内存管理
- 引用计数:对象计数为0时释放内存
- 对象共享:初始化创建0-9999字符串对象,相同值共享
- 空转时长:记录最后访问时间,maxmemory策略为volatile-lru/allkeys-lru时优先释放空转时间长的键
最佳实践:将简单字符串或重复率高的字符串映射到0-9999范围内的数字,节省内存
二、单机数据库的实现
1. 数据库
- 默认数据库数:16(
CONFIG GET databases) - 过期键策略:
- 惰性删除:读取时发现过期才删除
- 定期删除:定时遍历检查部分键的过期时间
主从模式注意事项:从服务器不会主动删除过期键,会将其当作正常键返回。主从模式不同于MySQL(主写从读),过期数据可能在一段时间内是脏数据。
2. RDB持久化
- 特点:
- 保存所有数据库数据
SAVE阻塞服务器,BGSAVE通过子进程非阻塞- 二进制压缩文件
3. AOF持久化
- 特点:
- 保存所有修改命令
- 命令以Redis协议格式保存
- 先存入AOF缓冲区,定期写入AOF文件
- AOF重写:通过读取数据库生成更小的AOF文件
4. 事件处理
- 事件类型:
- 文件事件:基于Reactor模式的网络通信(读/写事件)
- 时间事件:定时/周期事件(如
serverCron每100ms执行一次)
- 执行顺序:事件在文件和时间事件中轮询,不抢占
5. 客户端管理
- 缓冲区:
- 输入缓冲区:1GB
- 输出缓冲区:固定16KB + 可变缓冲区(
CONFIG GET client-output-buffer-limit)
- 最大连接数:默认10000(受系统文件描述符限制,最大=文件描述符数-32)
- 关闭条件:连接关闭、协议错误、
CLIENT KILL、空转超时、缓冲区超限
可变输出缓冲区限制:
- 普通客户端:无限制
- PubSub客户端:超过32M或持续60s>8M
- Slave客户端:超过256M或持续60s>64M
6. 服务器流程
-
启动步骤:
- 初始化服务器状态
- 载入配置
- 初始化数据结构
- 还原数据库状态
- 执行事件循环
-
命令执行流程:
- 客户端发送命令
- 服务器解析命令参数
- 查找并执行命令实现函数
- 返回命令回复
三、多机数据库的实现
1. 复制
- 部分重同步(Redis 2.8+):
- 通过复制偏移量、复制挤压缓冲区、服务器运行ID实现
- 从服务器每1秒发送
REPLCONF ACK检测心跳和命令丢失
2. Sentinel(哨兵)
- 工作原理:
- 运行在特殊模式下的Redis服务器
- 向主从服务器创建命令连接和订阅连接
- 感知其他Sentinel存在,建立命令连接
- 主观下线后相互询问,收集足够票数(>1/2)后判断客观下线
3. 集群
- 核心机制:
- 数据库分为16384个槽
- 每个节点记录指派的槽
- 命令请求检查键是否在节点槽中,否则返回
MOVED或ASK - 重新分片由
redis-trib负责 - 从节点用于主节点故障时的选举
错误区别:
MOVED:键已转移至其他节点ASK:槽正在转移过程中的临时错误
四、独立功能的实现
1. 发布与订阅
- 类型:
- 频道发布订阅
- 模式发布订阅
- 实现:
pubsub_channels字典:保存频道订阅关系pubsub_patterns链表:保存模式订阅关系
2. 事务
- 特点:
- 打包多个命令,按顺序执行
- 无回滚功能
WATCH命令:监视键是否被修改,若修改则拒绝事务提交
3. Lua脚本
- 执行机制:
- 内嵌Lua环境,修改函数适应Redis
- 使用伪客户端执行Redis命令
- 脚本字典:以SHA1校验和为键名
- 超时处理:
SCRIPT KILL或SHUTDOWN nosave
Redis创建Lua环境步骤:
- 创建基础Lua环境
- 载入函数库
- 创建Redis操作函数全局表
- 替换随机函数
- 创建排序辅助函数
- 创建错误报告函数
redis.pcall- 保护全局变量
- 保存到服务器状态
4. 排序
- 实现:快速排序算法
- 执行:将元素保存在数组中,再对数组排序
5. 慢查询日志
- 默认设置:
- 记录>10000us的命令(
CONFIG GET slowlog-log-slower-than) - 保留128条(
CONFIG GET slowlog-max-len)
- 记录>10000us的命令(
五、Redis 4.0特性
1. 模块系统
- 作用:扩展数据结构及功能
- 示例:ReJSON模块
127.0.0.1:6379> JSON.SET test . '{"first": 1, "second": 2, "three": 3}' OK 127.0.0.1:6379> JSON.GET test "{\"first\":1,\"second\":2,\"three\":3}"
2. PSYNC 2.0
- 改进:主从节点在条件允许下使用部分复制
3. 缓存驱逐策略优化(LFU)
- 原理:根据访问计数和最近访问时间计算分值
- 计数器函数:
uint8_t LFULogIncr(uint8_t counter) { if (counter == 255) return 255; double r = (double)rand()/RAND_MAX; double baseval = counter - LFU_INIT_VAL; if (baseval < 0) baseval = 0; double p = 1.0/(baseval*server.lfu_log_factor+1); if (r < p) counter++; return counter; }
4. 非阻塞删除命令
- 新增:
UNLINK(非阻塞DEL)FLUSHDB ASYNC/FLUSHALL ASYNC
5. 混合RDB-AOF持久化
- 特点:AOF重写文件同时包含RDB和AOF格式内容
- RDB格式:记录已有数据
- AOF格式:记录最近变化数据
6. 内存命令
- 新命令:
MEMORY HELP用于查看内存使用情况和进行管理
六、Redis 5.0特性
1. Stream(流)
- 特点:多播的可持久化消息队列(借鉴Kafka)
- 文档:Redis Streams Introduction
- 示例文章:《挑战Kafka!Redis5.0重量级特性Stream尝鲜》
2. SortedSet新增命令
| 命令 | 说明 |
|---|---|
ZPOPMAX |
移除并弹出有序集合中分值最大的元素 |
ZPOPMIN |
移除并弹出有序集合中分值最小的元素 |
BZPOPMAX |
阻塞移除并弹出有序集合中分值最大的元素 |
BZPOPMIN |
阻塞移除并弹出有序集合中分值最小的元素 |
重要提示:以上内容为《Redis设计与实现》核心要点的简明总结,适用于Redis 3.0-5.0版本。实际使用中,应参考官方文档了解最新特性和最佳实践。