Redis核心模块详解


Redis 核心模块详解

Redis 是一个高性能的内存数据库,其核心架构由多个模块组成,每个模块负责不同的功能。本文详细介绍 Redis 的核心模块及其工作原理。

1. 事件驱动模型(Event Loop)

Redis 采用单线程事件驱动模型,使用 I/O 多路复用技术(epoll、kqueue、select)处理多个客户端连接。

核心组件

1.1 事件循环(aeEventLoop)

1
2
3
4
5
6
7
8
9
10
// Redis 事件循环结构(简化)
typedef struct aeEventLoop {
int maxfd; // 最大文件描述符
aeFileEvent *events; // 文件事件数组
aeFiredEvent *fired; // 已触发事件数组
aeTimeEvent *timeEventHead; // 时间事件链表
void *apidata; // 多路复用 API 数据
aeBeforeSleepProc *beforesleep; // 事件循环前的回调
aeBeforeSleepProc *aftersleep; // 事件循环后的回调
} aeEventLoop;

1.2 事件类型

  • 文件事件(File Event):处理客户端连接、读写操作
  • 时间事件(Time Event):处理定时任务,如过期键清理、RDB 持久化等

1.3 工作流程

  1. 初始化:创建事件循环,注册多路复用器(epoll/kqueue/select)
  2. 事件注册:将客户端连接注册为可读/可写事件
  3. 事件等待:调用 aeApiPoll() 等待事件发生
  4. 事件处理
    • 处理文件事件:读取客户端命令、执行、返回结果
    • 处理时间事件:执行定时任务
  5. 循环执行:重复步骤 3-4

优势

  • 高性能:单线程避免了上下文切换和锁竞争
  • 低延迟:事件驱动模型响应迅速
  • 简单可靠:单线程模型避免了复杂的并发问题

2. 内存管理模块

Redis 需要高效管理内存,包括内存分配、回收、淘汰策略等。

2.1 内存分配器

Redis 使用多种内存分配策略:

  • jemalloc(默认):Facebook 开发的内存分配器,性能优秀
  • tcmalloc:Google 开发的内存分配器
  • libc malloc:系统默认的内存分配器

2.2 内存淘汰策略

当内存达到 maxmemory 限制时,Redis 会根据配置的策略删除键:

1
2
3
# 配置示例
maxmemory 2gb
maxmemory-policy allkeys-lru

淘汰策略

  • noeviction:不删除,返回错误(默认)
  • allkeys-lru:从所有键中选择最近最少使用的键删除
  • volatile-lru:从设置了过期时间的键中选择最近最少使用的删除
  • allkeys-lfu:从所有键中选择使用频率最低的键删除
  • volatile-lfu:从设置了过期时间的键中选择使用频率最低的删除
  • allkeys-random:随机删除任意键
  • volatile-random:随机删除设置了过期时间的键
  • volatile-ttl:删除最接近过期时间的键

2.3 渐进式删除(Lazy Free)

Redis 4.0 引入渐进式删除,避免大键删除时阻塞主线程:

1
2
3
4
5
# 配置示例
lazyfree-lazy-eviction yes # 内存淘汰时使用异步删除
lazyfree-lazy-expire yes # 过期键删除时使用异步删除
lazyfree-lazy-server-del yes # 命令删除时使用异步删除
replica-lazy-flush yes # 副本同步时使用异步删除

2.4 内存压缩

对于字符串类型,Redis 使用多种编码方式节省内存:

  • int:整数编码(8 字节)
  • embstr:小于 44 字节的字符串,嵌入到对象中
  • raw:大于 44 字节的字符串,单独分配内存

3. 数据结构模块

Redis 提供丰富的数据结构,每种结构都有多种底层实现,根据数据特征自动选择最优编码方式。

3.1 Redis 对象系统(redisObject)

Redis 使用对象系统统一管理所有数据结构:

1
2
3
4
5
6
7
8
// Redis 对象结构(简化)
typedef struct redisObject {
unsigned type:4; // 对象类型(OBJ_STRING/OBJ_LIST/OBJ_HASH/OBJ_SET/OBJ_ZSET)
unsigned encoding:4; // 编码方式(决定了底层数据结构)
unsigned lru:24; // LRU 时间戳或 LFU 计数器(用于内存淘汰)
int refcount; // 引用计数(用于内存管理)
void *ptr; // 指向底层数据结构的指针
} robj;

对象类型(type)

  • OBJ_STRING:字符串类型
  • OBJ_LIST:列表类型
  • OBJ_HASH:哈希类型
  • OBJ_SET:集合类型
  • OBJ_ZSET:有序集合类型

编码方式(encoding):同一类型可能使用不同的编码方式,Redis 会根据数据大小和特性自动选择最优编码。

3.2 字符串(String)

3.2.1 底层实现

1. INT 编码(REDIS_ENCODING_INT)

  • 适用场景:整数字符串(范围:-2^63 到 2^63-1)
  • 存储方式:整数直接存储在 ptr 中,不额外分配内存
  • 优势:节省内存,无需额外的字符串对象

2. EMBSTR 编码(REDIS_ENCODING_EMBSTR)

  • 适用场景:字符串长度 ≤ 44 字节
  • 存储方式:字符串嵌入到 redisObject 中,使用连续内存
  • 优势
    • 减少内存分配次数(只需分配一次)
    • 提高缓存局部性
    • 减少内存碎片

3. RAW 编码(REDIS_ENCODING_RAW)

  • 适用场景:字符串长度 > 44 字节
  • 存储方式:使用 SDS(Simple Dynamic String)存储
  • 结构ptr 指向 SDS 结构

3.2.2 SDS(Simple Dynamic String)结构

1
2
3
4
5
struct sdshdr {
unsigned int len; // 字符串长度(O(1) 获取长度)
unsigned int free; // 剩余空间
char buf[]; // 字符数组(以 '\0' 结尾,兼容 C 字符串)
};

SDS 的优势

  • O(1) 获取长度:无需遍历字符串
  • 二进制安全:可以存储任意二进制数据,包括 ‘\0’
  • 预分配空间:减少内存重分配次数
  • 自动扩容:空间不足时自动扩展

SDS 的扩容策略

  • 如果修改后长度 < 1MB,分配 2 倍空间
  • 如果修改后长度 ≥ 1MB,分配额外 1MB 空间

使用场景

  • 缓存
  • 计数器
  • 分布式锁
  • 二进制数据存储

3.3 列表(List)

3.3.1 底层实现

1. ZIPLIST 编码(REDIS_ENCODING_ZIPLIST)

  • 适用场景:元素数量少且元素小
  • 转换条件
    • 任意元素长度 > 64 字节,或
    • 元素数量 > 512

2. QUICKLIST 编码(REDIS_ENCODING_QUICKLIST,Redis 3.2+ 默认)

  • 适用场景:大多数场景
  • 结构:双向链表 + ZIPLIST 的组合
  • 特点:每个节点是一个 ZIPLIST

3. LINKEDLIST 编码(REDIS_ENCODING_LINKEDLIST,已废弃)

  • Redis 3.2 之前使用
  • 已被 QUICKLIST 替代

3.3.2 ZIPLIST(压缩列表)结构

内存布局

1
<zlbytes> <zltail> <zllen> <entry1> <entry2> ... <entryN> <zlend>
  • zlbytes(4 字节):整个 ZIPLIST 占用的字节数
  • zltail(4 字节):到达表尾节点的偏移量
  • zllen(2 字节):节点数量(如果节点数 ≥ 65535,需要遍历整个列表)
  • entry:节点数据
  • zlend(1 字节):结束标记(0xFF)

节点结构(entry)

1
<prevlen> <encoding> <data>
  • prevlen:前一个节点的长度(用于反向遍历)
  • encoding:编码方式(整数或字符串)
  • data:实际数据

ZIPLIST 的优势

  • 内存紧凑,减少内存碎片
  • 适合存储小数据

ZIPLIST 的劣势

  • 插入/删除需要重新分配内存
  • 节点过多时性能下降

3.3.3 QUICKLIST(快速列表)结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct quicklist {
quicklistNode *head; // 头节点
quicklistNode *tail; // 尾节点
unsigned long count; // 元素总数
unsigned long len; // 节点数量
int fill : 16; // ZIPLIST 大小限制(-1 到 2^15-1)
unsigned int compress : 16; // 压缩深度(0 表示不压缩)
} quicklist;

typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl; // ZIPLIST 指针
unsigned int sz; // ZIPLIST 字节数
unsigned int count : 16; // ZIPLIST 元素数量
unsigned int encoding : 2; // 编码方式(RAW=1, LZF=2)
unsigned int container : 2; // 容器类型(ZIPLIST=1)
unsigned int recompress : 1; // 是否被压缩
unsigned int attempted_compress : 1;
unsigned int extra : 10;
} quicklistNode;

QUICKLIST 的优势

  • 结合了 ZIPLIST 和链表的优点
  • 支持压缩(LZF 算法),减少内存占用
  • 性能与内存使用平衡
  • 支持头尾快速插入/删除

使用场景

  • 消息队列
  • 最新列表
  • 栈和队列

3.4 哈希(Hash)

3.4.1 底层实现

1. ZIPLIST 编码(REDIS_ENCODING_ZIPLIST)

  • 适用场景:字段少且值小
  • 转换条件
    • 任意键值对长度 > 64 字节,或
    • 键值对数量 > 512
  • 存储方式:按顺序存储 field-value 对

2. HT 编码(REDIS_ENCODING_HT)

  • 适用场景:字段多或值大
  • 结构:字典(Dict/HashTable)

3.4.2 字典(Dict)结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct dict {
dictType *type; // 类型特定函数(哈希函数、键比较函数等)
void *privdata; // 私有数据
dictht ht[2]; // 两个哈希表(用于渐进式 rehash)
long rehashidx; // rehash 进度,-1 表示未进行 rehash
int iterators; // 迭代器数量
} dict;

typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小(2 的幂次)
unsigned long sizemask; // 大小掩码(size - 1)
unsigned long used; // 已使用节点数量
} dictht;

typedef struct dictEntry {
void *key; // 键
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; // 值
struct dictEntry *next; // 指向下一个节点(解决哈希冲突)
} dictEntry;

哈希函数:使用 MurmurHash2 算法,性能优秀,分布均匀

解决冲突:链地址法(拉链法),冲突的键值对形成链表

渐进式 Rehash

  • 触发条件
    • 扩容:当负载因子(used/size)> 1 时触发
    • 缩容:当负载因子 < 0.1 时触发
  • 执行过程
    1. 分配 ht[1] 空间(大小为 2 的幂次)
    2. rehashidx 设为 0,开始 rehash
    3. 每次对字典执行增删改查操作时,将 ht[0]rehashidx 索引位置的所有键值对 rehash 到 ht[1]
    4. rehashidx 加 1
    5. 当所有键值对都 rehash 完成后,将 ht[1] 设为 ht[0]ht[1] 清空
  • 优势:避免一次性 rehash 造成阻塞,保证服务可用性

使用场景

  • 对象存储(用户信息、商品信息等)
  • 小对象使用 ZIPLIST,大对象使用 HT

3.5 集合(Set)

3.5.1 底层实现

1. INTSET 编码(REDIS_ENCODING_INTSET)

  • 适用场景:所有元素都是整数
  • 转换条件
    • 元素数量 > 512,或
    • 包含非整数元素
  • 结构:有序整数数组

2. HT 编码(REDIS_ENCODING_HT)

  • 适用场景:包含非整数元素或元素数量多
  • 结构:字典(value 为 NULL,只存储 key)

3.5.2 INTSET(整数集合)结构

1
2
3
4
5
typedef struct intset {
uint32_t encoding; // 编码方式(INTSET_ENC_INT16/INT32/INT64)
uint32_t length; // 元素数量
int8_t contents[]; // 元素数组(有序存储)
} intset;

INTSET 的特点

  • 元素按从小到大排序
  • 支持三种整数编码(16/32/64 位)
  • 自动升级编码(当新元素超出当前编码范围时)

INTSET 的升级过程

  1. 根据新元素类型,扩展数组大小
  2. 将原有元素转换为新类型
  3. 将新元素添加到数组
  4. 更新 encoding 属性

INTSET 的优势

  • 内存紧凑,节省空间
  • 查找效率高(二分查找,O(log N))
  • 支持范围查询

使用场景

  • 纯整数集合(用户ID、商品ID等)
  • 集合运算(交集、并集、差集)
  • 去重操作

3.6 有序集合(Sorted Set)

3.6.1 底层实现

1. ZIPLIST 编码(REDIS_ENCODING_ZIPLIST)

  • 适用场景:元素少且小
  • 转换条件
    • 任意元素长度 > 64 字节,或
    • 元素数量 > 128
  • 存储方式:按 score 排序,存储 member-score 对

2. SKIPLIST 编码(REDIS_ENCODING_SKIPLIST)

  • 适用场景:大多数场景
  • 结构:跳跃表 + 字典

3.6.2 跳跃表(SkipList)结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct zset {
dict *dict; // 字典(用于 O(1) 查找 member 的 score)
zskiplist *zsl; // 跳跃表(用于范围查询和排序)
} zset;

typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 头尾节点
unsigned long length; // 节点数量
int level; // 最大层数
} zskiplist;

typedef struct zskiplistNode {
robj *obj; // 成员对象(member)
double score; // 分值(score)
struct zskiplistNode *backward; // 后退指针(用于反向遍历)
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned int span; // 跨度(用于计算排名)
} level[]; // 层数组(每层都有前进指针和跨度)
} zskiplistNode;

跳跃表原理

  • 多层有序链表结构
  • 底层包含所有元素,上层是索引层
  • 查找、插入、删除时间复杂度 O(log N)
  • 通过随机层数保证平衡性

跳跃表的查找过程

  1. 从最高层开始,向右查找
  2. 如果当前节点的 score 小于目标 score,继续向右
  3. 如果当前节点的 score 大于目标 score,向下一层
  4. 重复步骤 2-3,直到找到目标或到达底层

跳跃表的插入过程

  1. 查找插入位置
  2. 随机生成层数(1 到 MAX_LEVEL)
  3. 创建新节点,连接各层的前后指针
  4. 更新各层的跨度

跳跃表的优势

  • 实现简单,相比平衡树更容易维护
  • 支持范围查询(ZRANGE、ZREVRANGE)
  • 性能接近平衡树(红黑树)
  • 支持排名查询(ZRANK、ZREVRANK)

为什么使用跳跃表 + 字典

  • 字典:O(1) 查找 member 的 score(ZSCORE 命令)
  • 跳跃表:O(log N) 范围查询和排序(ZRANGE、ZRANK 等)

使用场景

  • 排行榜(游戏排行、热门文章等)
  • 延迟队列(按时间排序)
  • 范围查询(按分数范围查询)

3.7 编码转换总结表

数据类型 编码方式 转换条件
String INT 整数字符串
EMBSTR 长度 ≤ 44 字节
RAW 长度 > 44 字节
List ZIPLIST 元素 ≤ 64 字节 且 数量 ≤ 512
QUICKLIST 默认(Redis 3.2+)
Hash ZIPLIST 键值对 ≤ 64 字节 且 数量 ≤ 512
HT 键值对 > 64 字节 或 数量 > 512
Set INTSET 所有元素都是整数 且 数量 ≤ 512
HT 包含非整数 或 数量 > 512
ZSet ZIPLIST 元素 ≤ 64 字节 且 数量 ≤ 128
SKIPLIST 元素 > 64 字节 或 数量 > 128

3.8 内存优化策略

Redis 通过多种策略优化内存使用:

  1. 编码自动选择:根据数据特征自动选择最优编码方式
  2. 压缩列表:小数据使用 ZIPLIST 节省内存
  3. 整数编码:整数使用 INT 编码,无需额外的字符串对象
  4. 共享对象:小整数(0-9999)共享对象池,减少内存占用
  5. 渐进式 Rehash:避免一次性迁移造成内存峰值
  6. 压缩:QUICKLIST 支持 LZF 压缩,减少内存占用

3.9 性能特点总结

数据结构 查找 插入 删除 范围查询 说明
String O(1) O(1) O(1) - 直接访问
List O(N) O(1) O(1) O(N) 头尾 O(1),中间 O(N)
Hash O(1) O(1) O(1) O(N) 字典实现
Set O(1) O(1) O(1) O(N) 字典实现
ZSet O(1) O(log N) O(log N) O(log N + M) 跳跃表实现

3.10 实际应用建议

  1. String

    • 缓存、计数器、分布式锁
    • 大字符串使用 RAW 编码
  2. List

    • 消息队列、最新列表
    • 优先使用 QUICKLIST(默认)
  3. Hash

    • 对象存储、用户信息
    • 小对象使用 ZIPLIST,大对象使用 HT
  4. Set

    • 去重、集合运算
    • 纯整数集合使用 INTSET
  5. ZSet

    • 排行榜、延迟队列
    • 大集合使用 SKIPLIST(默认)

4. 命令处理模块

4.1 命令执行流程

  1. 接收命令:从客户端连接读取命令
  2. 解析命令:将命令字符串解析为命令对象
  3. 查找命令:在命令表中查找对应的命令处理器
  4. 参数验证:检查参数数量、类型是否正确
  5. 执行命令:调用命令处理器执行命令
  6. 返回结果:将结果发送给客户端

4.2 命令表

Redis 维护一个命令表,包含所有支持的命令及其处理器:

1
2
3
4
5
6
7
8
9
10
// 命令结构(简化)
struct redisCommand {
char *name; // 命令名称
redisCommandProc *proc; // 命令处理器
int arity; // 参数数量(负数表示可变参数)
char *sflags; // 命令标志(read/write/admin等)
int flags; // 标志位
long long microseconds; // 命令执行总耗时
long long calls; // 命令调用次数
};

4.3 命令分类

  • 读命令:GET、HGET、LRANGE 等
  • 写命令:SET、HSET、LPUSH 等
  • 管理命令:CONFIG、INFO、DEBUG 等
  • 事务命令:MULTI、EXEC、DISCARD 等

5. 持久化模块

Redis 提供两种持久化方式:RDB 和 AOF。

5.1 RDB 持久化

工作原理

  • 在指定时间点生成数据快照
  • 将内存数据序列化为二进制文件

触发方式

  • 手动触发SAVE(阻塞)、BGSAVE(后台)
  • 自动触发:根据配置的 save 规则
1
2
3
save 900 1      # 900秒内至少1个键变化
save 300 10 # 300秒内至少10个键变化
save 60 10000 # 60秒内至少10000个键变化

BGSAVE 实现

  • 主进程 fork 子进程
  • 子进程将数据写入 RDB 文件
  • 主进程继续处理请求(使用写时复制 COW)

5.2 AOF 持久化

工作原理

  • 记录所有写命令
  • 追加到 AOF 文件末尾

同步策略

  • always:每次写操作都同步(最安全,性能最低)
  • everysec:每秒同步一次(默认,平衡性能和安全)
  • no:由操作系统决定(性能最好,安全性最低)
1
2
appendonly yes
appendfsync everysec

AOF 重写

  • 目的:压缩 AOF 文件,去除冗余命令
  • 触发:文件大小增长超过指定百分比
  • 实现:fork 子进程,将当前数据库状态写入新 AOF 文件

5.3 混合持久化

Redis 4.0+ 支持混合持久化:

1
aof-use-rdb-preamble yes

工作原理

  • AOF 文件前半部分是 RDB 格式(快照)
  • AOF 文件后半部分是 AOF 格式(增量命令)

优势

  • 恢复速度快(RDB 部分)
  • 数据安全性高(AOF 部分)

6. 复制模块(Replication)

Redis 复制用于实现主从复制,提供数据冗余和读扩展。

6.1 复制流程

6.1.1 建立连接

  1. 从节点向主节点发送 PSYNC 命令
  2. 主节点响应,开始复制

6.1.2 全量同步(Full Sync)

触发条件

  • 从节点首次连接主节点
  • 从节点复制偏移量不在主节点复制积压缓冲区中

流程

  1. 主节点执行 BGSAVE,生成 RDB 文件
  2. 主节点将 RDB 文件发送给从节点
  3. 从节点接收 RDB 文件,加载到内存
  4. 主节点将 RDB 生成期间的命令发送给从节点
  5. 从节点执行这些命令,完成同步

6.1.3 部分同步(Partial Sync)

触发条件

  • 从节点复制偏移量在主节点复制积压缓冲区中

流程

  1. 主节点从复制积压缓冲区发送缺失的命令
  2. 从节点执行这些命令,完成同步

6.2 复制积压缓冲区(Replication Backlog)

作用

  • 保存主节点最近执行的写命令
  • 用于部分同步

配置

1
2
repl-backlog-size 1mb
repl-backlog-ttl 3600

6.3 心跳机制

作用

  • 检测主从连接是否正常
  • 从节点向主节点报告复制偏移量

配置

1
repl-ping-replica-period 10

7. 哨兵模块(Sentinel)

Redis Sentinel 用于监控 Redis 主从节点,实现自动故障转移。

7.1 核心功能

  • 监控(Monitoring):定期检查主从节点状态
  • 通知(Notification):发现故障时通知管理员
  • 自动故障转移(Automatic Failover):主节点故障时自动提升从节点
  • 配置提供者(Configuration Provider):为客户端提供主节点地址

7.2 故障检测

7.2.1 主观下线(SDOWN)

  • 单个 Sentinel 认为节点不可用
  • 通过 PING 命令检测

7.2.2 客观下线(ODOWN)

  • 多个 Sentinel 都认为节点不可用
  • 需要达到 quorum 数量
1
2
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000

7.3 故障转移流程

  1. 客观下线确认:多个 Sentinel 确认主节点下线
  2. 选举领头 Sentinel:使用 Raft 算法选举
  3. 选择新主节点
    • 排除不健康的从节点
    • 选择优先级高的从节点
    • 选择复制偏移量大的从节点
  4. 故障转移
    • 将选中的从节点提升为主节点
    • 通知其他从节点复制新主节点
    • 更新客户端配置

8. 集群模块(Cluster)

Redis Cluster 提供分布式解决方案,实现数据分片和高可用。

8.1 数据分片

槽(Slot)分配

  • Redis 集群将键空间划分为 16384 个槽
  • 每个主节点负责一部分槽
  • 键通过 CRC16(key) % 16384 计算槽号

槽分配示例

1
2
3
节点 A:0-5460
节点 B:5461-10922
节点 C:10923-16383

8.2 节点通信

Gossip 协议

  • 节点间定期交换信息
  • 包括节点状态、槽分配、故障信息等

消息类型

  • PING:检测节点是否存活
  • PONG:响应 PING
  • MEET:请求加入集群
  • FAIL:标记节点下线
  • PUBLISH:发布订阅消息

8.3 故障转移

8.3.1 故障检测

  1. 节点间通过 PING 检测状态
  2. 节点长时间未响应,标记为 PFAIL(疑似下线)
  3. 多数节点认为节点下线,标记为 FAIL(已下线)

8.3.2 选举流程

  1. 从节点发起选举:发现主节点下线后等待一段时间
  2. 主节点投票:每个主节点在一个配置纪元内只能投一票
  3. 选举成功:从节点获得超过半数主节点投票
  4. 接管槽位:新主节点接管原主节点的槽位

8.4 客户端重定向

MOVED 重定向

  • 客户端请求的键不在当前节点
  • 返回 MOVED <slot> <ip>:<port>
  • 客户端更新槽位映射,重定向到正确节点

ASK 重定向

  • 槽位正在迁移中
  • 返回 ASK <slot> <ip>:<port>
  • 客户端临时重定向,不更新槽位映射

9. 发布订阅模块(Pub/Sub)

Redis 提供发布订阅功能,实现消息传递。

9.1 核心概念

  • 频道(Channel):消息的通道
  • 发布者(Publisher):发送消息
  • 订阅者(Subscriber):接收消息

9.2 命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 订阅频道
SUBSCRIBE channel1 channel2

# 发布消息
PUBLISH channel1 "message"

# 取消订阅
UNSUBSCRIBE channel1

# 模式订阅
PSUBSCRIBE news.*

# 取消模式订阅
PUNSUBSCRIBE news.*

9.3 实现原理

  • 维护频道订阅关系字典
  • 发布消息时遍历订阅者,发送消息
  • 使用链表存储订阅者

10. 过期键处理模块

Redis 支持键的过期时间,自动删除过期键。

10.1 过期时间设置

1
2
3
EXPIRE key 60        # 60秒后过期
EXPIREAT key 1234567890 # 指定时间戳过期
TTL key # 查看剩余生存时间

10.2 过期键删除策略

10.2.1 定时删除

  • 设置过期时间时创建定时器
  • 过期时立即删除
  • 优点:内存释放及时
  • 缺点:CPU 开销大

10.2.2 惰性删除

  • 访问键时检查是否过期
  • 过期则删除
  • 优点:CPU 开销小
  • 缺点:内存可能泄漏

10.2.3 定期删除

  • 定期扫描过期键并删除
  • Redis 默认采用惰性删除 + 定期删除

定期删除流程

  1. 从过期字典中随机抽取 20 个键
  2. 删除其中过期的键
  3. 如果过期键比例 > 25%,重复步骤 1

10.3 过期键处理时机

  • 读操作:GET、HGET 等,惰性删除
  • 写操作:SET、HSET 等,先删除过期键再写入
  • 定期任务:每秒执行 10 次定期删除

11. 事务模块

Redis 提供简单的事务支持。

11.1 事务命令

1
2
3
4
5
6
7
MULTI          # 开始事务
SET key1 value1
SET key2 value2
EXEC # 执行事务
DISCARD # 取消事务
WATCH key # 监视键
UNWATCH # 取消监视

11.2 事务特性

  • 原子性:所有命令要么全部执行,要么全部不执行
  • 隔离性:事务执行期间不会被其他客户端打断
  • 一致性:事务执行前后数据库状态一致
  • 持久性:取决于持久化配置

11.3 WATCH 机制

作用

  • 监视键是否被其他客户端修改
  • 如果被修改,事务执行失败

实现原理

  • 维护被监视键的字典
  • 执行写命令时检查键是否被监视
  • 如果被监视,标记为脏键,事务执行时返回空

12. Lua 脚本模块

Redis 支持执行 Lua 脚本,实现复杂的原子操作。

12.1 脚本执行

1
2
3
4
5
6
7
8
# 执行脚本
EVAL "return redis.call('get', KEYS[1])" 1 key1

# 加载脚本
SCRIPT LOAD "return redis.call('get', KEYS[1])"

# 执行已加载的脚本
EVALSHA sha1_hash 1 key1

12.2 脚本特性

  • 原子性:脚本执行期间不会被其他命令打断
  • 可复制:主节点执行脚本,从节点重放
  • 可持久化:脚本会记录到 AOF 文件

12.3 脚本限制

  • 执行时间限制:默认 5 秒,可通过 lua-time-limit 配置
  • 脚本阻塞:脚本执行期间阻塞其他命令
  • 脚本缓存:Redis 会缓存已执行的脚本

13. 慢查询日志模块

Redis 记录执行时间超过阈值的命令。

13.1 配置

1
2
slowlog-log-slower-than 10000  # 记录超过10毫秒的命令
slowlog-max-len 128 # 最多保存128条慢查询

13.2 命令

1
2
3
SLOWLOG GET 10      # 获取最近10条慢查询
SLOWLOG LEN # 获取慢查询数量
SLOWLOG RESET # 清空慢查询日志

13.3 实现原理

  • 使用链表存储慢查询日志
  • 每条记录包含:ID、时间戳、执行时间、命令、客户端信息

14. 客户端管理模块

Redis 管理所有客户端连接。

14.1 客户端结构

1
2
3
4
5
6
7
8
9
// 客户端结构(简化)
typedef struct client {
int fd; // 客户端文件描述符
sds querybuf; // 查询缓冲区
robj **argv; // 命令参数数组
int argc; // 参数数量
struct redisCommand *cmd; // 命令对象
// ...
} client;

14.2 客户端限制

1
2
maxclients 10000    # 最大客户端连接数
timeout 300 # 客户端空闲超时时间(秒)

14.3 客户端命令

1
2
3
4
5
CLIENT LIST              # 列出所有客户端
CLIENT GETNAME # 获取客户端名称
CLIENT SETNAME name # 设置客户端名称
CLIENT KILL ip:port # 关闭客户端连接
CLIENT PAUSE timeout # 暂停客户端命令执行

15. 模块化架构

Redis 4.0+ 支持模块化扩展,可以加载自定义模块。

15.1 模块加载

1
loadmodule /path/to/module.so

15.2 模块 API

  • 数据类型 API:定义新的数据类型
  • 命令 API:定义新的命令
  • 键空间通知 API:监听键变化
  • 阻塞命令 API:实现阻塞操作

总结

Redis 的核心模块相互协作,共同实现了高性能、高可用的内存数据库:

  1. 事件驱动模型:提供高性能的网络处理能力
  2. 内存管理:高效管理内存,支持多种淘汰策略
  3. 数据结构:丰富的数据类型,灵活的编码方式
  4. 命令处理:统一的命令执行框架
  5. 持久化:RDB 和 AOF 保证数据安全
  6. 复制:主从复制实现数据冗余和读扩展
  7. 哨兵:自动故障转移,提高可用性
  8. 集群:分布式部署,水平扩展
  9. 发布订阅:消息传递功能
  10. 过期键处理:自动清理过期数据
  11. 事务:简单的原子操作支持
  12. Lua 脚本:复杂逻辑的原子执行
  13. 慢查询日志:性能监控和优化
  14. 客户端管理:连接管理和限制

这些模块共同构成了 Redis 强大的功能体系,使其成为高性能应用的理想选择。


文章作者: djaigo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 djaigo !
评论
  目录