6Database Internals

一致性模型

一致性与可用性

线性一致性最终一致性CAP定理PACELC

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

第6章:一致性模型(Consistency Models)

一、导读

1.1 本章学习目标

第6章深入讲解分布式系统中的一致性模型。一致性模型定义了客户端在读写数据时能获得的保证——从最弱的"最终一致性"到最强的"线性一致性",不同的一致性级别对应不同的性能和可用性特征。

本章核心学习目标:

  • 理解线性一致性(Linearizability):最强的 consistency 保证
  • 掌握顺序一致性(Sequential Consistency):与线性一致性的区别
  • 理解因果一致性(Causal Consistency):如何捕捉事件之间的因果关系
  • 掌握最终一致性(Eventual Consistency):各种弱一致性模型的统称
  • 理解向量时钟(Vector Clock):如何检测并发写入和因果序

1.2 为什么本章如此重要

一致性模型是分布式系统设计的核心决策。选择太强的一致性会导致延迟高、可用性差;选择太弱的一致性会导致业务逻辑复杂(需要处理冲突)。理解各种一致性模型的精确含义,是做出正确设计决策的前提。

1.3 核心问题引导

线性一致性和顺序一致性的关键区别是什么?

为什么向量时钟能检测并发但无法确定因果序?

最终一致性有哪些具体的变体?它们各提供什么保证?

为什么"读己之所写"看起来简单但实现起来并不 trivial?


二、核心概念详解

2.1 线性一致性(Linearizability)

线性一致性是最强的一致性保证:每个操作都像是在某个全局时间点原子地执行的,所有节点在同一时刻看到相同的值。

线性一致性:
Client A: write(X=1) ──── 成功 ────→
                              ↓ 全局时间点 t
Client B:          read(X) ── 返回 1 ──→

所有节点在 t 之后都能读到 X=1
操作的效果看起来是在 t 时刻瞬间完成的

实现代价:
- 需要协调多数节点(如 Paxos/Raft)
- 延迟至少为一次网络往返
- 分区时可能不可用

2.2 顺序一致性(Sequential Consistency)

顺序一致性保证:所有操作以某种全局顺序执行,每个处理器的操作按程序顺序出现。

顺序一致性 vs 线性一致性:

顺序一致性: 操作有全局顺序,但顺序不一定与实时时间一致
线性一致性: 全局顺序必须与实时时间一致

示例:
Client A: write(X=1) at time 100
Client B: write(X=2) at time 200

顺序一致性允许: 全局顺序为 X=2, X=1 (只要所有节点看到相同顺序)
线性一致性不允许: 因为 X=2 的写入时间晚于 X=1,全局顺序必须是 X=1, X=2

2.3 因果一致性(Causal Consistency)

因果一致性捕捉事件之间的因果关系:有因果关系的事件必须按因果顺序被所有节点看到,没有因果关系的事件(并发事件)可以以任意顺序被看到。

因果关系:
A: write(X=1)
    ↓ (因果依赖)
B: read(X=1), write(Y=2)
    ↓ (因果依赖)
C: read(Y=2)

因果一致性保证: 如果 A 发生在 B 之前,所有节点都先看到 A 的效果再看到 B 的效果

并发事件:
A: write(X=1)  ─┐
                 ├→ 这两个事件是并发的,没有因果关系
B: write(Y=2)  ─┘
因果一致性允许: 不同节点可能以不同顺序看到 X=1 和 Y=2

2.4 向量时钟(Vector Clock)

向量时钟是一种检测事件因果关系的数据结构:

向量时钟: [N_A, N_B, N_C]  (每个节点一个计数器)

事件发生时: 本地计数器 +1
收到消息时: 每个分量取 max,然后本地计数器 +1

示例:
A: [1,0,0] → 发送消息 → B
B: 收到后 [1,1,0] → 发送消息 → C
C: 收到后 [1,1,1]

比较:
[1,0,0] < [1,1,0] → 因果关系 (A 发生在 B 之前)
[1,0,0] vs [0,1,0] → 并发 (无法比较)

2.5 最终一致性的变体

最终一致性是一个宽泛的概念,有多种具体变体:

读己之所写 (Read Your Writes):
- 客户端写入后,后续读取一定能看到自己的写入
- 实现: 客户端携带版本号,读时等待版本 ≥ 写入版本

单调读 (Monotonic Reads):
- 客户端一旦读到某个值,后续读取不会读到更旧的值
- 实现: 将客户端绑定到特定节点,或携带版本号

单调写 (Monotonic Writes):
- 一个客户端的写入不会在后续写入之后被覆盖
- 实现: 将同一客户端的写入路由到同一节点

一致前缀读 (Consistent Prefix Reads):
- 读取结果一定是某个写入序列的前缀
- 实现: 保证写入按顺序传播

三、实战案例

3.1 案例一:向量时钟的实现

python
class VectorClock:
    def __init__(self, nodes):
        self.clock = {node: 0 for node in nodes}

    def increment(self, node):
        self.clock[node] += 1

    def merge(self, other):
        for node in self.clock:
            self.clock[node] = max(self.clock[node], other.clock[node])

    def compare(self, other):
        """返回: 'happened_before', 'happened_after', 'concurrent', 'equal'"""
        le = ge = False
        for node in self.clock:
            if self.clock[node] < other.clock[node]:
                le = True
            elif self.clock[node] > other.clock[node]:
                ge = True
        if not le and not ge:
            return 'equal'
        elif le and not ge:
            return 'happened_before'
        elif ge and not le:
            return 'happened_after'
        else:
            return 'concurrent'

    def __repr__(self):
        return str(self.clock)

# 使用示例
vc_a = VectorClock(['A', 'B', 'C'])
vc_a.increment('A')  # A 的事件: [1,0,0]

vc_b = VectorClock(['A', 'B', 'C'])
vc_b.merge(vc_a)
vc_b.increment('B')  # B 收到 A 的消息: [1,1,0]

print(vc_a.compare(vc_b))  # 'happened_before'

vc_c = VectorClock(['A', 'B', 'C'])
vc_c.increment('C')  # C 独立事件: [0,0,1]
print(vc_a.compare(vc_c))  # 'concurrent'

四、本章小结

线性一致性是最强的一致性,保证操作按实时顺序原子执行

因果一致性只保证有因果关系的事件按序,允许并发事件以任意顺序出现

向量时钟通过每个节点维护一个计数器向量来检测因果关系

最终一致性有多种变体,提供不同程度的读写保证


五、思考与练习

思考题

为什么线性一致性在分区时不可用?这与 CAP 定理有什么关系?

向量时钟的"并发"检测结果在业务上意味着什么?如何处理?

"读己之所写"在移动端(网络不稳定)场景下如何实现?

编程练习

实现一个基于向量时钟的 KV 存储,支持因果一致性读写。

模拟网络分区场景,验证线性一致性和最终一致性的行为差异。


6/7一致性模
0%