注册中心挂了,服务调用方还能撑多久?刷新失败该保留旧列表还是清空?注册中心不可用时实例死了怎么办?etcd 失去多数时读写和续约各是什么表现?这篇用 3 节点 etcd 3.7.2 逐项量出来。
调用方 | 第 0 秒 成功/失败 | 第 1 秒 成功/失败 | 第 2 秒 成功/失败 | 第 3 秒 成功/失败 | 第 4 秒 成功/失败 | 第 5 秒 成功/失败 | 第 6 秒 成功/失败 | 第 7 秒 成功/失败 | 第 8 秒 成功/失败 | 第 9 秒 成功/失败 --------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+------------ (a) 刷新失败保留旧列表 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 (b) 刷新失败清空列表 | 40/0 | 40/0 | 1/39 | 0/40 | 0/40 | 0/40 | 0/40 | 0/40 | 0/40 | 0/40 注册中心整体挂掉时,后端实例其实都还活着:保留旧列表的调用方什么都没感觉;把“拿不到列表”当成“没有实例”的调用方,在下一次刷新失败时(最多一个刷新周期之后)所有请求都失败——为了“一致”而丢掉可用的缓存,把一个控制面故障放大成了数据面故障
刷新失败保留旧列表,控制面故障就不会影响数据面;把拿不到当成没有会放大故障。
做法 | 第 0 秒 | 第 1 秒 | 第 2 秒 | 第 3 秒 | 第 4 秒 | 第 5 秒 | 第 6 秒 | 第 7 秒 | 第 8 秒 | 第 9 秒 | 失败 / 总数 -------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+--------- (a) 无被动摘除 | 40/0 | 40/0 | 40/0 | 40/0 | 27/13 | 26/14 | 27/13 | 27/13 | 26/14 | 27/13 | 80 / 400 (b) 请求失败就本地摘除 30 s | 40/0 | 40/0 | 40/0 | 40/0 | 39/1 | 40/0 | 40/0 | 40/0 | 40/0 | 40/0 | 1 / 400 实例死了而注册中心恰好也不可用时,没有人能把它从列表里去掉:没有被动摘除的调用方会一直把约三分之一的请求发给死实例;被动摘除让调用方靠自己的调用结果修正缓存(第 4 秒之后很快就不再失败)
没有被动摘除就会一直把请求发给死实例;调用方要靠调用结果自己修正。
操作(只剩 1 个节点,约 2 秒后开始) | 结果 -----------------------+------------------------------------------------------------------ 线性一致读(默认) | 失败:TimeoutError(2002 ms) serializable 读(本地读) | 成功(1 ms),读到 /svc/ 下 6 个 key(3 个是本节的 j*,其余是前面几节登记后 lease 还没过期的 i*) 写入 | 失败:TimeoutError(2002 ms) lease 续约 | 失败:TimeoutError(2003 ms) 恢复多数之后 /svc/j* 还在的实例 | 3 / 3 恢复之后各 lease 的剩余 TTL(秒) | 19, 18, 18 少数派节点上:线性一致读和写入都不可用(等不到多数),serializable 读还能读本地的数据(可能是旧的);lease 续约要经过 leader,也不可用。恢复多数、选出新 leader 之后,实例的 key 还在,lease 的剩余时间被重置(见第 41 篇),不会因为故障期间没续上约而大批过期
线性一致读、写入、续约不可用,serializable 读可用;恢复后 key 和 lease 都在。