Skip to content

从零构建分布式 KV 存储(一):开篇与 CAP 定理

纸上得来终觉浅,绝知此事要躬行。本系列将带你从零实现一个生产级的分布式 KV 存储系统。

为什么写这个系列?

对于没有分布式系统经验的 Golang 开发者来说,分布式系统常常像一个"黑盒":

  • 听说过高大上的 Raft 算法,但不知道怎么用代码实现
  • 知道 CAP 定理的十二字真言,但遇到实际场景不会权衡
  • 读过 etcd、TiKV 的源码,但面对空白的编辑器不知从何下手

这个系列就是为了解决这个问题。

我们将从一个最简单的单机 KV 存储开始,每篇文章增加一个分布式特性,最终构建一个完整的分布式系统。

系列路线图

阶段文章核心产出
基础01 开篇与 CAP 定理理解分布式系统的核心权衡
基础02 单机 KV 存储可运行的 HTTP KV 服务
分布式03 gRPC 节点通信多节点 RPC 调用
分布式04 Raft 共识算法强一致性保证
分布式05 服务发现与注册动态节点管理
进阶06 一致性哈希分片水平扩展能力
进阶07 可观测性Prometheus 监控
收官08 集群部署与测试Docker 本地集群

最终产出:一个支持多节点、强一致、可水平扩展、可观测的分布式 KV 存储系统。

什么是分布式系统?

在深入 CAP 定理之前,我们先明确一下定义。

分布式系统是由一组通过网络通信、协同工作的独立计算机节点组成的系统。对外表现为一个统一的整体。

分布式系统的挑战

与单机系统相比,分布式系统引入了全新的问题:

挑战说明类比
网络延迟节点间通信有不可预测的延迟寄信可能迟到
网络分区节点间的网络可能断开快递员罢工了
节点故障任何节点随时可能宕机员工突然请假
时钟不同步不同节点的系统时间不一致挂钟各走各的
部分失败一个操作可能在部分节点成功,部分失败只给半桌客人上了菜

这些问题在单机系统中不存在(或容易处理),但在分布式系统中是常态。CAP 定理正是对这些挑战的理论抽象。

CAP 定理:分布式系统的第一性原理

在动手写代码之前,我们必须先理解 CAP 定理——它解释了分布式系统的根本权衡。

历史背景

CAP 定理由加州大学伯克利分校的 Eric Brewer (埃里克·布鲁尔) 教授在 2000 年提出,2002 年被 MIT 的 Seth Gilbert (塞思·吉尔伯特) 和 Nancy Lynch (南希·林奇) 正式证明。它成为了分布式系统设计的基石。

定理内容

CAP 定理指出,一个分布式系统最多只能同时满足以下三个中的两个:

特性含义通俗解释
一致性(C)所有节点在同一时间看到相同的数据你更新完数据,任何人都能读到最新值
可用性(A)每个请求都能收到非错误的响应系统永远在线,不会拒绝服务
分区容错(P)系统在网络分区时仍能正常工作节点间网络断开,系统不崩溃

深入理解三个特性

一致性(Consistency)

强一致性要求:写操作完成后,所有后续读操作都必须返回该写入的值

弱一致性/最终一致性则允许短暂的不一致,最终所有节点数据会趋于一致。

可用性(Availability)

可用性要求:每个非故障节点必须在有限时间内响应请求

注意:可用性不要求"返回最新数据",只要求"返回非错误响应"。

分区容错(Partition Tolerance)

网络分区是指节点之间的网络连接完全断开:

分区容错要求:即使发生网络分区,系统仍能继续运行

核心洞察:P 是必选项!

P 是必选项!

在网络环境中,网络分区是必然发生的(交换机故障、网线松动、丢包等)。因此,分布式系统必须在 C 和 A 之间做出权衡:

CP 系统

  • 选择:保证一致性和分区容错,牺牲可用性
  • 行为:网络分区时,系统暂停服务,等待分区恢复
  • 代表:ZooKeeper、etcd、HBase、MongoDB(默认配置)

AP 系统

  • 选择:保证可用性和分区容错,牺牲一致性
  • 行为:网络分区时,系统继续服务,但数据可能短暂不一致
  • 代表:Cassandra、Amazon Dynamo、CouchDB、Riak

一个经典例子

假设有两个节点 A 和 B,存储同一个数据 x(初始值 1)。客户端向 A 写入 x=2,但 A 和 B 之间的网络断了:

系统类型处理方式结果
CP拒绝写入或读取 B,返回错误数据一致,但部分请求失败
AP允许写入 A,继续服务数据不一致,但系统可用

我们怎么选?

本系列实现的 KV 存储将是一个 CP 系统(类似 etcd):

  • 优先保证一致性:强一致比最终一致更容易理解和推理
  • 可用性:单节点故障时,只要多数派(超过半数节点)存活,系统可用
  • 适合场景:配置管理、服务发现、分布式锁

用公式表达:CP = 一致性 + 分区容错 - 部分可用性

整体架构设计

核心模块说明

模块职责技术选型
API 层接收客户端请求,返回响应HTTP + gRPC
Raft 共识层领导者选举、日志复制、保证一致性hashicorp/raft
状态机存储 KV 数据,应用日志条目内存 Map + 持久化
WAL 日志预写日志,保证数据不丢失文件存储(如 BoltDB)
服务发现节点动态注册与发现Consul
监控暴露指标,观察系统状态Prometheus

请求处理流程

环境准备

开始编码前,请确保已安装:

bash
# Go 版本要求
go version  # >= 1.21

# 验证安装
go env

# 创建项目目录
mkdir -p ~/projects/distributed-kv
cd ~/projects/distributed-kv
go mod init github.com/yourname/distributed-kv

# 后续各篇会用到的依赖(可以先安装)
go get google.golang.org/[email protected]
go get google.golang.org/protobuf@latest
go get github.com/hashicorp/[email protected]
go get github.com/hashicorp/[email protected]
go get github.com/hashicorp/consul/[email protected]
go get github.com/prometheus/[email protected]

下篇预告

第二篇我们将正式开始编码,实现一个单机版本的 KV 存储

  • 基于 sync.RWMutex 的内存存储
  • HTTP API(/put/get/delete
  • 简单的文件持久化
  • 单元测试

届时你将拥有一个可运行的服务,为后续添加分布式特性打好基础。

bash
# 第二篇结束时的效果
$ curl -X PUT localhost:8080/put -d '{"key":"name","value":"Alice"}'
{"status":"ok"}

$ curl localhost:8080/get?key=name
{"key":"name","value":"Alice"}

思考题

在等待第二篇的同时,你可以先思考以下问题:

  1. 如果我们要实现一个 AP 系统,在发生网络分区时,应该如何处理写入冲突?
  2. 为什么 etcd 推荐使用奇数个节点(3、5、7)而不是偶数个?
  3. 一致性哈希和传统哈希取模(hash(key) % N)相比,有什么优缺点?

答案会在后续文章或评论区讨论。


本系列所有代码均会在 GitHub 上开源(链接待补充)。

下一篇:从零构建分布式 KV 存储(二):单机 KV 与 HTTP API (待撰写)

最后更新2026/06/11 14:52
如果你觉得这篇文章有帮助,或者想聊聊技术、工作,欢迎通过下面方式联系我:
contact fishfinal