Zookeeper是一个分布式协调框架,而在它的基本用法在上一节讲过了,很多同学可能会Zookeeper这么好,直接做微服务的注册中心也没啥问题的,其实关于这样点社区里也喋喋不休,几乎在dubbo的每届大会中有大篇幅的讲解。接下来将针对大牛在大厂大集群的实践中去探讨一些坑:
1.注册中心是CP还是AP
在之前的章节分享过关于Consul、zooKeeper、etcd、eureka作为注册中心的选型,对此中间件小哥是这么讲的:
- 首先ZooKeeper诞生晚于内部注册中心ConfigServer
- 当时无法解决多机房的问题(ZooKeeper后续版本可以部署observer解决)
- 他们认为整体设计更应该偏向 AP,而非 CP
- ZooKeeper 的复杂度难以掌握
笔者观点是,这些今天看来未必是问题,引用大牛的话:
Google的Chubby的设计开发者Burrows曾经说过:“所有一致性协议本质上要么是Paxos要么是其变体”
2.客户端与服务端超时时间
Zookeeper的Client与Zookeeper之间维持的是长连接,并且保持心跳,Client会与Zookeeper之间协商出一个Session超时时间出来(其实就是Zookeeper Server里配置了最小值,最大值,如果client的值在这两个值之间则采用client的,小于最小值就是最小值,大于最大值就用最大值),如果在Session超时时间内没有收到心跳,则该Session过期(关注后续SessionExpired 事件)。
3.复杂的状态机及异常
掌握 ZooKeeper Client/Session 的状态机要并不简单,看完下图你可能依然觉得很懵逼
没关系,那我们就关注重要的事件及异常吧
Disconnected 事件(ConnectionLossException)
发生这个异常的原因有很多,例如应用机器与ZooKeeper节点之间网络闪断,ZooKeeper节点宕机,服务端Full GC时间超长,甚至你的应用进程Hang死,应用进程 Full GC 时间超长之后恢复都有可能。很多时候,客户端请求到服务端响应中,当它们之间的长连接闪断的时候,客户端感知到这个闪断事件的时候,会处在一个比较尴尬的境地,Server端到底收到这个请求了么?那我该做什么呢?重试(Retry)?
这完全需要应用开发者自己根据业务语义去评估和处理,必须知晓一点业务请求在Server端服务处理上对于”仅处理一次” “最多处理一次” “最少处理一次”语义要有选择和预期。
SessionExpired 事件(SessionExpiredException )
Session 超时是一个不可恢复的异常,这是指应用Catch到这个异常的时候,应用不可能在同一个Session中恢复应用状态,必须要重新建立新Session,老Session关联的临时节点也可能已经失效,拥有的锁可能已经失效。
4. 网络抖动问题
Zookeeper主要是提供分布式环境的一致性,对于网络隔离的极度敏感,一旦出现网络隔离,zookeeper就要发起选举流程。zookeeper的选举流程通常耗时30到120秒,期间zookeeper由于没有master,都是不可用的。Zookeeper如果出现分区,少数派是不能提供任何服务的,读都不可以。
关于选举这块,重点关注源码的QuorumCnxManager类里的Listener
解决方案:
服务发现其实是不需要严格的一致性的,我们可以缓存server list,当Zookeeper出现问题的时候已然可以正常工作,而etcd要做的更好一些,少数派仍然可以提供读服务。
5.惊群
如果你watch了一些配置,但这个配置发生变更时:
- Zookeeper会广播给所有的watcher,广播的内容是zooKeeper事件(非常简短的内容)
- 所有Client都来拉取,瞬间造成非常大的网络流量,引起所谓的『惊群』
其实这是zk的问题,据一个工商银行的架构师反馈,他们部署的dubbot集群近万台,除了上面的问题外,注册信息的数据占用应用很高内存,watch时网络消耗非常大,甚至是导致业务的应用系统有性能问题乃至不可用。
一般解决方案:
- 注册中心的数据那么大,需要思考存储的数据信息,dubbo最新的版本已经分离为元数据中心和注册中心
- 更新那么频繁,可以通过延迟处理,反正getChildren()可以获取到最新的数据
6.内存占用高,全量同步易超时
zookeeper集群中leader和follower同步数据的极限值是500M,这500M的数据,加载到内存中,大约占用3个G的内存。数据过大,在每次选举之后,需要从server同步到follower,容易造成下面2个问题:
- 网络传输超时,因为文件过大,传输超过最大超时时间,造成TimeoutException,从而引起重新选举。
- 如果调大这个超时值,则很可能达到磁盘读写的上限
为何磁盘IO高?
zk通过心跳将事务日志频繁的写磁盘,而优化思路有:
1.将zk事务的日志放入内存中
降低对磁盘的依赖,但需要控制事务日志大小,受内存空间容量限制,它风险点是一旦机房断电会导致zookeeper中的部分数据丢失,可以通过以下步骤解决:
#zookeeper配置: dataDir=/opt/zookeeperdata #1.事务日志指向内存文件系统 dataLogDir=/dev/shm #自动清理时保存在datadir里的数据快照数。最小值3 #autopurge.snapRetainCount=3 #2.关闭自动事务日志的管理(自动清理任务的间隔小时数) #autopurge.purgeInterval=1 # 每当10000条事务日志写入时,创建snapshot文件 snapCount=10000 #清理事务日志 #!/bin/bash source /etc/profile #snapshot file dir dataDir=/opt/zookeeperdata/version-2 #tran log dir dataLogDir=/dev/shm/version-2 #leave transaction files leaveTran=10 #leave snapshot files leaveSnap=10000 echo "" echo "" echo "before purge memory:" free -m ls -t $dataLogDir/log.* | tail -n +$leaveTran | xargs rm -f ls -t $dataDir/snapshot.* | tail -n +$leaveSnap | xargs rm -f echo "after purge memory:" free -m
2.SSD-更强的IO
如果不允许重要的数据丢失,它可能是一个不错的方案
可行解决方案:
- 如果你的应用强依赖zookeeper,则应该申请机器资源,单独配置zookeeper服务器,防止其他应用的影响。这是目前比较可行的解决方案。不要与那些大应用共用一个zookeeper集群,你可能会被它拖挂的。
- 如果不需要分布式锁,你应该优先考虑不用zookeeper。
- 不要对zookeeper频繁写入,它只应该存储控制信息和配置信息,也就是说,它更多应该用来做读操作。
- 不要把zookeeper作为数据存储器
7.关于扩容
zk扩容主要是通过Observer完成的,Observer从3.4系统中新增的,它不负责投票选举,是同步Leader并承担客户端的连接和请求:
- 不影响参与者选举速度
- 不影响整体的吞吐量
除此之外,它还有一些高级玩法:
- Observer分组,客户端流量隔离
- 异地Observer节点,降低跨城订阅推送流量
而这一块的优化点,可以通过关闭Observer的snapshot和事务日志,来提升Observer节点的IO性能
总结
对应Zookeeper的使用,任何事情所有的未雨绸缪其实源于事前布局,因此我们一定要一些监控,比如:
- 是否可写:一个定时任务定时的去创建节点,删节点等操作(这些操作的时候不要连接整个集群,而是直接去连接单个节点)
- 监控watcher数和连接数,特别是这两个数据有较大波动的时候
- 网络流量以及client ip(谁请求的)