Redis底层原理(一):概述与架构设计
前言
Redis 作为当今最流行的内存数据库之一,以其卓越的性能和丰富的数据结构支撑着无数高并发系统。从简单的缓存场景到复杂的分布式锁、消息队列、排行榜等应用,Redis 已经成为后端工程师的必备技能。
本系列将从底层源码角度深入剖析 Redis 的实现原理,帮助读者不仅"知其然",更"知其所以然"。本文作为系列开篇,将从宏观视角介绍 Redis 的整体架构设计。
一、Redis 简介
1.1 什么是 Redis
Redis(Remote Dictionary Server)是一个开源的、基于内存的键值对存储系统,由 Salvatore Sanfilippo 于 2009 年开发。它支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等。
┌─────────────────────────────────────────────────────────┐
│ Redis 核心定位 │
├─────────────────────────────────────────────────────────┤
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 缓存系统 │ │ 消息队列 │ │ 数据库 │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 分布式锁 │ │ 排行榜 │ │ 社交网络 │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 Redis 核心特性
| 特性 | 说明 | 应用场景 |
|---|---|---|
| 高性能 | 基于内存操作,QPS 可达 10万+ | 高并发读写、热点数据缓存 |
| 丰富数据类型 | String、Hash、List、Set、ZSet、Stream 等 | 复杂数据结构存储 |
| 原子操作 | 所有操作都是原子性的 | 分布式锁、计数器 |
| 持久化 | RDB 和 AOF 两种持久化方式 | 数据安全性要求高的场景 |
| 主从复制 | 支持一主多从架构 | 读写分离、数据备份 |
| 高可用 | Sentinel 哨兵机制 | 自动故障转移 |
| 集群 | Redis Cluster 分布式方案 | 大数据量、高吞吐场景 |
| 发布订阅 | Pub/Sub 消息模式 | 消息推送、实时通信 |
1.3 Redis 应用场景
// 典型应用场景示例
// 1. 缓存 - 最常见用途
SET user:1001 '{"name":"张三","age":25}'
EXPIRE user:1001 3600 // 1小时过期
// 2. 分布式锁
SET lock:order:1001 "uuid" NX PX 30000 // 30秒超时
// 3. 计数器
INCR article:1001:views
INCRBY user:1001:points 10
// 4. 排行榜
ZADD leaderboard 100 "player1"
ZREVRANGE leaderboard 0 9 WITHSCORES // Top 10
// 5. 消息队列(延迟队列)
ZADD delay:queue <timestamp> <task_id>
二、Redis 整体架构
2.1 架构分层
Redis 采用经典的分层架构设计,从下到上依次为:
┌─────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Java │ │ Python│ │ Go │ │ Node │ │ C/C++│ │
│ └──────┘ └──────┘ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────────────────────────┘
▼ RESP 协议
┌─────────────────────────────────────────────────────────────┐
│ 服务端层 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 命令执行器 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ SET/GET │ │ LPUSH │ │ ZADD │ ... │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 对象系统 │ │
│ │ String │ Hash │ List │ Set │ ZSet │ Stream │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 数据结构层 │ │
│ │ SDS │ Dict │ List │ ZSet │ IntSet │ ZipList │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 基础设施层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 内存管理 │ │ 持久化 │ │ 事件循环 │ │ 网络 IO │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
2.2 核心组件详解
2.2.1 服务器结构(redisServer)
Redis 服务器以 redisServer 结构体为核心,管理着服务器的所有状态:
// server.h - 简化的服务器结构
struct redisServer {
/* 通用配置 */
pid_t pid; // 进程 ID
char *configfile; // 配置文件路径
int port; // 监听端口
/* 数据库 */
redisDb *db; // 数据库数组
int dbnum; // 数据库数量(默认 16)
/* 持久化 */
pid_t rdb_child_pid; // RDB 子进程 PID
pid_t aof_child_pid; // AOF 子进程 PID
/* 统计信息 */
long long stat_numcommands; // 执行命令数
long long stat_numconnections; // 连接数
/* 客户端 */
list *clients; // 客户端链表
client *current_client; // 当前客户端
/* 事件循环 */
aeEventLoop *el; // 事件循环
/* 网络 */
char *bindaddr_count; // 绑定地址数量
int *tcp_port; // TCP 端口
int *unixsocket; // Unix 套接字
/* 复制相关 */
char *masterhost; // 主机地址
int masterport; // 主机端口
client *master; // 主机客户端
/* 集群相关 */
clusterState *cluster; // 集群状态
};
2.2.2 数据库结构(redisDb)
每个 Redis 数据库由 redisDb 结构表示:
// server.h
typedef struct redisDb {
dict *dict; // 键空间(存放所有键值对)
dict *expires; // 过期时间字典
dict *blocking_keys; // 阻塞的键
dict *ready_keys; // 就绪的键
dict *watched_keys; // 被 WATCH 的键
int id; // 数据库 ID
long long avg_ttl; // 平均 TTL
} redisDb;
键空间示意图:
┌─────────────────────────────────────────────────────────┐
│ redisDb.dict │
├─────────────────────────────────────────────────────────┤
│ Key │ Value │
├───────────────┼────────────────────────────────────────┤
│ "user:1001" │ String Object: "张三" │
│ "counter" │ String Object: 100 │
│ "list:msg" │ List Object: [a, b, c] │
│ "set:tags" │ Set Object: {tag1, tag2} │
│ "zset:rank" │ ZSet Object: {(a,1), (b,2)} │
│ "hash:user" │ Hash Object: {name: "李四"} │
└─────────────────────────────────────────────────────────┘
2.3 客户端结构(client)
Redis 使用 client 结构表示每个连接的客户端:
// server.h - 简化的客户端结构
typedef struct client {
uint64_t id; // 客户端唯一 ID
redisDb *db; // 当前使用的数据库
robj *name; // 客户端名称
/* 查询缓冲区 */
sds querybuf; // 查询缓冲区
size_t querybuf_peak; // 缓冲区峰值
/* 命令参数 */
int argc; // 参数数量
robj **argv; // 参数数组
struct redisCommand *cmd; // 要执行的命令
/* 输出缓冲区 */
int bufpos; // 固定缓冲区位置
char buf[PROTO_REPLY_CHUNK_BYTES]; // 固定缓冲区
list *reply; // 可变缓冲区链表
/* 状态标志 */
int flags; // 客户端状态标志
/* 认证信息 */
robj *user; // 认证用户
int authenticated; // 是否已认证
} client;
三、Redis 核心设计原理
3.1 单线程模型
Redis 采用单线程模型处理命令请求,这是其高性能的关键设计之一:
┌──────────────────────────────────────────────────────────────┐
│ Redis 单线程工作流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 客户端请求 ──► 接收请求 ──► 解析命令 ──► 执行命令 ──► 返回结果│
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ 主事件循环(单线程) │ │
│ └─────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
为什么单线程还能这么快?
-
纯内存操作:数据存储在内存中,避免了磁盘 IO 的性能瓶颈
-
非阻塞 IO:使用 epoll/kqueue 等 IO 多路复用技术
// Redis 事件循环核心代码 (ae.c)
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) {
// 处理事件
numevents = aeApiPoll(eventLoop, tvp);
for (j = 0; j < numevents; j++) {
// 处理文件事件(网络 IO)
if (fe->mask & mask) {
fe->fileProc(eventLoop, fd, fe->clientData, mask);
}
}
// 处理时间事件(定时任务)
if (flags & AE_TIME_EVENTS)
processed += processTimeEvents(eventLoop);
}
}
-
避免线程切换开销:没有多线程的上下文切换和锁竞争
-
高效的数据结构:SDS、压缩列表、跳跃表等经过精心设计
3.2 IO 多路复用
Redis 使用 IO 多路复用技术处理大量并发连接:
┌──────────────────────────────────────────────────────────────┐
│ IO 多路复用模型 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌───────┐ │
│ │Client1│──┐ │
│ └───────┘ │ ┌─────────────────┐ ┌──────────────┐ │
│ ┌───────┐ │ │ │ │ │ │
│ │Client2│──┼───►│ IO 多路复用器 │───►│ 事件分发器 │ │
│ └───────┘ │ │ (epoll/kqueue) │ │ │ │
│ ┌───────┐ │ │ │ └──────┬───────┘ │
│ │Client3│──┘ └─────────────────┘ │ │
│ └───────┘ ▼ │
│ ┌──────────────┐ │
│ │ 命令处理器 │ │
│ └──────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
// 不同平台的 IO 多路复用实现
#ifdef HAVE_EPOLL
#include "ae_epoll.c" // Linux
#elif HAVE_KQUEUE
#include "ae_kqueue.c" // macOS/BSD
#elif HAVE_EVPORT
#include "ae_evport.c" // Solaris
#else
#include "ae_select.c" // 通用 select
#endif
3.3 内存管理
Redis 实现了自己的内存管理机制:
3.3.1 内存分配
// zmalloc.h - Redis 内存分配封装
void *zmalloc(size_t size); // 分配内存
void *zcalloc(size_t size); // 分配并清零
void zfree(void *ptr); // 释放内存
void *zrealloc(void *ptr, size_t size); // 重新分配
// 内部实现使用 jemalloc 或 tcmalloc
#if defined(USE_JEMALLOC)
#include <jemalloc/jemalloc.h>
#elif defined(USE_TCMALLOC)
#include <google/tcmalloc.h>
#endif
3.3.2 内存淘汰策略
当内存使用达到上限时,Redis 提供多种淘汰策略:
┌──────────────────────────────────────────────────────────────┐
│ 内存淘汰策略 │
├──────────────┬───────────────────────────────────────────────┤
│ 策略名称 │ 说明 │
├──────────────┼───────────────────────────────────────────────┤
│ noeviction │ 不淘汰,内存满时返回错误 │
│ allkeys-lru │ 从所有键中使用 LRU 算法淘汰 │
│ volatile-lru │ 从设置了过期时间的键中使用 LRU 淘汰 │
│ allkeys-lfu │ 从所有键中使用 LFU 算法淘汰 │
│ volatile-lfu │ 从设置了过期时间的键中使用 LFU 淘汰 │
│ allkeys-random│ 从所有键中随机淘汰 │
│ volatile-random│ 从设置了过期时间的键中随机淘汰 │
│ volatile-ttl │ 从设置了过期时间的键中选择 TTL 最小的淘汰 │
└──────────────┴───────────────────────────────────────────────┘
配置方式:
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
3.4 过期键删除策略
Redis 采用惰性删除 + 定期删除的组合策略:
┌──────────────────────────────────────────────────────────────┐
│ 过期键删除策略 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 惰性删除 │ │ 定期删除 │ │
│ │ │ │ │ │
│ │ 访问时检查过期 │ │ 定时随机检查 │ │
│ │ 过期则删除 │ │ 过期则删除 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ └───────────┬─────────────┘ │
│ ▼ │
│ 最小化 CPU 和内存开销 │
│ │
└──────────────────────────────────────────────────────────────┘
// 惰性删除 - db.c
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
robj *val;
if (expireIfNeeded(db, key) == 1) {
// 键已过期,返回 NULL
return NULL;
}
val = dictFind(db->dict, key->ptr);
return val;
}
// 定期删除 - expire.c
void activeExpireCycle(int type) {
// 随机选择一些键检查是否过期
// 每次执行时间限制,避免阻塞
}
四、Redis 执行流程
4.1 命令执行流程
一个完整的 Redis 命令执行流程:
┌──────────────────────────────────────────────────────────────┐
│ 命令执行流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. 客户端发送命令 │
│ │ │
│ ▼ │
│ 2. 服务端读取到 querybuf │
│ │ │
│ ▼ │
│ 3. 解析命令,填充 argc 和 argv │
│ │ │
│ ▼ │
│ 4. 查找命令表,找到对应的 redisCommand │
│ │ │
│ ▼ │
│ 5. 执行命令前的检查(权限、参数等) │
│ │ │
│ ▼ │
│ 6. 调用命令实现函数 │
│ │ │
│ ▼ │
│ 7. 将结果写入输出缓冲区 │
│ │ │
│ ▼ │
│ 8. 返回响应给客户端 │
│ │
└──────────────────────────────────────────────────────────────┘
4.2 命令表结构
Redis 使用命令表存储所有支持的命令:
// server.h - 命令结构
struct redisCommand {
char *name; // 命令名称
redisCommandProc *proc; // 命令实现函数
int arity; // 参数个数,-N 表示大于等于 N
char *sflags; // 字符串格式的标志
uint64_t flags; // 二进制标志
redisGetKeysProc *getkeys_proc; // 获取键的函数
int firstkey; // 第一个键的位置
int lastkey; // 最后一个键的位置
int keystep; // 键的步长
long long microseconds; // 执行时间统计
long long calls; // 调用次数统计
};
// 命令表部分示例
struct redisCommand redisCommandTable[] = {
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
{"lpush",lpushCommand,-4,"wmF",0,NULL,1,1,1,0,0},
{"zadd",zaddCommand,-4,"wmF",0,NULL,1,1,1,0,0},
// ... 更多命令
};
五、Redis 设计哲学
5.1 核心设计原则
| 原则 | 说明 | 体现 |
|---|---|---|
| 简单优先 | 保持代码简洁,避免过度设计 | 单线程模型、简单协议 |
| 性能至上 | 追求极致的执行效率 | 内存操作、高效数据结构 |
| 实用主义 | 功能设计以实用为主 | 丰富的数据类型、原子操作 |
| 可扩展性 | 支持水平扩展 | 集群模式、主从复制 |
| 可靠性 | 保证数据安全 | 持久化、复制、哨兵 |
5.2 技术选型原因
┌──────────────────────────────────────────────────────────────┐
│ 技术选型理由 │
├──────────────────────────────────────────────────────────────┤
│ │
│ Q: 为什么用 C 语言开发? │
│ A: 接近底层,性能可控;手动内存管理;系统调用方便 │
│ │
│ Q: 为什么单线程? │
│ A: 避免锁竞争;CPU 不是瓶颈;代码简单易维护 │
│ │
│ Q: 为什么不用 malloc 而封装? │
│ A: 统计内存使用;选择高性能分配器;便于调试 │
│ │
│ Q: 为什么实现自己的数据结构? │
│ A: 针对场景优化;减少内存碎片;提高访问效率 │
│ │
└──────────────────────────────────────────────────────────────┘
六、Redis 版本演进
6.1 重要版本里程碑
┌──────────────────────────────────────────────────────────────┐
│ Redis 版本演进 │
├───────────┬──────────────────────────────────────────────────┤
│ 版本 │ 重要特性 │
├───────────┼──────────────────────────────────────────────────┤
│ 1.0 (2009)│ 首次发布,基础数据类型 │
│ 2.0 (2010)│ 虚拟内存、发布订阅 │
│ 2.6 (2012)│ Lua 脚本、毫秒级过期 │
│ 2.8 (2013)│ 部分重同步、哨兵改进 │
│ 3.0 (2015)│ Redis Cluster 集群 │
│ 3.2 (2016)│ GEO 地理位置、Lua 脚本改进 │
│ 4.0 (2017)│ Module 系统、混合持久化、PSYNC2 │
│ 5.0 (2018)│ Stream 数据类型、集群改进 │
│ 6.0 (2020)│ 多线程 IO、ACL、客户端缓存 │
│ 6.2 (2021)│ 新命令和改进、Function 功能 │
│ 7.0 (2022)│ Function 升级、Stream 改进、ACL 增强 │
└───────────┴──────────────────────────────────────────────────┘
6.2 Redis 6.0 多线程 IO
Redis 6.0 引入了多线程 IO,但命令执行仍是单线程:
┌──────────────────────────────────────────────────────────────┐
│ Redis 6.0 多线程 IO 模型 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ 主线程 │ ◄── 命令执行(单线程) │
│ │ 事件循环 │ │
│ └─────────────┘ │
│ ▲ │
│ │ │
│ ┌─────┴─────┐ │
│ │ IO 线程组 │ ◄── 读写数据(多线程) │
│ │ T1 T2 T3 │ │
│ └───────────┘ │
│ │
│ 优势:利用多核 CPU 加速网络数据处理 │
│ 注意:需要配置 io-threads 和 io-threads-do-reads │
│ │
└──────────────────────────────────────────────────────────────┘
配置示例:
# redis.conf
io-threads 4
io-threads-do-reads yes
七、学习路线图
7.1 本系列文章结构
┌──────────────────────────────────────────────────────────────┐
│ Redis 底层原理系列 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 第一章(本文)概述与架构设计 │
│ │ │
│ ▼ │
│ 第二章 基础数据结构(SDS、链表、字典) │
│ │ │
│ ▼ │
│ 第三章 核心数据结构(跳跃表、整数集合、压缩列表) │
│ │ │
│ ▼ │
│ 第四章 对象系统与类型系统 │
│ │ │
│ ▼ │
│ 第五章 持久化机制 │
│ │ │
│ ▼ │
│ 第六章 事件驱动模型 │
│ │ │
│ ▼ │
│ 第七章 复制与哨兵机制 │
│ │ │
│ ▼ │
│ 第八章 集群原理 │
│ │ │
│ ▼ │
│ 第九章 生产实践与最佳实践 │
│ │
└──────────────────────────────────────────────────────────────┘
7.2 推荐学习资源
| 类型 | 资源 | 说明 |
|---|---|---|
| 官方 | redis.io | 官方文档 |
| 书籍 | 《Redis设计与实现》 | 经典源码分析 |
| 书籍 | 《Redis开发与运维》 | 实战经验 |
| 源码 | github.com/redis/redis | 最新源码 |
| 工具 | redis-cli、RedisInsight | 客户端工具 |
总结
本文从宏观视角介绍了 Redis 的整体架构设计,包括:
- 核心特性:高性能、丰富数据类型、持久化、高可用
- 架构分层:客户端层、服务端层、基础设施层
- 核心组件:redisServer、redisDb、client 结构
- 设计原理:单线程模型、IO 多路复用、内存管理、过期策略
- 版本演进:从 1.0 到 7.0 的重要特性
下一章将深入分析 Redis 的基础数据结构,包括 SDS(简单动态字符串)、链表和字典的底层实现。
参考资料
- Redis Official Documentation
- Redis GitHub Repository
- 《Redis设计与实现》- 黄健宏
- 《Redis开发与运维》- 付磊、张益军
相关文章
Milvus底层原理(一):概述与架构设计
深入理解 Milvus 向量数据库的整体架构设计,探索存储计算分离、分布式查询、向量索引等核心原理,为后续深入学习 Milvus 底层实现奠定基础。
Milvus底层原理(七):数据模型与存储
深入理解 Milvus 的数据模型设计,掌握 Collection、Partition、Segment 的层次结构,了解列式存储格式和 Schema 设计原则。
Milvus底层原理(八):数据写入流程
深入理解 Milvus 的数据写入流程,掌握从客户端请求到数据持久化的完整链路,了解写入优化策略和数据一致性保证机制。