5Database Internals

分布式数据

分布式基础

数据分片复制策略一致性哈希数据放置

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章:分布式数据(Distributed Data)

一、导读

1.1 本章学习目标

第5章从单机存储引擎扩展到分布式数据系统。当数据量或访问量超过单机能力时,必须将数据分布到多个节点上。这引入了数据分片、复制策略、一致性哈希等核心问题。

本章核心学习目标:

  • 掌握数据分片的策略:哈希分片、范围分片、一致性哈希的优劣
  • 理解复制策略:单主、多主、无主复制的权衡
  • 掌握 CAP 定理的含义:为什么不能同时满足一致性、可用性和分区容忍?
  • 理解 PACELC 扩展:在没有分区时,延迟和一致性如何权衡?
  • 掌握数据放置策略:如何将数据分配到节点以平衡负载?

1.2 为什么本章如此重要

几乎所有现代数据库系统都是分布式的——从 MySQL 的主从复制,到 Cassandra 的无主复制,再到 TiDB 的分布式事务。理解分布式数据的基本原理,是设计和运维大规模系统的基础。

1.3 核心问题引导

一致性哈希如何解决数据分片中的负载不均衡问题?

CAP 定理中的"一致性"具体指什么?与 ACID 中的一致性有何不同?

为什么 Cassandra 选择最终一致性而不是强一致性?

数据分片的再平衡(Rebalancing)为什么是一个棘手的问题?


二、核心概念详解

2.1 数据分片(Sharding/Partitioning)

2.1.1 范围分片(Range Partitioning)

按键值的范围将数据分配到不同节点:

节点 0: [A, D)   → 键值以 A-C 开头的数据
节点 1: [D, H)   → 键值以 D-G 开头的数据
节点 2: [H, Z]   → 键值以 H-Z 开头的数据

优势: 范围查询高效(只需访问一个节点)
劣势: 数据倾斜(某些范围的数据量远大于其他范围)
代表: HBase, MongoDB (默认)

2.1.2 哈希分片(Hash Partitioning)

对键值取哈希后分配到节点:

shard = hash(key) % num_shards

key="user:1001" → hash=0x3a7f → 0x3a7f % 4 = 3 → 节点 3
key="user:1002" → hash=0x8b2c → 0x8b2c % 4 = 0 → 节点 0

优势: 数据分布均匀
劣势: 范围查询需要广播到所有节点
代表: Cassandra, Voldemort

2.1.3 一致性哈希(Consistent Hashing)

一致性哈希解决了普通哈希分片在节点增减时大量数据迁移的问题:

哈希环 (0 到 2^32-1):

       节点A (hash=1000)
         /
        /
节点D  /   节点B (hash=8000)
(15000) \ /
         \
          \
       节点C (hash=22000)

数据分配:
key1 → hash=3000  → 顺时针找到节点B
key2 → hash=12000 → 顺时针找到节点C
key3 → hash=25000 → 顺时针找到节点A

新增节点E (hash=5000):
只需要将 A→B 之间的数据从 B 迁移到 E
其他节点的数据不受影响

虚拟节点:
每个物理节点对应多个虚拟节点(如 100-200 个)
解决数据倾斜问题,使分布更均匀

2.2 复制策略

2.2.1 单主复制(Single-Leader)

写入流程:
Client → Leader → Follower 1
                  → Follower 2

同步复制: Leader 等待所有 Follower 确认 → 强一致,延迟高
异步复制: Leader 不等待 Follower → 低延迟,可能丢数据
半同步: Leader 等待至少一个 Follower → 折中方案

代表: MySQL 主从复制, PostgreSQL Streaming Replication

2.2.2 多主复制(Multi-Leader)

写入流程:
Client → Leader A → Leader B
Client → Leader B → Leader A

冲突解决:
- 最后写入胜出 (LWW): 用时间戳解决冲突
- 合并: 自动合并不同节点的修改
- 手动: 交给用户决定

代表: GitHub (git), CouchDB, Baidu DTS

2.2.3 无主复制(Leaderless)

写入流程:
Client → 同时发送到 N 个节点
读取流程:
Client → 同时从 N 个节点读取
         → 取最新版本(通过版本号或时间戳)

Quorum 机制:
写入: W 个节点确认
读取: R 个节点确认
条件: W + R > N → 保证读写重叠,一定能读到最新数据

典型配置: N=3, W=2, R=2
代表: Cassandra, DynamoDB, Riak

2.3 CAP 定理

CAP 定理指出,分布式系统不可能同时满足以下三个性质:

  • C(Consistency):所有节点在同一时刻看到相同的数据
  • A(Availability):每个请求都能得到非错误的响应
  • P(Partition Tolerance):即使网络分区,系统仍能运行
网络分区时:
节点A ←──×──→ 节点B

选择 C: 拒绝部分请求,保证一致性 → CP 系统
选择 A: 继续接受请求,但可能不一致 → AP 系统

注意: P 不是可选项——网络分区一定会发生
真正的选择是: 分区发生时,选 C 还是 A?

CP 系统: ZooKeeper, HBase, Redis Cluster
AP 系统: Cassandra, DynamoDB, CouchDB

2.4 PACELC 扩展

PACELC 是对 CAP 的扩展,考虑了没有分区时的情况:

  • Partition: 分区发生时,选 A 还是 C?
  • Else: 没有分区时,选 Latency 还是 Consistency?
PACELC 分类:
CP/EC: 分区时选一致性,正常时选一致性 (如 ZooKeeper)
CP/EL: 分区时选一致性,正常时选延迟 (如 Bigtable)
AP/EL: 分区时选可用性,正常时选延迟 (如 Cassandra, DynamoDB)
AP/AL: 分区时选可用性,正常时也选延迟 (如 PNUTS)

三、实战案例

3.1 案例一:一致性哈希的实现

python
import hashlib
from sortedcontainers import SortedDict

class ConsistentHash:
    def __init__(self, replicas=100):
        self.replicas = replicas
        self.ring = SortedDict()
        self.nodes = {}

    def _hash(self, key):
        return int(hashlib.md5(key.encode()).hexdigest(), 16)

    def add_node(self, node):
        self.nodes[node] = True
        for i in range(self.replicas):
            vnode_key = f"{node}:vnode{i}"
            vnode_hash = self._hash(vnode_key)
            self.ring[vnode_hash] = node

    def remove_node(self, node):
        self.nodes.pop(node, None)
        for i in range(self.replicas):
            vnode_key = f"{node}:vnode{i}"
            vnode_hash = self._hash(vnode_key)
            self.ring.pop(vnode_hash, None)

    def get_node(self, key):
        if not self.ring:
            return None
        h = self._hash(key)
        idx = self.ring.bisect_left(h)
        if idx == len(self.ring):
            idx = 0
        return self.ring.iloc[idx]

# 测试
ch = ConsistentHash(replicas=150)
for node in ["node_a", "node_b", "node_c"]:
    ch.add_node(node)

# 分布测试
from collections import Counter
distribution = Counter()
for i in range(10000):
    node = ch.get_node(f"key_{i}")
    distribution[node] += 1

for node, count in sorted(distribution.items()):
    print(f"{node}: {count} keys ({count/100:.1f}%)")

# 输出示例 (虚拟节点使分布更均匀):
# node_a: 3412 keys (34.1%)
# node_b: 3298 keys (33.0%)
# node_c: 3290 keys (32.9%)

四、本章小结

数据分片将数据分布到多个节点,范围分片适合范围查询,哈希分片保证均匀分布

一致性哈希通过虚拟节点解决数据倾斜,并在节点增减时最小化数据迁移

复制策略分为单主、多主、无主三种模式,各有适用场景

CAP 定理表明分区时必须在一一致性和可用性之间选择;PACELC 进一步考虑了正常情况下的延迟与一致性权衡


五、思考与练习

思考题

一致性哈希中,虚拟节点的数量对系统有什么影响?太少或太多各有什么问题?

为什么 Cassandra 选择 AP 而不是 CP?这对其应用场景意味着什么?

在多主复制中,如何处理跨节点的写冲突?

编程练习

实现一致性哈希,测试不同虚拟节点数量下的数据分布均匀度。

模拟 Quorum 读写:实现 N=5, W=3, R=3 的读写逻辑,验证读写重叠保证一致性。


5/7分布式数
0%