C++项目推荐-真正可以媲美redis的kv存储项目-包括性能如何逐步优化
项目目标
- 协议兼容: 支持标准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.
更多推荐
所有评论(0)