用 etcd 做分布式锁和 Leader 选举,CAS 锁和排队锁差多少?持有者崩溃或被暂停会怎样?fencing token 怎么救场?选举的故障转移时间由什么决定?这篇用 etcd 3.7.2 的 Txn 和 lease 逐项量出来。
锁 | 同一时刻最多持有者 | 300 次总用时 | 发给 etcd 的请求数 | 等待 p50 | 等待 p99 | 相邻两次获取锁按 create_revision 递增的比例(FIFO 的话应为 100%) -------------------------+-----------+----------+--------------+--------+--------+----------------------------------------------- (a) 单 key CAS 锁 + 随机退避重试 | 1 | 4.4 s | 3807 | 157 ms | 936 ms | (不适用) (b) 排队锁 + 监听前一个 key | 1 | 3.5 s | 1798 | 210 ms | 263 ms | 100% 两种锁都做到了互斥(最多 1 个持有者)。CAS 锁在竞争激烈时,大量请求浪费在失败的重试上;排队锁每个竞争者只监听前一个,请求数少、没有惊群,并且按 create_revision 先到先得,最坏等待更可预期
排队锁每个竞争者只监听前一个,请求数少、先到先得;CAS 锁请求多、等待波动大。
情形 | 结果 -----------------------------+------------------------------------------------------- (a) 持有者崩溃,等待者拿到锁用了 | 2.51 s / 2.54 s / 2.42 s (b) 暂停的持有者恢复后继续写(没有 fencing) | A(token 1214):写入 → B(token 1216):写入 → A(token 1214):写入 (c) 同上,共享资源检查 fencing token | A(token 1218):写入 → B(token 1220):写入 → A(token 1218):拒绝 锁靠 lease 保活,持有者崩溃之后要等 lease 过期(约 TTL 再加一点)别人才能拿到;但 lease 过期而持有者自己不知道时(进程被暂停、时钟跳变),它醒来之后仍然以为持有锁。锁服务本身无法阻止这件事,要靠共享资源一侧检查 fencing token(拿锁时 etcd 返回的单调递增 revision)拒绝旧持有者的写入
崩溃靠 lease 过期释放锁;暂停后醒来的旧持有者要靠 fencing token 在资源一侧拒绝。
lease TTL | 失效方式 | 新 leader 出现用了多久(3 次) | 前任 → 新 leader ----------+------------+--------------------------+---------------------- 2 s | (a) 崩溃 | 1.92 s / 2.13 s / 2.20 s | c0→c1 / c0→c1 / c0→c1 2 s | (b) resign | 0.01 s / 0.01 s / 0.01 s | c0→c1 / c0→c1 / c0→c1 5 s | (a) 崩溃 | 5.09 s / 5.16 s / 5.21 s | c0→c1 / c0→c1 / c0→c1 5 s | (b) resign | 0.00 s / 0.00 s / 0.01 s | c0→c1 / c0→c1 / c0→c1 崩溃的故障转移时间与 TTL 相当(看上表:TTL 2 s 约 2 s,TTL 5 s 约 5 s,里面包括 etcd 检查 lease 到期的延迟);主动 resign 几乎是立刻的。TTL 越短故障转移越快,但网络抖动时 leader 被误判失效(丢掉领导权)的概率越高——和第 40 篇里心跳与 TTL 的关系是同一个取舍。新 leader 总是 create_revision 次小的候选者,所以顺序是确定的(先来的先当)
崩溃时约等于 TTL,主动 resign 几乎立刻。