首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++项目推荐-真正可以媲美redis的kv存储项目-包括性能如何逐步优化

C++项目推荐-真正可以媲美redis的kv存储项目-包括性能如何逐步优化

原创
作者头像
程序员老廖
发布2026-01-29 15:54:30
发布2026-01-29 15:54:30
1090
举报

来源:程序员老廖

概述

本教程将手把手带你从零实现一个高性能的Mini-Redis,涵盖RESP协议解析、事件驱动网络编程、数据结构实现、持久化和主从复制等核心技术。

这个kv存储项目是可以真正写入简历的C++项目,不同于网上其它的kv存储项目,加了持久化后,QPS测试性能只能到一千多,而这个项目可以达到55000+,性能可以媲美redis

视频讲解及源码领取: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 请求处理流程

一个完整的请求经历以下阶段:

  1. 网络接收: epoll监听客户端连接和数据
  2. 协议解析: 解析RESP格式的命令
  3. 命令执行: 在KV存储中执行操作
  4. 持久化: AOF记录命令,RDB定期快照
  5. 主从复制: 同步命令到从节点
  6. 响应返回: 将结果序列化为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
复制
# 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
复制
mkdir mini-redis && cd mini-redis
mkdir -p include/mini_redis src docs tools build

项目目录结构:

代码语言:javascript
复制
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
复制
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
复制
cd mini-redis
cmake -S . -B build
cmake --build build -j

2.4.2 单机启动模式

Mini-Redis 支持三种持久化配置模式:

1. 无持久化模式 (none.conf)

适用于:测试、缓存场景,不需要数据持久化

代码语言:javascript
复制
# 启动服务器
./build/mini_redis --config build/none.conf
​
# 服务器将在端口 6388 启动,无 AOF 和 RDB

2. 每秒同步模式 (everysec.conf)

适用于:生产环境,平衡性能和数据安全

代码语言:javascript
复制
# 启动服务器
./build/mini_redis --config build/everysec.conf
​
# 服务器将在端口 6388 启动,AOF 每秒同步一次

3. 立即同步模式 (always.conf)

适用于:对数据一致性要求极高的场景

代码语言:javascript
复制
# 启动服务器  
./build/mini_redis --config build/always.conf
​
# 服务器将在端口 6388 启动,每个写操作立即同步到磁盘

配置文件详情

none.conf

代码语言:javascript
复制
port=6388
aof.enabled=false
rdb.enabled=false

everysec.conf

代码语言:javascript
复制
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
复制
port=6388
aof.enabled=true
aof.mode=always
aof.dir=./data
aof.filename=appendonly.aof
rdb.enabled=false

2.4.3 主从复制模式

主节点启动

创建主节点配置文件 master.conf:

代码语言:javascript
复制
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
复制
./build/mini_redis --config master.conf

从节点启动

创建从节点配置文件 replica.conf:

代码语言:javascript
复制
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
复制
./build/mini_redis --config replica.conf

2.4.4 使用 redis-cli 进行测试

连接测试

代码语言:javascript
复制
# 连接到单机模式
redis-cli -p 6388
​
# 连接到主节点
redis-cli -p 6379
​
# 连接到从节点
redis-cli -p 6380

基本命令测试

连接和状态

代码语言:javascript
复制
# 测试连接
redis-cli -p 6388 PING
​
# 获取服务器信息
redis-cli -p 6388 INFO
​
# 回显测试
redis-cli -p 6388 ECHO "Hello Mini-Redis"

String 操作

代码语言:javascript
复制
# 设置键值
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
复制
# 设置 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
复制
# 添加成员和分数
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
复制
# 列出所有键
redis-cli -p 6388 KEYS "*"
​
# 清空所有数据
redis-cli -p 6388 FLUSHALL
​
# 触发 RDB 快照保存
redis-cli -p 6388 BGSAVE

主从复制测试

1. 在主节点写入数据

代码语言:javascript
复制
# 连接主节点并写入
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
复制
# 连接从节点并读取
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
复制
# 创建测试数据文件
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3" > test-commands.txt
​
# 通过管道执行
redis-cli -p 6388 --pipe < test-commands.txt

性能测试

代码语言:javascript
复制
# 基本性能测试
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
复制
# 基准测试命令
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
复制
// 优化前:同步写入(性能杀手)
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
复制
// 优化技巧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
复制
// 小集合用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
复制
// 预分配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.

学习价值

  • 系统设计能力:理解高性能服务器架构
  • 网络编程技能:掌握epoll、非阻塞I/O
  • 存储系统知识:学会设计持久化方案
  • 性能调优经验:掌握常见优化技巧
  • 面试准备:涵盖Redis相关高频面试题

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 项目目标
  • 技术栈
  • 第一章:项目架构设计
  • 1.1 整体架构
  • 1.2 请求处理流程
  • 1.3 模块划分
  • 第二章:环境准备与项目搭建
  • 2.1 环境要求
  • 2.2 项目结构创建
  • 2.3 CMake配置文件
  • 2.4 编译和使用
    • 2.4.1 构建项目
    • 2.4.2 单机启动模式
    • 1. 无持久化模式 (none.conf)
    • 2. 每秒同步模式 (everysec.conf)
    • 3. 立即同步模式 (always.conf)
    • 配置文件详情
    • none.conf
    • everysec.conf
    • always.conf
    • 2.4.3 主从复制模式
    • 主节点启动
    • 从节点启动
    • 2.4.4 使用 redis-cli 进行测试
    • 连接测试
    • 基本命令测试
    • 连接和状态
    • String 操作
    • Hash 操作
    • ZSet (有序集合) 操作
    • 其他操作
    • 主从复制测试
    • 1. 在主节点写入数据
    • 2. 在从节点读取数据
    • 批量操作测试
    • 使用管道模式
    • 性能测试
  • 第三章:RESP协议实现
  • 第四章:网络编程与事件循环
  • 第五章:KV存储引擎实现
  • 第六章:持久化实现
  • 第七章:主从复制实现
  • 第八章:性能优化与调优
  • 8.1 性能测试基准
    • 8.1.1 性能优化对比
  • 8.2 关键优化技术
    • 8.2.1 AOF性能优化核心
    • 8.2.2 网络I/O优化
  • 8.3 内存与算法优化
    • 8.3.1 ZSet自适应存储
    • 8.3.2 预分配和对象复用
  • 结语
  • 技术重点
  • 学习价值
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档