线性一致和最终一致到底差在哪?读 follower 会读到多旧的数据?把读请求变成线性一致要付出多少延迟?最终一致“最终”要多久?这篇用真实的 3 节点 etcd 和 Redis 主从,加上一个手写的线性一致性检查器,把它们的差别量出来。
条件 | 可串行化读读到旧值的次数 | 可串行化读延迟中位数 | 线性一致读读到旧值的次数 | 线性一致读延迟中位数 | 线性一致读 p99 -------------------+--------------+------------+--------------+------------+---------- 网络无额外延迟 | 101 / 300 | 0.35 ms | 0 / 300 | 0.91 ms | 7.0 ms peer 通信每个方向 +20 ms | 300 / 300 | 0.57 ms | 0 / 300 | 93.45 ms | 102.2 ms 可串行化读不需要问 leader,所以快,但读到的是这个 follower 当前应用到的状态——可能比刚确认的写入旧;线性一致读要先向 leader 确认当前的提交位置再读,保证读到的不比任何已完成的写入旧,代价是多一次与 leader 的往返
可串行化读只看 follower 自己的状态,快但可能比刚确认的写入旧;线性一致读要向 leader 确认,保证不旧,延迟随 leader 的往返上升。
条件 | 异步复制:读 replica 读到旧值的次数 | 写 + 读的总耗时(中位数) | 写后 WAIT 1 0 再读:读到旧值的次数 | 写 + WAIT + 读的总耗时(中位数) ----------------------------+------------------------+----------------+------------------------+---------------------- 无额外延迟 | 73 / 300 | 0.14 ms | 0 / 300 | 0.22 ms master ↔ replica 每个方向 +5 ms | 300 / 300 | 0.21 ms | 0 / 300 | 12.94 ms 每个方向 +20 ms | 300 / 300 | 0.17 ms | 0 / 300 | 48.24 ms Redis 的复制是异步的:master 回复写入时 replica 往往还没收到。WAIT 1 0 让写入等到 1 个 replica 确认,之后读这个 replica 就不会旧,代价是每次写多一个往返(不是线性一致:WAIT 只保证到这个 replica 已确认,别的读者和别的 replica 不在保证里)
Redis master 回复写入时 replica 常常还没收到,写后读 replica 常是旧值;WAIT 1 0 能让这个 replica 不旧,但不是线性一致。
条件 | 收敛时间中位数 | p99 | 最大 ------------+----------+----------+--------- 无额外延迟 | 0.21 ms | 0.38 ms | 0.48 ms 每个方向 +5 ms | 6.38 ms | 6.50 ms | 6.60 ms 每个方向 +20 ms | 24.29 ms | 25.25 ms | 25.42 ms “最终”有多久,取决于复制延迟:无延迟时是亚毫秒到毫秒,链路延迟越大越长;没有人保证它有上限(链路断了就一直不收敛)
最终一致的“最终”约等于复制链路的延迟,没有上限保证。
一段不可线性化的历史(每段开头先写 init、再写 v0,两次写入在其它操作开始之前就已经确认;下面是之后的操作,按开始时间排序,时间相对第一个操作,ms;读到 init 的读,读到的比已经确认的 v0 还旧):
0.00 → 0.79 读 init
0.80 → 1.38 读 init
6.01 → 6.42 读 init
8.81 → 9.78 读 init
8.83 → 9.93 读 init
9.31 → 56.67 写 w1
10.65 → 62.75 写 w2
12.85 → 65.86 写 w3
15.05 → 146.69 写 w4
62.28 → 146.56 写 w5
73.20 → 73.78 读 v0
152.86 → 197.47 写 w6
条件 | 读的方式 | 历史段数 | 不可线性化的段数
-------------------+--------------------+------+---------
网络无额外延迟 | 读走 follower 的可串行化读 | 100 | 46
网络无额外延迟 | 读走 follower 的线性一致读 | 100 | 0
peer 通信每个方向 +20 ms | 读走 follower 的可串行化读 | 100 | 100
peer 通信每个方向 +20 ms | 读走 follower 的线性一致读 | 100 | 0
线性一致读下 100 段历史全部可以找到合法的总顺序;可串行化读在 follower 落后(这里靠加延迟制造)时,会出现“某个写已经确认、之后开始的读却读到更旧的值”的历史,检查器找不到任何合法的顺序记录每个操作的开始和结束时间,找不到合法总顺序的历史就不是线性一致的;线性一致读 0 段违反,可串行化读大量违反。