从零实现一个高性能分布式 KV 存储:多引擎 + 三网络模型 + 主从同步

张开发
2026/4/9 1:53:14 15 分钟阅读

分享文章

从零实现一个高性能分布式 KV 存储:多引擎 + 三网络模型 + 主从同步
很多小伙伴学完数据结构、计算机网络后总觉得知识点是零散的哈希表只会做简单 CRUDepoll 只会写 echo 服务器分布式更是只听过概念。所以我就想能不能做一个项目把这些知识点串起来真正吃透底层原理这就是这个 KV 存储的由来 —— 不是为了造轮子而是为了把书本上的理论变成能跑、能测、能落地的代码。整个项目没有用任何第三方框架从网络连接、协议解析到数据存储、主从同步全是从零实现。今天就用最平易近人的话跟大家聊聊这个项目的来龙去脉不管你是刚学 C 语言的新手还是想巩固底层知识的同行都能看懂github仓库https://github.com/qingliuyuanbenyuan/9.1-kvstore一、项目整体架构四层解耦简单又灵活先给大家看一张我梳理的架构图其实核心逻辑特别简单就像一个流水线客户端比如 redis-cli发起请求网络层负责接电话、传消息处理成千上万的并发连接协议层负责 “翻译” 请求把客户端发的命令比如 SET、GET解析成程序能看懂的格式存储引擎层负责真正存数据、取数据支持多种存储方式自由切换复制层负责主从同步避免单点故障保证数据安全。为什么要这么设计因为 “解耦” 太重要了 —— 比如我想把网络层换成更高效的模型或者把存储引擎换成跳表不用改其他层的代码直接替换就行后期维护起来特别方便。这也是工业级项目的常用思路新手可以重点记一下二、网络层三种 IO 模型按需切换性能实测说话网络层是整个项目的 “大门”负责处理客户端的连接和数据传输。我没有只写一种网络模型而是实现了三种最常用、最高效的 IO 模型大家可以根据自己的环境按需切换这也是项目的核心亮点之一。1. epoll Reactor最通用、最稳定的 “老大哥”epoll 应该是大家最熟悉的高并发网络模型了也是生产环境中用得最多的。它的核心逻辑就是 “监听事件、处理事件”—— 先监听所有客户端的连接、读、写请求一旦有事件发生就去处理不用像同步阻塞那样一直等。我在实现的时候加了一个小优化用动态缓冲区DynBuf 读 / 写偏移r_off/w_off。简单说就是不用每次读数据都挪动内存memmove既能解决半包、粘包问题又能提升性能这也是 Redis、Nginx 里的常用优化技巧。比如客户端发数据可能会分好几次发半包或者好几条命令粘在一起粘包动态缓冲区会自动扩容偏移指针会记录已经处理过的数据不用重复处理效率直接拉满。2. io_uring Proactor性能天花板异步 IO 新王者io_uring 是 Linux 5.1 之后推出的新一代异步 IO 模型比 epoll 更高效 —— 它不用程序主动去 “问” 有没有事件而是内核处理完事件后主动通知程序减少了系统调用和上下文切换性能直接上一个台阶。我实测下来同样的配置io_uring 比 epoll 性能高 35% 左右写请求能到 3.7w QPS读请求能到 4.1w QPS适合高并发、低延迟的场景。不过它有个小缺点依赖 Linux 系统兼容性不如 epoll要是在 Windows 或者旧版 Linux 上跑就用不了啦。3. NtyCo 协程同步写法异步性能协程是近几年特别火的编程范式它的核心优势就是 “同步代码异步性能”。不用写复杂的回调函数像写普通同步代码一样就能实现高并发。我用 NtyCo 协程实现了 “一个协程处理一个客户端”逻辑特别简单不用关心事件循环代码可读性拉满。它的性能接近 io_uring单机轻松支持 5w 并发连接适合不想写复杂回调又想追求高并发的场景。三种模型性能对比实测数据不吹不黑网络模型写 QPSc50-P6读 QPSc50-P6特点epoll2800440998通用、稳定、全兼容io_uring3791041196性能最高、依赖 LinuxNtyCo3567441008开发效率高、同步写法总结一下追求兼容性用 epoll追求极致性能用 io_uring追求开发效率用 NtyCo按需选择就好三、协议层吃透 RESP 协议解决半包粘包客户端和服务器之间通信得有一套 “共同语言”不然服务器看不懂客户端发的是什么。我选择了 Redis 的 RESP 协议 —— 它简单、易解析还支持 Pipeline 批处理很多语言的 Redis SDK 都能直接对接我的项目比如 Java 的 Jedis、Python 的 redis-py。1. RESP 协议到底是什么其实很简单就是一种 “约定好格式” 的字符串。比如客户端发一个 SET key value 命令用 RESP 协议表示就是*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n拆解一下*3表示这是一个包含 3 个元素的数组SET、key、value$3表示后面的字符串长度是 3比如 SET\r\n是换行分隔符。这样服务器只要按照这个格式解析就能准确拿到客户端的命令和参数不会出错。2. 核心优化Pipeline 批处理性能提升 37 倍这是我最满意的一个优化点。普通情况下客户端发一条命令服务器处理完再回复来回耗时很长而 Pipeline 批处理就是客户端一次性发多条命令服务器一次性处理、一次性回复大大减少了网络往返的时间。我实测下来没有用 PipelineP1的时候SET 命令只有 244 QPS用了 PipelineP8QPS 直接涨到 9262提升了 37 倍这也证明了我的协议解析和网络层设计是没问题的能真正发挥批处理的优势。四、存储引擎层四种引擎按需切换吃透数据结构存储引擎是 KV 存储的 “心脏”负责数据的存取。我实现了四种常用的存储引擎对外提供统一的接口set、get、del 等不用改上层代码就能自由切换方便大家对比不同数据结构的性能。这里用通俗的比喻跟大家聊聊每种引擎的特点不用记复杂的原理懂场景就好1. 数组引擎最简单的 “抽屉”数组引擎就是用数组来存数据key 和 value 一一对应就像一个个抽屉找数据的时候要从头遍历性能最差O (n)。适用场景只能用于演示比如刚学数据结构用来理解 KV 存储的基本逻辑实际生产中根本用不上。2. 哈希表引擎最快的 “快递柜”哈希表是最常用的 KV 存储结构核心是 “哈希函数”—— 把 key 转换成一个索引直接定位到数据的位置读写性能都是 O (1)就像快递柜输入取件码就能直接找到快递不用遍历。我实现的是链式哈希解决了哈希冲突的问题纯 KV 读写场景下性能是四种引擎里最快的。适用场景纯 KV 快速查询比如缓存热点数据、Session 存储不需要有序查询的场景。3. 红黑树引擎最稳定的 “排序书架”红黑树是一种平衡二叉树能保证数据有序读写性能都是 O (logN)就像一个排序好的书架既能按顺序找书也能快速定位某本书。它的优点是内存占用稳定不会像哈希表那样出现大量冲突缺点是写入性能比跳表略差。适用场景需要有序查询、内存占用稳定的场景比如按时间排序的日志存储。4. 跳表引擎Redis 同款性能最优跳表是 Redis 底层用的存储结构也是我这个项目里性能最优的引擎。它的核心是 “分层索引”就像给书架加了电梯不用一层一层找直接通过索引快速定位读写性能都是 O (logN)而且写入性能比红黑树好。适用场景高并发、需要有序查询、范围查询的场景比如排行榜、范围统计也是我这个项目的默认引擎。四种引擎性能对比通俗版速度跳表 ≈ 哈希表 红黑树 数组有序性红黑树 跳表 数组 哈希表适用场景纯 KV 快速查哈希表有序 / 范围查跳表、红黑树演示学习数组五、持久化RDBAOF 双机制数据安全双重保障KV 存储如果重启就清空数据在实际使用中完全无法落地。因此我在项目中同时实现了 RDB 快照 AOF 日志两种持久化机制兼顾性能与数据安全性完美解决了数据丢失风险。1. RDB 快照全量二进制持久化RDB 是定时全量快照简单来说就是定期把内存中的所有数据以二进制文件的形式写入dump.rdb。实现逻辑主节点可通过手动 / 定时触发将内存数据一次性落盘保存主从全量同步时主节点会发送 RDB 文件给从机从机加载后快速恢复全量数据服务器重启时自动读取 RDB 文件恢复历史数据。它的优点是文件体积小、恢复速度快非常适合主从全量同步与冷启动恢复。2. AOF 日志实时命令持久化为了弥补 RDB 不能实时持久化、断电可能丢失数据的缺点我额外实现了AOF 增量日志持久化。AOF 会实时记录每一条写命令以文本形式追加到appendonly.aof文件中。服务器重启时只需逐行重放 AOF 里的命令就能完整恢复所有数据几乎可以做到数据零丢失。3. 双持久化机制的设计价值项目支持RDB AOF 混合使用日常用 AOF 保证数据实时性定时生成 RDB 用于快速恢复与主从同步兼顾了高性能与高数据可靠性完全达到了生产级轻量级 KV 存储的持久化标准。六、主从复制分布式入门高可用必备做分布式的第一步就是主从复制 —— 一台主节点Master多台从节点Slave主节点负责写数据从节点负责读数据既能分担主节点的压力又能实现高可用主节点挂了从节点可以切换成主节点。我实现的主从复制完全对标 Redis 的逻辑核心是PSYNC 命令支持全量同步和增量同步不用手动干预自动完成。1. 全量同步第一次见面把所有数据都给你当从节点第一次连接主节点的时候会发送 PSYNC ? -1 命令告诉主节点 “我是新节点需要全量数据”。主节点收到命令后会做两件事生成 RDB 快照把内存里的所有数据保存到文件把 RDB 文件发送给从节点从节点加载 RDB 文件完成全量同步主节点在生成 RDB 的过程中会把新的写命令保存到环形缓冲区backlog等从节点加载完 RDB再把这些增量命令补发过去保证数据一致。2. 增量同步断线重连只传新数据如果从节点和主节点断开连接再次重连的时候不会再做全量同步太耗时而是发送 PSYNC 主节点 runid 偏移量告诉主节点 “我上次同步到这个位置把后面的新数据给我就行”。主节点会检查偏移量如果在环形缓冲区的范围内就只把偏移量之后的增量命令发送给从节点快速完成同步如果偏移量超出范围就重新做全量同步。3. 其他细节心跳 ACK 主从切换心跳 ACK从节点会定期给主节点发送 REPLCONF ACK 命令告诉主节点 “我还活着同步到哪个位置了”主节点如果长时间没收到 ACK就会认为从节点断开清理连接主从切换支持手动切换主从角色从节点可以切换成主节点主节点也可以切换成从节点灵活应对节点故障。4.主从同步的角色定位我在设计主从同步时坚持架构解耦与职责分离将主从复制的网络通信与客户端业务网络完全分开。原因很简单原有的网络层核心职责是处理普通客户端的请求如果把从机的同步命令、心跳、复制协议也混杂在一起网络层需要同时识别 “业务请求” 和 “复制请求”会导致代码逻辑臃肿、边界模糊既增加了出错概率也不利于后期维护和性能优化。所以我单独实现了一套主从同步专用网络层它拥有独立的监听端口、独立的处理线程、独立的协议处理逻辑只负责主从之间的复制工作包括 PSYNC 全量 / 增量同步、REPLCONF ACK 心跳、命令补发、连接管理等。这种设计让业务归业务、复制归复制模块职责清晰互不干扰既提升了系统稳定性也让整体架构更符合工业级项目的设计规范。七、性能压测真实数据说话达到生产级水准光说不练假把式我用 redis-benchmark 做了完整的压测测试环境是普通 Linux 虚拟机没有做任何特殊优化所有数据都是真实可复现的。1. 压测基准测试工具redis-benchmark测试地址192.168.30.128:2000统一配置总请求数 50000长压、稳态避免虚高数据2. 核心压测数据汇总网络模型命令并发数-c批处理-P实测 QPS备注epollSET101244.15无批处理单条命令基准epollSET1089262.77批处理最优提升 37 倍epollSET50628004.48高并发最优epollGET50640998.36读性能比写高 46%io_uringSET50637910.54比 epoll 高 35%io_uringGET50641196.38读性能峰值一、核心总表所有有效测试结果统一基准-h 192.168.30.128 -p 2000 -t set -n 50000长压、稳态、无虚高序号 并发数 -c Pipeline -P 总请求数 -n 实测 QPS 数据有效性 备注1 10 1 50000 244.15 无批处理单条命令真实处理能力2 10 4 50000 2606.00 批处理生效性能大幅提升3 10 6 50000 5716.05 批处理持续优化性能稳步提升4 10 8 50000 9262.77 当前最优批处理大小性能峰值5 20 6 50000 13420.38 并发翻倍吞吐量同步提升6 50 6 50000 28004.48 高并发下批处理优势完全释放二、维度一固定并发 c10不同 Pipeline 对 QPS 的影响核心结论你的批处理设计完全生效P 越大稳态 QPS 越高无拥堵、无掉速。Pipeline -P 并发 -c 总请求 -n 实测 QPS 性能提升相对 P11 10 50000 244.15 基准线4 10 50000 2606.00 10.67 倍6 10 50000 5716.05 23.41 倍8 10 50000 9262.77 37.94 倍三、维度二固定 Pipeline P6不同并发数对 QPS 的影响核心结论你的 KV 存储在高并发下批处理吞吐量线性增长无明显瓶颈。并发数 -c Pipeline -P 总请求 -n 实测 QPS 性能提升相对 c1010 6 50000 5716.05 基准线20 6 50000 13420.38 2.35 倍50 6 50000 28004.48 4.90 倍四、补充历史有效数据用于完整性能画像序号 并发数 -c Pipeline -P 总请求数 -n 实测 QPS 备注7 10 16 50000 4112.23 P16 出现拥堵QPS 回落P8 为最优8 100 6 50000 6866.30 并发过高出现排队QPS 回落9 10 6 100000 3074.71 长压 10 万条QPS 回落进入稳态10 10 6 500000 957.05 极限长压彻底压崩无参考意义3. 压测结论通俗版Pipeline 批处理太香了从 P1 到 P8QPS 提升了 37 倍证明我的协议和网络层设计没问题读性能比写性能高 ——GET 能到 4.1w QPSSET 能到 3.7w QPS符合 KV 存储 “读多写少” 的特点高并发下性能线性增长 —— 并发从 10 涨到 50QPS 从 5716 涨到 28004没有出现瓶颈说明锁竞争、网络阻塞这些问题都解决了io_uring 性能最优比 epoll 高 35%适合追求极致性能的场景。总的来说这个项目的性能已经达到了生产级轻量级 KV 存储的水准用来做缓存、配置中心完全够用。八、总结与展望项目收获做这个项目的过程其实就是把零散的知识点串联起来的过程 —— 从一开始只会写简单的哈希表到后来实现三种网络模型、主从复制我对数据结构、计算机网络、操作系统、分布式的理解都深刻了很多。最开心的不是实现了多少功能而是解决问题的过程比如一开始遇到半包粘包问题卡了好几天后来用动态缓冲区 偏移指针解决比如主从同步的时候断线重连总是数据不一致后来吃透了 Redis 的 PSYNC 逻辑才终于搞定。这个项目虽然不是什么高大上的框架但它是一个 “能落地、能测试、能复盘” 的完整项目对于想巩固底层知识的小伙伴来说非常有参考价值。未来优化方向目前这个项目还有很多可以完善的地方后续我也会慢慢优化增加 AOF 日志实现实时持久化解决 RDB 断电丢数据的问题优化多线程把全局锁改成分段锁提升高并发下的写性能实现分片集群支持更多数据存储突破单机内存限制增加 TLS 加密提升协议安全性支持更多生产场景完善监控接入 Prometheus、Grafana实时查看性能指标。最后如果你也想吃透底层知识我建议大家也可以动手做一个类似的项目 —— 不用追求完美从简单的 KV 存储开始慢慢增加功能遇到问题就查资料、看源码不知不觉中你的技术能力就会有质的提升。这个项目的源码我已经整理好了后续会放到 GitHub 上感兴趣的小伙伴可以关注一下一起交流学习、一起优化感谢大家看到这里祝大家都能吃透底层稳步成长

更多文章