Appearance
分布式系统基础
概念
上一章讲"多核看到的东西可能不一致",本质上是分布式系统的一个小规模版本——没有全局时钟、通信有延迟、消息可能乱序到达。这一章把同一套问题放到网络尺度上重讲一遍,区别只在于"节点"从核变成了机器。
一句话定义:
分布式系统 = 多台机器通过网络协作,对外表现得像一台机器。
问题在于,一旦跨出单机,就永久失去三样东西:
| 单机里有 | 分布式里没有 | 直接后果 |
|---|---|---|
| 共享内存 | 每台机器只有自己的内存 | 数据必须存多份副本,副本之间要同步 |
| 全局时钟 | 各机器时钟有偏差,还可能回拨 | "谁先谁后"不能靠时间戳判断 |
| 可靠通信 | 网络会延迟、丢包、重排、分区 | 无法区分"对方挂了"和"网络只是慢" |
最后一条最要命:单机要么活着要么死了,而分布式系统里永远存在"一部分活着、一部分不知道另一部分活没活"的中间态——这叫部分失败(partial failure)。
这一章的所有机制(quorum、共识、向量时钟、2PC),都是在"失去这三样东西"之后,把秩序一点点重新建起来的过程。
原理
一、CAP:三个都想要,做不到
| 字母 | 含义 | 通俗说法 |
|---|---|---|
| C 一致性 | 所有节点在同一时刻看到同一份数据 | 谁读都是最新值 |
| A 可用性 | 每个请求都能在有限时间内得到非错误响应 | 一定有回话 |
| P 分区容忍 | 网络分区时系统仍能继续运行 | 线断了也照跑 |
网络分区是物理事实,不是你选的选项——光纤会被挖断,机房会断电。所以 P 必选,真正的选择只剩两个:
| 选择 | 分区正在发生时的做法 | 典型系统 |
|---|---|---|
| CP | 宁可拒绝服务,也不给出错的数据 | etcd / ZooKeeper(要选主)、银行账务 |
| AP | 宁可给出可能过期的数据,也要响应 | Cassandra、DNS |
最常见的误读:把 CAP 说成"永远只能三选二"。正确的表述是——CAP 的取舍只在"分区正在发生"的那段时间才生效。网络正常时,C 和 A 可以同时成立,大多数系统平时也正是这么工作的。
二、一致性不是二值的,而是一条谱系
| 级别 | 保证什么 | 代价 |
|---|---|---|
| 线性一致(强一致) | 所有操作看起来在某个全局时刻点上依次发生 | 延迟高;分区时不可用 |
| 顺序一致 | 各节点看到的操作顺序一致,但不要求与真实时间吻合 | 比线性一致弱 |
| 因果一致 | 有因果关系的操作保持顺序,无关的可以乱 | 常用折中 |
| 最终一致 | 停止写入后,所有副本最终会一致 | 中间那段时间可能读到旧值 |
另外常听到的一组对照:
| 传统数据库(ACID) | 大规模系统(BASE) |
|---|---|
| 原子性、一致性、隔离性、持久性 | 基本可用、软状态、最终一致 |
| 强一致,宁可牺牲可用 | 先响应,之后慢慢收敛 |
BASE 不是"放弃正确性",而是"把正确性的时间点往后挪"——挪到"没有新写入之后"。
三、复制:三套常见安排
| 方式 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 主从(leader-follower) | 写只走主,读可以走从 | 结构简单,不会写冲突 | 主挂了要选新主;从可能读到旧值 |
| 多主(multi-leader) | 多个节点都能写 | 写就近、可用性高 | 要处理写冲突(谁赢?) |
| 无主(leaderless) | 客户端直接写多个副本 | 没有单点 | 要靠"过半数"才能避免读到旧值 |
无主复制就是 quorum:N 个副本,写成功 W 个、读成功 R 个才算数,只要
W + R > N
读集合与写集合就必然相交,那个交点上的副本一定带着最新值。这是整个结论的全部推导——两个过半数集合不可能不相交。
| 副本 N | 配置 | W+R | 结论 |
|---|---|---|---|
| N=3 | W=2 R=2 | 4 | 强一致(最常见的一档) |
| N=5 | W=2 R=2 | 4 | 不够(4 ≤ 5),可能读到旧值 |
| N=5 | W=3 R=3 | 6 | 强一致 |
| N=5 | W=1 R=5 | 6 | 强一致,但写几乎不冗余 |
注意 N=5 时 W=R=2 这样的"少数派读、少数派写"是不成立的——写只有 2 个副本收到,读也可能只碰到另外的 2 个,两边不相交。这是最容易记错的一格。
四、共识与多数派:为什么是 N = 2f + 1
共识(consensus)要解决的问题是:一群可能乱说话的节点,怎么对"同一件事"达成一致。Raft 把它拆成三件套:
| 部件 | 管什么 |
|---|---|
| 选主(leader election) | 选出唯一的写入口,超时没心跳就重选 |
| 日志复制(log replication) | 主把日志发给从,多数派确认后算"已提交" |
| 安全性(safety) | 已提交的日志永远不会被覆盖 |
关键在第三件:任何决定都需要多数派同意,于是"两组多数派同时存在"不可能——这才保证了不会有两个主各自提交冲突的内容。
| 节点数 N | 多数派 | 占比 | 最多容忍挂掉 |
|---|---|---|---|
| 3 | 2 | 66.7% | 1 |
| 4 | 3 | 75.0% | 1 |
| 5 | 3 | 60.0% | 2 |
| 7 | 4 | 57.1% | 3 |
奇数 N 最省:容量 f 台挂掉要求 N ≥ 2f+1。偶数 N 的多数派与容错数和 N−1 完全相同——多买一台却没多一分容错,这就是部署奇数节点的理由。
五、分区:数据放哪,扩容时搬多少
| 方案 | 键的归属 | 加一台机器要搬多少 |
|---|---|---|
| 取模哈希 | h(k) mod N | 绝大多数(N=4 → 5 时约 80%) |
| 一致性哈希 | 节点与键都放在同一个环上,键归"顺时针第一个节点" | 只有新节点接管的那段弧(约 1/(N+1),N=4 → 5 时约 20%) |
差 m 倍,这就是一致性哈希存在的全部理由。它的问题是两个:弧长不均(节点少时数据倾斜)和热点——都用虚拟节点(一个物理节点在环上放几百个点)解决。
六、时间与顺序:逻辑时钟
既然"各机器时钟有偏差、还可能回拨",那就别用物理时钟判断先后,改用逻辑时钟:
| 时钟 | 维护什么 | 能回答 | 代价 |
|---|---|---|---|
| Lamport 逻辑时钟 | 每个进程一个计数器;发消息带出去,收到取 max 再 +1 | 给出全序 | 分不清"因果"与"并发" |
| 向量时钟 | 每个进程维护一个长度 N 的向量 | 给出偏序:能判"先于"也能判"并发" | 每个事件要带 N 个计数器 |
向量比较的三条规则(这是考点):
- 逐分量 A ≤ B 且 A ≠ B → A 先于 B(存在因果路径)
- 逐分量 A ≥ B 且 A ≠ B → B 先于 A
- 各有分量大于对方 → 并发,谁先谁后没有意义
- 逐分量完全相等 → 同一个事件
"并发"是一种真实的物理关系,不是一个还没查清的事实——这一点是向量时钟比单机时间戳强的地方。
七、分布式事务:2PC 与它的阻塞
跨两台机器改数据,怎么做成一个事务?两阶段提交(2PC):
| 阶段 | 协调者做什么 | 参与者做什么 |
|---|---|---|
| 第一阶段(准备) | 问"你能提交吗?" | 执行完、锁住资源、回答 yes / no |
| 第二阶段(提交) | 全部 yes 就发 commit,否则发 abort | 按指令提交或回滚,释放锁 |
致命问题在第二阶段:如果协调者在收齐所有 yes 之后崩溃了,参与者既不能提交也不能回滚——因为它们不知道别人是怎么答的。只能拿着锁等,等出新协调者来问。这段时间里相关数据全部不可写。
3PC 加了一轮把"准备"和"预提交"分开,缩短了阻塞窗口,但在网络分区下仍有出错场景。生产上更常用的是另一条路:
| 方案 | 思路 |
|---|---|
| Saga | 把长事务拆成一串本地事务,任何一步失败就依次执行补偿操作 |
| TCC | Try(预留资源)→ Confirm(确认)→ Cancel(撤销),业务层自己实现 |
| 幂等 + 重试 | 承认"至少一次"投递,让重试变安全 |
幂等这一条是基本功:网络不可靠,请求超时后重发是常态,所以每个写操作都要带唯一请求号,服务端重复收到就返回第一次的结果——而不是再扣一次钱。
示例
例 1:扩容一台机器到底要搬多少数据(C)
用 FNV-1a 给每个键算一个 32 位数,比较"取模哈希"和"一致性哈希"在 4 台变 5 台时的迁移率。
#include <stdio.h>
/* FNV-1a 32 位:给每个键一个"看起来随机"的桶号 */
static unsigned int fnv1a(unsigned int x) {
unsigned int h = 2166136261u;
for (int i = 0; i < 4; i++) {
h ^= (x >> (i * 8)) & 0xFFu;
h *= 16777619u;
}
return h;
}
int main(void) {
const unsigned int N = 1000000u;
printf("%-6s %-8s %-12s %-14s %-16s\n",
"m", "m+1", "mod-migrate", "m/(m+1)", "ring-migrate");
for (unsigned int m = 2; m <= 7; m++) {
unsigned int moved = 0;
for (unsigned int k = 0; k < N; k++) {
if (fnv1a(k) % m != fnv1a(k) % (m + 1)) moved++;
}
printf("%-6u %-8u %-12.2f %-14.4f %-16.4f\n",
m, m + 1, 100.0 * moved / N, 100.0 * m / (m + 1), 100.0 / (m + 1));
}
return 0;
}
c 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
m m+1 mod-migrate m/(m+1) ring-migrate
2 3 66.67 66.6667 33.3333
3 4 75.00 75.0000 25.0000
4 5 80.00 80.0000 20.0000
5 6 83.57 83.3333 16.6667
6 7 85.72 85.7143 14.2857
7 8 87.50 87.5000 12.5000读数:取模哈希的迁移率是 m/(m+1)(4 → 5 时 80%),一致性哈希只有 1/(m+1)(20%),差 m 倍。实测与理论一致到小数点后一位(个别行有 0.2 个百分点的采样偏差,因为只有 100 万个键)。
代价对比:100 万条数据、4 台机器变 5 台,取模方案要搬 80 万条,一致性哈希只要搬 20 万条。扩容成本从"几乎全量"降到"一台的量",这就是为什么分布式存储都长着环。
例 2:可用性、quorum、一致性哈希、向量时钟四笔账(Python)
C 段只算了迁移率。这里补上四件更基础的事:串联与并联的可用性、quorum 组合的枚举、多数派与容错、以及向量时钟的比较规则。
import hashlib
def pad(s, w):
dw = sum(2 if ord(c) > 0x2000 else 1 for c in str(s))
return str(s) + " " * max(0, w - dw)
def table(head, rows, gap=2):
data = [[str(c) for c in r] for r in rows]
w = [max([sum(2 if ord(c) > 0x2000 else 1 for c in str(head[i]))]
+ [sum(2 if ord(c) > 0x2000 else 1 for c in r[i]) for r in data]) + gap
for i in range(len(head))]
print(" " + "".join(pad(head[i], w[i]) for i in range(len(head))))
for r in data:
print(" " + "".join(pad(r[i], w[i]) for i in range(len(head))))
print("=== 1. 串联的代价:单机越可靠,串起来越不可靠 ===")
A = 0.999
rows = []
for n in (1, 2, 3, 5, 10):
a = A ** n
rows.append(["%d 台" % n, "%.6f" % a, "%.2f%%" % (a * 100),
"%.2f 小时" % ((1 - a) * 8760)])
table(["规模", "可用性", "折算", "一年停机"], rows)
print(" 单机 99.9%%(年停机 8.76 小时);10 台串联掉到 %.4f%%,年停机 %.2f 小时 —— 差 %.1f 倍。"
% ((A ** 10) * 100, (1 - A ** 10) * 8760, (1 - A ** 10) / (1 - A)))
print(" 只有做'冗余并联'(任何一台活着就行)才能把可用性抬上去:1-(1-A)^n。")
for n in (2, 3):
print(" %d 台并联 -> %.7f%%(年停机 %.5f 小时)"
% (n, (1 - (1 - A) ** n) * 100, (1 - (1 - (1 - A) ** n)) * 8760))
print()
print("=== 2. quorum:N 个副本,W 个写成功、R 个读成功才算数 ===")
rows = []
for N in (3, 5, 7):
maj = N // 2 + 1
pair = (maj, N - 1) if maj == 2 else (maj, maj)
for W, R in dict.fromkeys(((1, N), (2, 2), pair, (N, 1))):
rows.append(["N=%d" % N, "W=%d R=%d" % (W, R), "%d" % (W + R),
"强一致" if W + R > N else "可能读到旧值"])
table(["副本", "配置", "W+R", "结论(W+R>N ?)"], rows)
for N in (3, 5, 7):
good = sum(1 for W in range(1, N + 1) for R in range(1, N + 1) if W + R > N)
print(" N=%d:%d 种 (W,R) 组合里 %d 种满足 W+R>N,占 %.1f%%;多数派 = %d。"
% (N, N * N, good, 100.0 * good / (N * N), N // 2 + 1))
print(" 最省的一档:W = R = 多数派 = %d(N=3)—— 写要过半、读也要过半,两次过半必然有交集。"
% (3 // 2 + 1))
print()
print("=== 3. 多数派与容错:N = 2f+1 才敢容 f 个节点挂掉 ===")
rows = []
for N in (1, 2, 3, 4, 5, 7):
maj = N // 2 + 1
f = (N - 1) // 2
rows.append(["%d" % N, "%d" % maj, "%.1f%%" % (100.0 * maj / N), "%d" % f])
table(["节点数 N", "多数派", "占比", "最多容忍挂掉"], rows)
print(" N=2f+1 是唯一'多数派还能选出来'的形状:偶数 N 的多数派与容错数和 N-1 相同,白白多一台。")
print()
print("=== 4. 加一台机器,要搬多少数据?取模 vs 一致性哈希 ===")
KEYS = 200000
def h(key):
return int(hashlib.md5(str(key).encode()).hexdigest()[:8], 16)
mod4 = [h(k) % 4 for k in range(KEYS)]
mod5 = [h(k) % 5 for k in range(KEYS)]
moved_mod = sum(1 for i in range(KEYS) if mod4[i] != mod5[i])
def ring_pos(name, vnodes=150):
out = []
for v in range(vnodes):
out.append((h("%s#%d" % (name, v)) / float(1 << 32), name, v))
return out
def owner(ring, key):
p = h(key) / float(1 << 32)
for pos, name, _ in sorted(ring):
if pos >= p:
return name
return sorted(ring)[0][1]
r4 = ring_pos("S1") + ring_pos("S2") + ring_pos("S3") + ring_pos("S4")
r5 = r4 + ring_pos("S5")
o4 = [owner(r4, k) for k in range(KEYS)]
o5 = [owner(r5, k) for k in range(KEYS)]
moved_ring = sum(1 for i in range(KEYS) if o4[i] != o5[i])
table(["方案", "4 台时的归属", "5 台时的归属", "需要迁移的键"],
[["取模 hash mod N", "h(k) mod 4", "h(k) mod 5", "%d / %d = %.1f%%"
% (moved_mod, KEYS, 100.0 * moved_mod / KEYS)],
["一致性哈希", "环上顺时针首个节点", "环上顺时针首个节点", "%d / %d = %.1f%%"
% (moved_ring, KEYS, 100.0 * moved_ring / KEYS)]])
print(" 取模哈希几乎全搬(%.1f%%),一致性哈希只搬走新节点'接管的那一段弧'(%.1f%%,理论值 1/5 = 20%%)。"
% (100.0 * moved_mod / KEYS, 100.0 * moved_ring / KEYS))
print(" 差 %.1f 倍 —— 这就是一致性哈希存在的唯一理由。"
% ((moved_mod / KEYS) / (moved_ring / KEYS)))
print()
print("=== 5. 向量时钟:怎么判断'A 先于 B'还是'两者并发' ===")
rows = [
["A = (2, 0, 0)", "B = (3, 1, 0)", "逐分量 A ≤ B 且 A ≠ B", "A 先于 B(因果)"],
["A = (2, 1, 0)", "B = (1, 3, 0)", "各有分量大于对方", "并发(谁先谁后无所谓)"],
["A = (3, 2, 1)", "B = (1, 0, 0)", "逐分量 A ≥ B 且 A ≠ B", "B 先于 A"],
["A = (2, 1, 0)", "B = (2, 1, 0)", "逐分量相等", "同一个事件"],
]
table(["事件 A", "事件 B", "向量比较", "结论"], rows)
print(" 单机时间戳只能给'全序',向量时钟给的是'偏序':并发关系被显式表达出来了。")
print(" 3 个进程时,每个事件要带 3 个计数器 —— 精度是拿空间换的。")
python 本站为静态站,不提供在线运行;可复制到本地用 gcc / python 执行
预期输出:
=== 1. 串联的代价:单机越可靠,串起来越不可靠 ===
规模 可用性 折算 一年停机
1 台 0.999000 99.90% 8.76 小时
2 台 0.998001 99.80% 17.51 小时
3 台 0.997003 99.70% 26.25 小时
5 台 0.995010 99.50% 43.71 小时
10 台 0.990045 99.00% 87.21 小时
单机 99.9%(年停机 8.76 小时);10 台串联掉到 99.0045%,年停机 87.21 小时 —— 差 10.0 倍。
只有做'冗余并联'(任何一台活着就行)才能把可用性抬上去:1-(1-A)^n。
2 台并联 -> 99.9999000%(年停机 0.00876 小时)
3 台并联 -> 99.9999999%(年停机 0.00001 小时)
=== 2. quorum:N 个副本,W 个写成功、R 个读成功才算数 ===
副本 配置 W+R 结论(W+R>N ?)
N=3 W=1 R=3 4 强一致
N=3 W=2 R=2 4 强一致
N=3 W=3 R=1 4 强一致
N=5 W=1 R=5 6 强一致
N=5 W=2 R=2 4 可能读到旧值
N=5 W=3 R=3 6 强一致
N=5 W=5 R=1 6 强一致
N=7 W=1 R=7 8 强一致
N=7 W=2 R=2 4 可能读到旧值
N=7 W=4 R=4 8 强一致
N=7 W=7 R=1 8 强一致
N=3:9 种 (W,R) 组合里 6 种满足 W+R>N,占 66.7%;多数派 = 2。
N=5:25 种 (W,R) 组合里 15 种满足 W+R>N,占 60.0%;多数派 = 3。
N=7:49 种 (W,R) 组合里 28 种满足 W+R>N,占 57.1%;多数派 = 4。
最省的一档:W = R = 多数派 = 2(N=3)—— 写要过半、读也要过半,两次过半必然有交集。
=== 3. 多数派与容错:N = 2f+1 才敢容 f 个节点挂掉 ===
节点数 N 多数派 占比 最多容忍挂掉
1 1 100.0% 0
2 2 100.0% 0
3 2 66.7% 1
4 3 75.0% 1
5 3 60.0% 2
7 4 57.1% 3
N=2f+1 是唯一'多数派还能选出来'的形状:偶数 N 的多数派与容错数和 N-1 相同,白白多一台。
=== 4. 加一台机器,要搬多少数据?取模 vs 一致性哈希 ===
方案 4 台时的归属 5 台时的归属 需要迁移的键
取模 hash mod N h(k) mod 4 h(k) mod 5 159709 / 200000 = 79.9%
一致性哈希 环上顺时针首个节点 环上顺时针首个节点 38132 / 200000 = 19.1%
取模哈希几乎全搬(79.9%),一致性哈希只搬走新节点'接管的那一段弧'(19.1%,理论值 1/5 = 20%)。
差 4.2 倍 —— 这就是一致性哈希存在的唯一理由。
=== 5. 向量时钟:怎么判断'A 先于 B'还是'两者并发' ===
事件 A 事件 B 向量比较 结论
A = (2, 0, 0) B = (3, 1, 0) 逐分量 A ≤ B 且 A ≠ B A 先于 B(因果)
A = (2, 1, 0) B = (1, 3, 0) 各有分量大于对方 并发(谁先谁后无所谓)
A = (3, 2, 1) B = (1, 0, 0) 逐分量 A ≥ B 且 A ≠ B B 先于 A
A = (2, 1, 0) B = (2, 1, 0) 逐分量相等 同一个事件
单机时间戳只能给'全序',向量时钟给的是'偏序':并发关系被显式表达出来了。
3 个进程时,每个事件要带 3 个计数器 —— 精度是拿空间换的。四条结论:
- 冗余必须"并联"才有用:单机 99.9% 串 10 台掉到 99.00%(年停机 87.21 小时);两台并联反而升到 99.9999%。串得越多越不可靠,是因为任何一环断了整条链就断——所以"加机器"和"加冗余"是两件相反的事。
- quorum 的门槛是"过半数相交":N=3 时 W=R=2 成立(4 > 3),N=5 时 W=R=2 不成立(4 ≤ 5)。看的是 W+R 与 N 的大小,不是"W 够不够大"。
- 一致性哈希把扩容成本从 79.9% 降到 19.1%,差 4.2 倍(理论值正好是机器数 m=4 倍)。
- 向量时钟给出的是偏序:四行里有两行判出"先后",一行判出"并发",一行判出"同一个事件"。"并发"是一个明确答案,不是"暂时还不知道"。
考点
考点
1. CAP 的正确说法
- P 必选(网络分区是物理事实),真正的取舍是 CP 还是 AP。
- 取舍只在分区期间生效:网络正常时 C 与 A 可以同时成立。凡是说"CAP 永远三选二"的都是误读。
- 典型对照:CP = etcd / ZooKeeper(宁可拒绝),AP = Cassandra / DNS(宁可给旧值)。
2. quorum 与多数派
- 判据是 W + R > N,读集合与写集合必然相交。
- N=3:W=R=2 成立;N=5:W=R=2 不成立(要 3+3 或 2+4)。
- 容错要求 N ≥ 2f+1:3 台容 1、5 台容 2、7 台容 3;偶数 N 白多一台。
- 常见搭配:读多写少 → W 小 R 大,写多读少则反过来。
3. 一致性谱系
线性一致 → 顺序一致 → 因果一致 → 最终一致,从强到弱、从慢到快。ACID 对应强一致,BASE 对应最终一致,BASE 不是放弃正确性,而是把正确性的时点推后。
4. 一致性哈希
- 键与节点共用一个环,键归顺时针第一个节点;加/删节点只搬"相邻一段弧",迁移率 1/(m+1)。
- 取模哈希的迁移率是 m/(m+1),两者相差 m 倍。
- 虚拟节点解决弧长不均与热点。
5. 向量时钟的比较规则(最高频)
| 情形 | 结论 |
|---|---|
| 逐分量 A ≤ B 且 A ≠ B | A 先于 B |
| 逐分量 A ≥ B 且 A ≠ B | B 先于 A |
| 各有分量大于对方 | 并发 |
| 逐分量相等 | 同一个事件 |
注意"有等于、有小于"仍然算 A ≤ B——判据是"存在一个分量严格小于",而不是"所有分量都小于"。
6. 2PC 的缺点与替代
- 两阶段:准备(投票 + 加锁)→ 提交/回滚。
- 致命缺陷是阻塞:协调者在收齐 yes 后崩溃,参与者持有锁且无法决定,只能等。
- 替代路线:3PC(缩短窗口)、Saga(本地事务 + 补偿)、TCC(Try/Confirm/Cancel)、幂等 + 重试。
7. 易错点清单
- 把"可用性 99.9%"当成"三台机器就有 99.7% 可用性":串联系数是相乘,A 的 n 次幂只会更低。
- 认为"加副本就等于加可用性":不解决写冲突与选主,副本只是多了一份可能不一致的数据。
- 用物理时钟判断分布式事件的先后:时钟偏差与回拨会让结论翻转。
- 以为"最终一致"是"随时会一致":它只保证"停止写入后最终一致",中间没有时间上界。
- 把"分区容忍"当成可以放弃的选项:放弃 P 等于假设网络永不故障。
- 用"至少一次"投递却不做幂等:重试就会重复扣款。
小结
- 分布式 = 失去三样东西(共享内存、全局时钟、可靠通信)之后的补救:副本解决共享内存、逻辑时钟解决全局时钟、quorum 与共识解决不可靠通信。
- CAP 的真身是"分区期间的 CP 或 AP",不是"永远三选二"。
- 一切"过半"机制都源于同一个事实:两个过半数集合必然相交——quorum(W+R>N)与共识(N=2f+1)都是它的变形。
- 一致性哈希把扩容成本从"几乎全量"降到"1/(m+1)",代价是要处理数据倾斜。
- 物理时钟不可靠,所以顺序要自己造:Lamport 给全序、向量时钟给偏序,"并发"是它们能明确说出口的结论。
- 2PC 的问题不是"慢"而是"会阻塞在某些中间态",所以工程上多用 Saga / TCC / 幂等重试。
回到主线:这一章是"多核一致性"(内存一致性模型)的网络版,也是操作系统里那些同步机制在"机器之间"的重新发明——os 的进程同步 管的是同一台机器上两个线程抢一个变量,这一章管的是两台机器抢一个键。而"网络为什么会延迟、丢包、分区",答案在 计网的物理层 与 TCP 的可靠传输。soft 分支到此收口。
下一篇:密码学基础
评论(0)
当前浏览器不允许本地存储,评论无法保存。
还没有评论,来说两句。