服务发现该选 CP 还是 AP?分区时 CP 系统怎么表现、AP 系统又丢了什么?etcd 和 Consul 的故障转移要多久?这篇用 etcd 3.7.2、Consul 2.0.4 的真实集群和一个很小的 AP 复制模型对照。
操作(只剩 1 个 server) | 结果 ------------------+------------------------------------------------------------------------------ 读 KV(默认) | HTTP 500(rpc error getting client: failed to get conn: dial tcp 127.0) · 1 ms 读 KV(?stale) | HTTP 200,读到 1 · 0 ms 读 KV(?consistent) | HTTP 500(rpc error getting client: failed to get conn: dial tcp 127.0) · 0 ms 写 KV | HTTP 500(rpc error getting client: failed to get conn: dial tcp 127.0) · 0 ms 服务目录(默认) | HTTP 500(rpc error getting client: failed to get conn: dial tcp 127.0) · 1 ms 服务目录(?stale) | HTTP 200,读到 1 · 1 ms Consul 默认的读要经过 leader,没有 leader 就失败;加 ?stale 之后任何 server 都可以回答(可能是旧数据),所以剩下的节点还能返回服务目录;写入需要多数,失败。和 etcd 的 serializable 读是同一个思路:用“可能旧”换“可用”,由调用方显式选择
默认读和写都失败;显式选 stale 读才能读到可能旧的数据。
系统 | 4 次:杀掉 leader 到写入恢复 | 中位数 -------+--------------------------------------+-------- etcd | 2.08 s / 2.07 s / 2.08 s / 2.07 s | 2.08 s consul | 9.52 s / 11.94 s / 12.24 s / 10.28 s | 11.11 s 两个 CP 系统在 leader 挂掉之后都要先重新选主才能继续写,期间(这里是秒级)写入不可用;时间取决于选举超时等参数(这里用的都是默认值,不同的调参结果不同)
选主期间不可写,时长取决于默认参数。
时刻 | /svc/web | /svc/a | /svc/b -----------+--------------------------------+-----------------------------+------------------------------- 分区期间 R0 读到 | from-R0 | only-R0 | NULL 分区期间 R1 读到 | from-R12 | NULL | only-R12 分区期间 R2 读到 | from-R12 | NULL | only-R12 愈合后三个副本读到 | from-R12 / from-R12 / from-R12 | only-R0 / only-R0 / only-R0 | only-R12 / only-R12 / only-R12 分区期间 4 次写入全部成功(4 / 4),两边都能读也都能写——可用性是 100%,代价是两边读到不一样的值(/svc/web 在 R0 一侧是 from-R0,在另一侧是 from-R12;/svc/a、/svc/b 只在各自一侧可见)。愈合后 0.28 s 三个副本收敛到同一个值,/svc/web 留下的是 from-R12,被覆盖(丢掉)的写入是 from-R0;/svc/a 和 /svc/b 因为 key 不同,都保留了下来。对照:CP 系统在少数派一侧(第 1 节)拒绝写入,没有分叉,也没有丢更新;AP 系统用冲突解决(这里是最后写入者胜)换来了分区期间的可用
分区期间两边都可用、读到不同的值;愈合后用冲突解决合并,会丢更新。