Skip to content

Commit fa4d49e

Browse files
committed
Add: linearizable
1 parent c90cdec commit fa4d49e

8 files changed

+175
-0
lines changed

_data/refs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ universal:
4242
- "post-paxoskv": https://blog.openacid.com/algo/paxoskv/ "200行代码实现基于paxos的kv存储"
4343
- "post-quorum": https://blog.openacid.com/algo/quorum/ "多数派读写的少数派实现"
4444
- "post-raft-bug": https://blog.openacid.com/algo/raft-bug/ "TiDB 在 Raft 成员变更上踩的坑"
45+
- "post-mmp3": https://blog.openacid.com/algo/mmp3/ "Multi-Master-Paxos 3"
46+
- "post-apaxos": https://blog.openacid.com/algo/abstract-paxos/ "Abstract-Paxos 统一 Paxos 和 Raft"
4547

4648

4749
zhihu:

_posts/2024-04-02-linearizable.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: "分布式系统中的Linearizable事务:时间、通信与一致性"
3+
authors:
4+
- xp
5+
categories:
6+
- algo
7+
tags:
8+
- distributed
9+
- 分布式
10+
11+
refs:
12+
- x: y
13+
14+
disabled_article:
15+
image: /post-res/linearizable/linearizable-banner-big.png
16+
17+
mathjax: false
18+
toc: true
19+
toc_label: 本文目录
20+
toc_sticky: true
21+
excerpt: "分布式系统中,Linearizable事务的实现需要解决事务间先后顺序的判断问题,本文深入探讨了这一难题,分析了其中的时间一致性挑战,并提出了几种解决方案和设计思路。"
22+
---
23+
24+
![](/post-res/linearizable/ed2084df235602ac-linearizable-banner-47x20.jpg)
25+
26+
**Linearizable 事务的定义**: 对一个分布式系统S, 如果 txn2 在 txn1 commit之后发起, 那么 txn2 一定能看到 txn1 提交的数据.
27+
28+
那么, **怎么知道 txn2 是不是在 txn1 之后呢**?
29+
30+
例如可以这么做: 在 txn1 commit后看一眼表得到时间 t1 , 在 txn2 开始前看一眼表得到时间 t2 , 如果 `t2 > t1` , 那么就说 txn2 是在 txn1 之后.
31+
32+
那么, **怎么保证他俩看的这两块表的时间一致呢**?
33+
34+
相对论告诉我们对不同的观察者, `t2 > t1``t1 > t2` 可能在不同的参考系里分别被观察到.
35+
因此看2块表决定先后的方法, 理论上在我们这个宇宙中就是无法实现的.
36+
37+
![](/post-res/linearizable/22d5429bc09f05e3-linearizable-relativity.excalidraw.png)
38+
39+
因此, **要确定先后, txn1 和 txn2 就必须看同一块表**.
40+
41+
也就是说, **txn1 和 txn2 必须有一次通讯**(直接通讯, 或通过第三方, 即同一块表), 才能确认彼此先后.
42+
43+
也就是说, 要真正达成 Linearizable, txn1 commit 后必须有个 **写表** 的操作记录自己
44+
commit 的 **时刻**, txn2 开始前必须有个 **读表** 的操作, 才能正确区分先后,
45+
从而达成 Linearizable. 有些分布式系统本身就可以做表(虚拟时间), 例如 Paxos 的 `ballot` 就是表,
46+
Raft 的 `(term, log_index)` 也是表.
47+
48+
当然, 你可以说我不用看表, 我的代码写的就是完成 txn1 后, 才发起的 txn2;
49+
在这个情况下, 实际上是 **让 txn1 和 txn2 直接通讯了**, 这个情况下要达成
50+
Linearizable, 应该直接把 t1 当做 txn2 的一部分提交到系统S, 告诉系统S, txn2 要在
51+
t1 前 commit 的事务都执行完之后再执行.
52+
53+
即:
54+
55+
- 如果一个线程内的2个 txn, 那么这个线程应该负责保证自己需要的 Linearizable;
56+
- 2个线程中的2个 txn:
57+
- 如果彼此不知道对方, 则不需要保证 Linearizable;
58+
- 如果 txn2 知道 txn1 , 那么说明他们通过某种途径进行了通讯,
59+
那么应该由这个通讯的路径(可能是通过某表)来保证 Linearizable. 而不是把麻烦不分职责的丢给系统S让它做表.
60+
61+
各种解决分布式 Linearizable 的文章讨论的, 就是在解决怎么把 t1 告诉 txn2 的问题:
62+
或者让系统S本身做表, 或者找一个第三方发号器做表.
63+
64+
有不少的 Linearizable 看起来是在绑起双手解决问题, 例如 Raft 中
65+
Linearizable-read 的实现, 是让系统S自己做表的例子: txn2 做 read 时,
66+
要等待系统中所有 log 都 apply 才进行 read, 不论这些 log 是否跟这个 read 操作相关.
67+
68+
在我看来, 更好的设计应该是, Raft 给每个 write 操作返回它的 log 的对应 id, 后面的
69+
read 操作如果依赖之前某个 write 的结果, 那么就把这个 log id
70+
交给 Raft 使之知道至少 apply 到哪个 log 为止才能被 read.
71+
72+
log id 在 Raft 系统中就是这个表的时间.
73+
74+
75+
76+
Reference:
77+

_src/build.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/sh
2+
3+
path="$1"
4+
5+
base="$(git rev-parse --show-toplevel)"
6+
echo "git root: ($base)"
7+
8+
9+
fn="${path##*/}"
10+
echo "article: $path"
11+
12+
md2zhihu \
13+
--platform zhihu \
14+
--refs "$base/_data/refs.yml" \
15+
--jekyll \
16+
--output-dir "$base/post-res" \
17+
--md-output "$base/_posts/$fn" \
18+
--rewrite "^../post-res/" "/post-res/" \
19+
$path
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: "分布式系统中的Linearizable事务:时间、通信与一致性"
3+
authors:
4+
- xp
5+
categories:
6+
- algo
7+
tags:
8+
- distributed
9+
- 分布式
10+
11+
refs:
12+
- x: y
13+
14+
disabled_article:
15+
image: /post-res/linearizable/linearizable-banner-big.png
16+
17+
mathjax: false
18+
toc: true
19+
toc_label: 本文目录
20+
toc_sticky: true
21+
excerpt: "分布式系统中,Linearizable事务的实现需要解决事务间先后顺序的判断问题,本文深入探讨了这一难题,分析了其中的时间一致性挑战,并提出了几种解决方案和设计思路。"
22+
---
23+
24+
![](./linearizable-banner-47x20.jpg)
25+
26+
**Linearizable 事务的定义**: 对一个分布式系统S, 如果 txn2 在 txn1 commit之后发起, 那么 txn2 一定能看到 txn1 提交的数据.
27+
28+
那么, **怎么知道 txn2 是不是在 txn1 之后呢**?
29+
30+
例如可以这么做: 在 txn1 commit后看一眼表得到时间 t1 , 在 txn2 开始前看一眼表得到时间 t2 , 如果 `t2 > t1` , 那么就说 txn2 是在 txn1 之后.
31+
32+
那么, **怎么保证他俩看的这两块表的时间一致呢**?
33+
34+
相对论告诉我们对不同的观察者, `t2 > t1``t1 > t2` 可能在不同的参考系里分别被观察到.
35+
因此看2块表决定先后的方法, 理论上在我们这个宇宙中就是无法实现的.
36+
37+
38+
如下图, R₁ 惯性系相对 R₀ 惯性系以 v = sinθ 运动时, R₀ 中的 t=1 时刻在 R₁
39+
看来发生在时刻 1 之后, 反之一样; 其中距离单位为1光秒, 所以以光速 C
40+
运动的参考系是直线 x=t; 某个 R₁ 中的 t=1 的点在 R₀ 中的 t²-x²=1 的双曲线上:
41+
42+
43+
![](./linearizable-relativity.excalidraw.png)
44+
45+
因此, **要确定先后, txn1 和 txn2 就必须看同一块表**.
46+
47+
也就是说, **txn1 和 txn2 必须有一次通讯**(直接通讯, 或通过第三方, 即同一块表), 才能确认彼此先后.
48+
49+
也就是说, 要真正达成 Linearizable, txn1 commit 后必须有个 **写表** 的操作记录自己
50+
commit 的 **时刻**, txn2 开始前必须有个 **读表** 的操作, 才能正确区分先后,
51+
从而达成 Linearizable. 有些分布式系统本身就可以做表(虚拟时间), 例如 Paxos 的 `ballot` 就是表,
52+
Raft 的 `(term, log_index)` 也是表.
53+
54+
当然, 你可以说我不用看表, 我的代码写的就是完成 txn1 后, 才发起的 txn2;
55+
在这个情况下, 实际上是 **让 txn1 和 txn2 直接通讯了**, 这个情况下要达成
56+
Linearizable, 应该直接把 t1 当做 txn2 的一部分提交到系统S, 告诉系统S, txn2 要在
57+
t1 前 commit 的事务都执行完之后再执行.
58+
59+
即:
60+
- 如果一个线程内的2个 txn, 那么这个线程应该负责保证自己需要的 Linearizable;
61+
- 2个线程中的2个 txn:
62+
- 如果彼此不知道对方, 则不需要保证 Linearizable;
63+
- 如果 txn2 知道 txn1 , 那么说明他们通过某种途径进行了通讯,
64+
那么应该由这个通讯的路径(可能是通过某表)来保证 Linearizable. 而不是把麻烦不分职责的丢给系统S让它做表.
65+
66+
各种解决分布式 Linearizable 的文章讨论的, 就是在解决怎么把 t1 告诉 txn2 的问题:
67+
或者让系统S本身做表, 或者找一个第三方发号器做表.
68+
69+
有不少的 Linearizable 看起来是在绑起双手解决问题, 例如 Raft 中
70+
Linearizable-read 的实现, 是让系统S自己做表的例子: txn2 做 read 时,
71+
要等待系统中所有 log 都 apply 才进行 read, 不论这些 log 是否跟这个 read 操作相关.
72+
73+
在我看来, 更好的设计应该是, Raft 给每个 write 操作返回它的 log 的对应 id, 后面的
74+
read 操作如果依赖之前某个 write 的结果, 那么就把这个 log id
75+
交给 Raft 使之知道至少 apply 到哪个 log 为止才能被 read.
76+
77+
log id 在 Raft 系统中就是这个表的时间.
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)