以下一次关于ZooKeeper为主的一次激烈的技术讨论。
为什么说一套系统用了ZooKeeper,这个系统一定很low?
ZooKeeper = low?
提出此观点的架构师介绍其原因如下。
在Docker环境下,通常虚机会比较多,我们发现ZooKeeper不能承受太多节点。我们的游戏平台是一个多租户场景,需要频繁进行创建及删除,在这种情况下如果超过3,000虚拟机,ZooKeeper就不行了。
当ZooKeeper挂了后,meta数据也会跟着丢了。Meta数据和和监控功能在ZooKeeper当初设计时并没有考虑的问题,因此后来我们打算自己造轮子,将这个需求实现。
我也并非说ZooKeeper一无是处,ZooKeeper有它适合使用的场景,比如Hadoop那种场景ZooKeeper就可以工作得很好,但并不是所有场景都适用。Tim说过一句经典的话,Redis是把好锤子,但不能把所有存储的问题都当做钉子。ZooKeeper也是一样,不是所有的配置的问题都适合拿它来解决。
其他一些网友的看法。
Z: 同意ZooKeeper是有很多运维的问题,一个解决方法是自己实现一个single node all in memory的lock service,然后运行在多个机器上,用ZooKeeper当作一个distributed lock来选一个master,这样ZooKeeper上的压力就小多了,而且不会随着集群大小增加而增加。
Y: Kafka里使用ZooKeeper的方式有好几个地方可以借鉴。除了上面说的选一个master处理的方式外,为了避免订阅大量节点,也可以单设置一个变更节点,然后只订阅这个变更节点。
G: Kafka集群依赖于ZooKeeper,但ZooKeeper也是Kafka的瓶颈。
某知名大型互联网公司方案
某知名大型互联网公司的架构师也基本认可上述观点,其新开发的服务框架中不再采用ZooKeeper作为注册中心,主要说明如下
1. 随着部署规模增大,客户端增多,ZooKeeper服务器中节点数量大增,核心ZooKeeper集群5台,每台服务器1,5000左右的长链接,近43万个节点,平均30余万watch。每个上线日,ZooKeeper服务器的流量都能上200mb/s,通过mntr的观察zk_outstanding_requests 经常达到300M以上;
2. 有业务方报告,应用启动后连上ZooKeeper但一直读失败(读数据返回为null),也就是取服务列表失败,一段时间如半小时后自动恢复,查看zk server端日志,只看到一堆session过期的警告,没有其他异常;
3. 由于历史原因,ZooKeeper服务器与其他应用共用,40万+节点中60%是我们的,其他应用也是重度依赖zookeeper,互相之间有影响;
4. ZooKeeper推送的频率、内容,我们自己不能控制,比如一个600 provider,12000 consumer的服务,如果新增一个provider,其实并不是每个consumer都需要通知的,但目前的机制下consumer监听一个目录,每个consumer都会得到一次provider列表推送;还有我们想在推送前根据一定的规则对provider列表做动态过滤排序,这个需求在zookeeper服务端也没法实现。
5. ZooKeeper客户端使用的是 172.17.xx.yy,172.17.xx.yz:2181 这样的ip串方式连接,后期想添加服务器分散主集群读写压力,也不好实现,因为需要更改地址串,需要update配置的客户端太多了;
6. 跨机房容灾方面,一旦出现机房间通信问题,另一个机房的部署的Observer节点,就不可读写了;
7. provider约6万实例,consumer22万实例;
基于以上的原因,新的方案实现也很简单,自己实现了一个服务用来做注册中心的,实现的是服务注册/订阅方面的功能。它负责接收长链接并推送,数据存储在MySQL。
Q:使用ZooKeeper主要是它实现了强一致性,你这个注册中心是单点的吧?master election用ZooKeeper?注册中心会成为新的瓶颈和故障点?
A:性能方面完全够用,因为这个服务也是集群部署的,客户端首先访问一个http接口拿到所有服务器的地址,并优先访问本机房的注册中心。注册中心基本读多写少,内存里面也缓存provider列表,不会有太大压力到MySQL;存储上通过MySQL实现一致性保证。
Q:跨机房容灾方面有什么优化吗?如果网络没有足够的redundency,一旦出现network partiton,那么有一个机房就没办法到quorum了?
A:通过多机房部署,每个机房保证至少2个节点,本机房挂了还可以访问其它机房,并且在客户端本地还有缓存文件。
Q:当配置变化,client以http轮询方式去感知吗?
A:Session过期那个,开始我们设的是很短,后来发现不对,网络一闪断,大量掉节点。现在我们自己实现的服务,半分钟一心跳,几个心跳没收到我才认为下线了。如果provider列表发生变化,我这边服务端会主动推送的,因为用的是TCP长连接。
Q: Client有没有主动发现本地cache与服务端数据不一致的机制?例如当provider列表发生变化且服务端通过TCP长连给client推送失败的场景。
A:有,我们心跳带本地版本号的,如果与服务端不一致的话,会触发服务端再次推送;
另一知名大型互联网公司ZooKeeper运行现状
而另外一知名大公司架构师认为如果consumer数量没有那么多,用ZooKeeper也能较好满足要求。其业务场景的ZooKeepr使用及运行情况如下。
线上的单个ZooKeeper节点平均连接数是6K,watcher是30万,netIO不高,ZooKeeper的机器配置比较差,用的是4G 4CPU 的虚拟机,5个节点,最多的consumer不超过200,平均consumer数量就5,6个左右。
目前线上provider数3万左右实例数,consumer数10万左右,目前运行的没什么问题。3万个,如果每个服务10个实例,也就3,000个服务,全公司的,也没多少。看起来是多了点。 跟公司发展历史及使用场景关系很大。
之前关于ZooKeeper踩坑最多的是在客户端上,最开始用的是netflix的curator(后来贡献给apache,但是我们用的是老的,没升级),遇到网络闪断重连不上,然后死循环一样,升级到apache curator就好了。
后来遇到一个问题是,如果注册watcher太多,发生重连的时候,zk client会自动注册之前的所有watcher,这样会导致一个包的大小超过1M,然后也就重连不上又不断地重连,后来hack了zk client解决了这个问题,这个bug到目前为止zk官方仍然没有修复。
不过这些坑基本上遇到一个可以解决一个,但是目前有一个问题目前也没找到好的解决方案,那就是业务方系统load变高,或者发生长时间gc,导致zk重连甚至session过期。
另外关于zk连接,我们在最底下做了连接共享,因为好多服务都依赖zk,这个也降低了不少连接数。