站在未来的路口,回望历史的迷途,常常会很有意思,因为我们会不经意地兴起疯狂的念头,例如如果当年某事提前发生了,而另外一件事又没有发生会怎样?一如当年的奥匈帝国皇位继承人斐迪南大公夫妇如果没有被塞尔维亚族热血青年普林西普枪杀会怎样,又如若当年的丘老道没有经过牛家村会怎样?
2008 年底,淘宝开启一个叫做“五彩石”的内部重构项目,这个项目后来成为了淘宝服务化、面向分布式走自研之路,走出了互联网中间件体系之始,而淘宝服务注册中心 ConfigServer 于同年诞生。
2008 年前后,Yahoo 这个曾经的互联网巨头开始逐渐在公开场合宣讲自己的大数据分布式协调产品 ZooKeeper,这个产品参考了 Google 发表的关于 Chubby 以及 Paxos 的论文。
2010 年 11 月,ZooKeeper 从 Apache Hadoop 的子项目发展为 Apache 的顶级项目,正式宣告 ZooKeeper 成为一个工业级的成熟稳定的产品。
2011 年,阿里巴巴开源 Dubbo,为了更好开源,需要剥离与阿里内部系统的关系,Dubbo 支持了开源的 ZooKeeper 作为其注册中心,后来在国内,在业界诸君的努力实践下,Dubbo + ZooKeeper 的典型的服务化方案成就了 ZooKeeper 作为注册中心的声名。
2015 年双 11,ConfigServer 服务内部近 8 个年头过去了,阿里巴巴内部“服务规模”超几百万 ,以及推进“千里之外”的 IDC 容灾技术战略等,共同促使阿里巴巴内部开启了 ConfigServer 2.0 到 ConfigServer 3.0 的架构升级之路。
时间走向 2018 年,站在 10 年的时间路口上,有多少人愿意在追逐日新月异的新潮技术概念的时候,稍微慢一下脚步,仔细凝视一下服务发现这个领域,有多少人想到过或者思考过一个问题:
服务发现,ZooKeeper 真的是最佳选择么?
而回望历史,我们也偶有迷思,在服务发现这个场景下,如果当年 ZooKeeper 的诞生之日比我们 HSF 的注册中心 ConfigServer 早一点会怎样?
我们会不会走向先使用 ZooKeeper 然后疯狂改造与修补 ZooKeeper 以适应阿里巴巴的服务化场景与需求的弯路?
但是,站在今天和前人的肩膀上,我们从未如今天这样坚定的认知到,在服务发现领域,ZooKeeper 根本就不能算是最佳的选择,一如这些年一直与我们同行的 Eureka 以及这篇文章 《Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery》那坚定的阐述一样,为什么你不应该用 ZooKeeper 做服务发现!
吾道不孤矣。
接下来,让我们回归对服务发现的需求分析,结合阿里巴巴在关键场景上的实践,来一一分析,一起探讨为何说 ZooKeeper 并不是最合适的注册中心解决方案。
CAP 和 BASE 理论相信读者都已经耳熟能详,其业已成了指导分布式系统及互联网应用构建的关键原则之一,在此不再赘述其理论,我们直接进入对注册中心的数据一致性和可用性需求的分析:
注册中心最本质的功能可以看成是一个 Query 函数
Si = F(service-name)
,以
service-name
为查询参数,
service-name
对应的服务的可用的
endpoints (ip:port)
列表为返回值.
注: 后文将 service 简写为 svc。
先来看看关键数据
endpoints (ip:port)
不一致性带来的影响,即 CAP 中的 C 不满足带来的后果 :
如上图所示,如果一个
svcB 部署了 10 个节点 (副本 /Replica),如果对于同一个服务名 svcB, 调用者 svcA 的 2 个节点的 2 次查询返回了不一致的数据,例如: S1 = { ip1,ip2,ip3...,ip9 }, S2 = { ip2,ip3,....ip10 }, 那么这次不一致带来的影响是什么?相信你一定已经看出来了,svcB 的各个节点流量会有一点不均衡。
ip1 和 ip10 相对其它 8 个节点{ip2...ip9},请求流量小了一点,但很明显,在分布式系统中,即使是对等部署的服务,因为请求到达的时间,硬件的状态,操作系统的调度,虚拟机的 GC 等,任何一个时间点,这些对等部署的节点状态也不可能完全一致,而流量不一致的情况下,只要注册中心在 SLA 承诺的时间内(例如 1s 内)将数据收敛到一致状态(即满足最终一致),流量将很快趋于统计学意义上的一致,所以注册中心以最终一致的模型设计在生产实践中完全可以接受。
接下来我们看一下网络分区(Network Partition)情况下注册中心不可用对服务调用产生的影响,即 CAP 中的 A 不满足时带来的影响。
考虑一个典型的 ZooKeeper 三机房容灾 5 节点部署结构 (即 2-2-1 结构),如下图:
当机房 3 出现网络分区 (Network Partitioned) 的时候,即机房 3 在网络上成了孤岛,我们知道虽然整体 ZooKeeper 服务是可用的,但是节点 ZK5 是不可写的,因为联系不上 Leader。
也就是说,这时候机房 3 的应用服务 svcB 是不可以新部署,重新启动,扩容或者缩容的,但是站在网络和服务调用的角度看,机房 3 的 svcA 虽然无法调用机房 1 和机房 2 的 svcB, 但是与机房 3 的 svcB 之间的网络明明是 OK 的啊,为什么不让我调用本机房的服务?
现在因为注册中心自身为了保脑裂 (P) 下的数据一致性(C)而放弃了可用性,导致了同机房的服务之间出现了无法调用,这是绝对不允许的!
可以说在实践中,注册中心不能因为自身的任何原因破坏服务之间本身的可连通性,这是注册中心设计应该遵循的铁律!
后面在注册中心客户端灾容上我们还会继续讨论。
同时我们再考虑一下这种情况下的数据不一致性,如果机房 1,2,3 之间都成了孤岛,那么如果每个机房的 svcA 都只拿到本机房的 svcB 的 ip 列表,也即在各机房 svcB 的 ip 列表数据完全不一致,影响是什么?
其实没啥大影响,只是这种情况下,全都变成了同机房调用,我们在设计注册中心的时候,有时候甚至会主动利用这种注册中心的数据可以不一致性,来帮助应用主动做到同机房调用,从而优化服务调用链路 RT 的效果!
通过以上我们的阐述可以看到,在 CAP 的权衡中,注册中心的可用性比数据强一致性更宝贵,所以整体设计更应该偏向 AP,而非 CP,数据不一致在可接受范围,而 P 下舍弃 A 却完全违反了注册中心不能因为自身的任何原因破坏服务本身的可连通性的原则。
你所在公司的“微服务”规模有多大?数百微服务?部署了上百个节点?那么 3 年后呢?互联网是产生奇迹的地方,也许你的“服务”一夜之间就家喻户晓,流量倍增,规模翻番!
当数据中心服务规模超过一定数量 (服务规模 =F{服务 pub 数, 服务 sub 数}),作为注册中心的 ZooKeeper 很快就会像下图的驴子一样不堪重负
其实当 ZooKeeper 用对地方时,即用在粗粒度分布式锁,分布式协调场景下,ZooKeeper 能支持的 tps 和支撑的连接数是足够用的,因为这些场景对于 ZooKeeper 的扩展性和容量诉求不是很强烈。
但在服务发现和健康监测场景下,随着服务规模的增大,无论是应用频繁发布时的服务注册带来的写请求,还是刷毫秒级的服务健康状态带来的写请求,还是恨不能整个数据中心的机器或者容器皆与注册中心有长连接带来的连接压力上,ZooKeeper 很快就会力不从心,而 ZooKeeper 的写并不是可扩展的,不可以通过加节点解决水平扩展性问题。
要想在 ZooKeeper 基础上硬着头皮解决服务规模的增长问题,一个实践中可以考虑的方法是想办法梳理业务,垂直划分业务域,将其划分到多个 ZooKeeper 注册中心,但是作为提供通用服务的平台机构组,因自己提供的服务能力不足要业务按照技术的指挥棒配合划分治理业务,真的可行么?
而且这又违反了因为注册中心自身的原因(能力不足)破坏了服务的可连通性,举个简单的例子,1 个搜索业务,1 个地图业务,1 个大文娱业务,1 个游戏业务,他们之间的服务就应该老死不相往来么?也许今天是肯定的,那么明天呢,1 年后呢,10 年后呢?谁知道未来会要打通几个业务域去做什么奇葩的业务创新?注册中心作为基础服务,无法预料未来的时候当然不能妨碍业务服务对未来固有联通性的需求。
需要,也不需要。
我们知道 ZooKeeper 的 ZAB 协议对每一个写请求,会在每个 ZooKeeper 节点上保持写一个事务日志,同时再加上定期的将内存数据镜像(Snapshot)到磁盘来保证数据的一致性和持久性,以及宕机之后的数据可恢复,这是非常好的特性,但是我们要问,在服务发现场景中,其最核心的数据 - 实时的健康的服务的地址列表真的需要数据持久化么?
对于这份数据,答案是否定的。
如上图所示,如果 svcB 经历了注册服务 (ip1) 到扩容到 2 个节点(ip1,ip2)到因宕机缩容 (ip1 宕机),这个过程中,产生了 3 次针对 ZooKeeper 的写操作。
但是仔细分析,通过事务日志,持久化连续记录这个变化过程其实意义不大,因为在服务发现中,服务调用发起方更关注的是其要调用的服务的实时的地址列表和实时健康状态,每次发起调用时,并不关心要调用的服务的历史服务地址列表、过去的健康状态。
但是为什么又说需要呢,因为一个完整的生产可用的注册中心,除了服务的实时地址列表以及实时的健康状态之外,还会存储一些服务的元数据信息,例如服务的版本,分组,所在的数据中心,权重,鉴权策略信息,service label 等元信息,这些数据需要持久化存储,并且注册中心应该提供对这些元信息的检索的能力。
使用 ZooKeeper 作为服务注册中心时,服务的健康检测常利用 ZooKeeper 的 Session 活性 Track 机制 以及结合 Ephemeral ZNode 的机制,简单而言,就是将服务的健康监测绑定在了 ZooKeeper 对于 Session 的健康监测上,或者说绑定在 TCP 长链接活性探测上了。