项目目标
- 协议兼容: 支持标准RESP协议,兼容redis-cli工具
- 高性能: 单机QPS达5万+,AOF开启后仍保持高性能
- 完整功能: 数据结构、持久化、过期、主从复制
- 教学导向: 代码清晰,文档详细,适合学习
技术栈
- 语言: C++17
- 网络: epoll事件驱动
- 协议: RESP(Redis Serialization Protocol)
- 数据结构: unordered_map + 跳表
- 持久化: AOF + RDB
- 构建: CMake
第一章:项目架构设计
1.1 整体架构
1.2 请求处理流程
一个完整的请求经历以下阶段:
- 网络接收: epoll监听客户端连接和数据
- 协议解析: 解析RESP格式的命令
- 命令执行: 在KV存储中执行操作
- 持久化: AOF记录命令,RDB定期快照
- 主从复制: 同步命令到从节点
- 响应返回: 将结果序列化为RESP格式返回
1.3 模块划分
| 模块 | 文件 | 职责 |
|---|
| 网络层 | server.cpp | epoll事件循环,TCP连接管理 | | 协议层 | resp.hpp/cpp | RESP协议解析和序列化 | | 存储层 | kv.hpp/cpp | 数据结构实现,过期管理 | | 持久化 | aof.hpp/cpp, rdb.hpp/cpp | AOF/RDB持久化 | | 复制 | replica_client.hpp/cpp | 主从复制 | | 配置 | config.hpp, config_loader.cpp | 配置解析 |
第二章:环境准备与项目搭建
2.1 环境要求
代码语言:javascript
AI代码解释 - # Ubuntu/Debian
- sudo apt install build-essential cmake pkg-config
-
- # CentOS/RHEL
- sudo yum install gcc-c++ cmake make
-
- # 检查版本
- g++ --version # 需要支持C++17
- cmake --version # 建议3.15+
复制代码
2.2 项目结构创建
代码语言:javascript
AI代码解释 - mkdir mini-redis && cd mini-redis
- mkdir -p include/mini_redis src docs tools build
复制代码
项目目录结构:
代码语言:javascript
AI代码解释 - mini-redis/
- ├── CMakeLists.txt # CMake构建文件
- ├── include/mini_redis/ # 头文件
- │ ├── config.hpp # 配置结构体
- │ ├── resp.hpp # RESP协议
- │ ├── kv.hpp # KV存储
- │ ├── aof.hpp # AOF持久化
- │ ├── rdb.hpp # RDB持久化
- │ └── replica_client.hpp # 主从复制
- ├── src/ # 源文件
- │ ├── main.cpp # 程序入口
- │ ├── server.cpp # 服务器主逻辑
- │ ├── resp.cpp # RESP实现
- │ ├── kv.cpp # KV存储实现
- │ ├── aof.cpp # AOF实现
- │ ├── rdb.cpp # RDB实现
- │ ├── replica_client.cpp # 复制实现
- │ └── config_loader.cpp # 配置加载
- ├── docs/ # 文档
- ├── tools/ # 工具脚本
- └── build/ # 构建目录
复制代码
2.3 CMake配置文件
创建 CMakeLists.txt:
代码语言:javascript
AI代码解释 - cmake_minimum_required(VERSION 3.15)
- project(mini_redis)
-
- set(CMAKE_CXX_STANDARD 17)
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
-
- # 编译选项
- set(CMAKE_CXX_FLAGS "-Wall -Wextra -g")
- set(CMAKE_BUILD_TYPE Release)
- set(CMAKE_CXX_FLAGS_RELEASE "-O3 -march=native")
-
- # 头文件路径
- include_directories(include)
-
- # 源文件
- set(SOURCES
- src/main.cpp
- src/server.cpp
- src/resp.cpp
- src/kv.cpp
- src/aof.cpp
- src/rdb.cpp
- src/replica_client.cpp
- src/config_loader.cpp
- )
-
- # 可执行文件
- add_executable(mini_redis ${SOURCES})
-
- # 链接库
- find_package(Threads REQUIRED)
- target_link_libraries(mini_redis PRIVATE Threads::Threads)
复制代码
2.4 编译和使用
2.4.1 构建项目
代码语言:javascript
AI代码解释 - cd mini-redis
- cmake -S . -B build
- cmake --build build -j
复制代码
2.4.2 单机启动模式
Mini-Redis 支持三种持久化配置模式:
1. 无持久化模式 (none.conf)
适用于:测试、缓存场景,不需要数据持久化
代码语言:javascript
AI代码解释 - # 启动服务器
- ./build/mini_redis --config build/none.conf
-
- # 服务器将在端口 6388 启动,无 AOF 和 RDB
复制代码
2. 每秒同步模式 (everysec.conf)
适用于:生产环境,平衡性能和数据安全
代码语言:javascript
AI代码解释 - # 启动服务器
- ./build/mini_redis --config build/everysec.conf
-
- # 服务器将在端口 6388 启动,AOF 每秒同步一次
复制代码
3. 立即同步模式 (always.conf)
适用于:对数据一致性要求极高的场景
代码语言:javascript
AI代码解释 - # 启动服务器
- ./build/mini_redis --config build/always.conf
-
- # 服务器将在端口 6388 启动,每个写操作立即同步到磁盘
复制代码
配置文件详情
none.conf
代码语言:javascript
AI代码解释 - port=6388
- aof.enabled=false
- rdb.enabled=false
复制代码
everysec.conf
代码语言:javascript
AI代码解释 - port=6388
- aof.enabled=true
- aof.mode=everysec
- aof.dir=./data
- aof.filename=appendonly.aof
- rdb.enabled=false
- aof.batch_bytes=262144
- aof.batch_wait_us=2000
- aof.prealloc_bytes=67108864
- aof.sync_interval_ms=250
复制代码
always.conf
代码语言:javascript
AI代码解释 - port=6388
- aof.enabled=true
- aof.mode=always
- aof.dir=./data
- aof.filename=appendonly.aof
- rdb.enabled=false
复制代码
2.4.3 主从复制模式
主节点启动
创建主节点配置文件 master.conf:
代码语言:javascript
AI代码解释 - port=6379
- bind_address=0.0.0.0
-
- # AOF 持久化
- aof.enabled=true
- aof.mode=everysec
- aof.dir=./data-master
- aof.filename=appendonly.aof
-
- # RDB 快照
- rdb.enabled=true
- rdb.dir=./data-master
- rdb.filename=dump.rdb
复制代码
启动主节点:
代码语言:javascript
AI代码解释 - ./build/mini_redis --config master.conf
复制代码
从节点启动
创建从节点配置文件 replica.conf:
代码语言:javascript
AI代码解释 - port=6380
- bind_address=0.0.0.0
-
- # RDB 用于接收主节点快照
- rdb.enabled=true
- rdb.dir=./data-replica
- rdb.filename=dump.rdb
-
- # 从节点一般不开启 AOF
- aof.enabled=false
-
- # 复制配置
- replica.enabled=true
- replica.master_host=127.0.0.1
- replica.master_port=6379
复制代码
启动从节点:
代码语言:javascript
AI代码解释 - ./build/mini_redis --config replica.conf
复制代码
2.4.4 使用 redis-cli 进行测试
连接测试
代码语言:javascript
AI代码解释 - # 连接到单机模式
- redis-cli -p 6388
-
- # 连接到主节点
- redis-cli -p 6379
-
- # 连接到从节点
- redis-cli -p 6380
复制代码
基本命令测试
连接和状态
代码语言:javascript
AI代码解释 - # 测试连接
- redis-cli -p 6388 PING
-
- # 获取服务器信息
- redis-cli -p 6388 INFO
-
- # 回显测试
- redis-cli -p 6388 ECHO "Hello Mini-Redis"
复制代码
String 操作
代码语言:javascript
AI代码解释 - # 设置键值
- redis-cli -p 6388 SET mykey "Hello World"
-
- # 获取值
- redis-cli -p 6388 GET mykey
-
- # 删除键
- redis-cli -p 6388 DEL mykey
-
- # 设置过期时间(秒)
- redis-cli -p 6388 SET tempkey "temporary"
- redis-cli -p 6388 EXPIRE tempkey 60
-
- # 查看剩余过期时间
- redis-cli -p 6388 TTL tempkey
-
- # 检查键是否存在
- redis-cli -p 6388 EXISTS mykey
复制代码
Hash 操作
代码语言:javascript
AI代码解释 - # 设置 Hash 字段
- redis-cli -p 6388 HSET user:1 name "Alice"
- redis-cli -p 6388 HSET user:1 age "25"
- redis-cli -p 6388 HSET user:1 city "Beijing"
-
- # 获取 Hash 字段
- redis-cli -p 6388 HGET user:1 name
-
- # 获取所有字段和值
- redis-cli -p 6388 HGETALL user:1
-
- # 检查字段是否存在
- redis-cli -p 6388 HEXISTS user:1 email
-
- # 删除字段
- redis-cli -p 6388 HDEL user:1 age
-
- # 获取字段数量
- redis-cli -p 6388 HLEN user:1
复制代码
ZSet (有序集合) 操作
代码语言:javascript
AI代码解释 - # 添加成员和分数
- redis-cli -p 6388 ZADD leaderboard 100 "player1"
- redis-cli -p 6388 ZADD leaderboard 85 "player2"
- redis-cli -p 6388 ZADD leaderboard 92 "player3"
-
- # 按分数范围查询(默认升序)
- redis-cli -p 6388 ZRANGE leaderboard 0 -1
-
- # 按分数范围查询并显示分数
- redis-cli -p 6388 ZRANGE leaderboard 0 -1 WITHSCORES
-
- # 获取成员分数
- redis-cli -p 6388 ZSCORE leaderboard "player2"
-
- # 删除成员
- redis-cli -p 6388 ZREM leaderboard "player2"
复制代码
其他操作
代码语言:javascript
AI代码解释 - # 列出所有键
- redis-cli -p 6388 KEYS "*"
-
- # 清空所有数据
- redis-cli -p 6388 FLUSHALL
-
- # 触发 RDB 快照保存
- redis-cli -p 6388 BGSAVE
复制代码
主从复制测试
1. 在主节点写入数据
代码语言:javascript
AI代码解释 - # 连接主节点并写入
- redis-cli -p 6379 SET repl:test "master-data"
- redis-cli -p 6379 HSET repl:hash field1 "value1"
- redis-cli -p 6379 ZADD repl:zset 90 "item1"
复制代码
2. 在从节点读取数据
代码语言:javascript
AI代码解释 - # 连接从节点并读取
- redis-cli -p 6380 GET repl:test
- redis-cli -p 6380 HGETALL repl:hash
- redis-cli -p 6380 ZRANGE repl:zset 0 -1 WITHSCORES
复制代码
批量操作测试
使用管道模式
代码语言:javascript
AI代码解释 - # 创建测试数据文件
- echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" > test-commands.txt
-
- # 通过管道执行
- redis-cli -p 6388 --pipe < test-commands.txt
复制代码
性能测试
代码语言:javascript
AI代码解释 - # 基本性能测试
- redis-benchmark -h 127.0.0.1 -p 6388 -n 10000 -c 50
-
- # 测试 SET 操作
- redis-benchmark -h 127.0.0.1 -p 6388 -t set -n 10000 -d 100
-
- # 测试 GET 操作
- redis-benchmark -h 127.0.0.1 -p 6388 -t get -n 10000
复制代码
第三章:RESP协议实现
第四章:网络编程与事件循环
第五章:KV存储引擎实现
第六章:持久化实现
第七章:主从复制实现
这几个章节需要搭配源码一起看,详细的学习文档和源码都在这个视频讲解中给出来,大家可以去观看领取
第八章:性能优化与调优
8.1 性能测试基准
使用redis-benchmark进行性能测试,对比优化前后的效果:
代码语言:javascript
AI代码解释 - # 基准测试命令
- redis-benchmark -h 127.0.0.1 -p 6379 -n 100000 -c 50 -P 10 -t set,get
- redis-benchmark -h 127.0.0.1 -p 6379 -n 50000 -c 10 -P 1 -d 1000 -t set,get
复制代码
8.1.1 性能优化对比
| 优化项 | 优化前QPS | 优化后QPS | 提升倍数 | 备注 |
|---|
| 基础实现 | 15k | - | - | 阻塞I/O + 同步AOF | | 非阻塞I/O + epoll | 15k | 45k | 3x | 事件驱动 | | 边沿触发(EPOLLET) | 45k | 52k | 1.15x | 减少系统调用 | | writev批量发送 | 52k | 58k | 1.12x | 减少网络系统调用 | | AOF异步写入 | 1.4k | 55k | 39x | AOF模式下的巨大提升 | | Group Commit | 55k | 48k | 0.87x | always模式权衡 |
8.2 关键优化技术
8.2.1 AOF性能优化核心
最关键的优化是AOF的异步写入机制:
代码语言:javascript
AI代码解释 - // 优化前:同步写入(性能杀手)
- void appendAOF_slow(const std::string& cmd) {
- std::ofstream file(aof_path_, std::ios::app);
- file << cmd;
- file.flush(); // 立即刷盘,QPS暴跌
- }
-
- // 优化后:异步批量写入
- void appendAOF_fast(const std::string& cmd) {
- {
- std::lock_guard<std::mutex> lock(queue_mutex_);
- aof_queue_.push(cmd);
- pending_bytes_ += cmd.size();
- }
- cv_.notify_one(); // 唤醒后台写入线程
- }
复制代码
8.2.2 网络I/O优化
代码语言:javascript
AI代码解释 - // 优化技巧1:TCP_NODELAY避免Nagle算法延迟
- int opt = 1;
- setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
-
- // 优化技巧2:writev批量发送
- struct iovec iov[64]; // 增大到64个iovec
- int count = 0;
- for (const auto& chunk : out_chunks) {
- iov[count].iov_base = (void*)chunk.data();
- iov[count].iov_len = chunk.size();
- if (++count >= 64) break;
- }
- writev(fd, iov, count);
-
- // 优化技巧3:边沿触发一次性读完
- while (true) {
- ssize_t n = read(fd, buf, sizeof(buf));
- if (n <= 0) {
- if (errno == EAGAIN) break; // 读完了
- // 处理错误
- }
- process_data(buf, n);
- }
复制代码
8.3 内存与算法优化
8.3.1 ZSet自适应存储
代码语言:javascript
AI代码解释 - // 小集合用vector,大集合用跳表
- class ZSetRecord {
- static constexpr size_t THRESHOLD = 128;
-
- std::vector<std::pair<double, std::string>> small_set; // <128元素
- std::unique_ptr<Skiplist> skiplist; // >=128元素
-
- void checkAndConvert() {
- if (!use_skiplist && small_set.size() >= THRESHOLD) {
- convertToSkiplist(); // 自动升级
- }
- }
- };
复制代码
8.3.2 预分配和对象复用
代码语言:javascript
AI代码解释 - // 预分配AOF文件空间,避免频繁扩展
- void preallocateAOF(int fd, size_t size) {
- if (posix_fallocate(fd, 0, size) == 0) {
- printf("Preallocated %zu bytes for AOF\n", size);
- }
- }
-
- // 连接对象复用
- class ConnectionPool {
- std::vector<std::unique_ptr<Conn>> free_conns_;
-
- std::unique_ptr<Conn> acquire() {
- if (!free_conns_.empty()) {
- auto conn = std::move(free_conns_.back());
- free_conns_.pop_back();
- conn->reset(); // 重置状态
- return conn;
- }
- return std::make_unique<Conn>();
- }
- };
复制代码
结语
技术重点
- 高性能:异步AOF、批量I/O、边沿触发
- 完整架构:网络层、协议层、存储层分离
- 数据结构:跳表、自适应ZSet、Hash表
- 持久化:AOF+RDB双重保障
- 复制:主从同步、部分重同步
- 优化:从1.4k到55k QPS的性能提升(这里是针对SET),如果是GET可以媲美Redis.
|