← 返回文章列表

Redis底层原理(一):概述与架构设计

2020-01-15·5 分钟阅读

前言

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 单线程工作流程                      │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  客户端请求 ──► 接收请求 ──► 解析命令 ──► 执行命令 ──► 返回结果│
│                     │              │              │          │
│                     ▼              ▼              ▼          │
│              ┌─────────────────────────────────────────┐    │
│              │          主事件循环(单线程)             │    │
│              └─────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

为什么单线程还能这么快?

  1. 纯内存操作:数据存储在内存中,避免了磁盘 IO 的性能瓶颈

  2. 非阻塞 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);
    }
}
  1. 避免线程切换开销:没有多线程的上下文切换和锁竞争

  2. 高效的数据结构: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 的整体架构设计,包括:

  1. 核心特性:高性能、丰富数据类型、持久化、高可用
  2. 架构分层:客户端层、服务端层、基础设施层
  3. 核心组件:redisServer、redisDb、client 结构
  4. 设计原理:单线程模型、IO 多路复用、内存管理、过期策略
  5. 版本演进:从 1.0 到 7.0 的重要特性

下一章将深入分析 Redis 的基础数据结构,包括 SDS(简单动态字符串)、链表和字典的底层实现。

参考资料

分享: