zookeeper是个啥
zookeeper是一个分布式的协同系统,来自于大名鼎鼎的Apache软件基金会。在分布式环境下,不同运行实例之间的协同是一个必须要考虑的问题,例如在单机模式下的锁用synchronized关键字或信号量就可以解决,但在分布式环境下就需要借助应用程序以外的存储来实现(如数据库或redis)。分布式协同还需考虑性能、一致性、高可用,在分布式锁这个场景中还需要提供通用的同步原语能力。
zookeeper就提供了这样一个机制,简化了分布式协同的开发。
说人话就是:在分布式场景下找个地方来放所有人都能访问的数据。
这样讲的话redis也能做,那他俩区别是啥?
redis主打的是一个高性能,每个请求都能快速响应,集群挂了也能自己恢复,但是有可能存在数据不一致,所以在CAP理论中,redis是一套CA系统。
zookeeper则属于CP系统,对数据有强一致性保证,在可用性上相对较弱(主要体现在选主时不可用)。适用于存储一些协同数据,或元数据,不适合做海量数据存储。另外,为保证一致性,zookeeper在数据更新操作上使用zab协议(有点类似两阶段提交技术),有一定的性能损耗,不适合做高并发操作。
早期的kafka、dubbo都是用zookeeper来做为注册中心的,但由于其可用性上的问题,后来都被放弃。
难道不能用MySQL来存吗?
当然也可以,不过MySQL毕竟是个关系型数据库,相对而言重了一些,消耗资源也较多。
zookeeper的节点类型
zookeeper上存储的数据是一个树状结构,有点类似于目录树,每一个目录都称作一个节点,也称为znode,znode通常可以表示为一个路径,例如/path,znode上可以包含数据(字节数组,或理解为只能存字符串),也可以不包含数据,数据的序列化和反序列化由应用实现。
znode有以下几种类型:
- 持久(persistent)节点。只能通过delete来删除
- 临时(ephemeral)节点。当创建该节点的客户端崩溃或关闭了连接时,节点会被删除。也可以主动调用delete删除
- 无序节点。
- 有序(sequential)节点。在创建有序znode时,会在路径之后追加一个单调递增的整数。
以上节点类型可以两两组合,一共有4种类型。
由于临时节点在创建者会话过期时会被删除,因此临时节点不可以有子节点
基本操作
- 创建节点:create /path "data"
- 创建临时节点:create -e /path "data"
- 查看当前节点内容:get /path
- 查看子节点:ls /path
- ls2 /path = ls + get
- 修改节点内容:set /path "data"
- 删除节点:delete /path
zxid
每一个可能会改变zookeeper状态的请求,例如create、delete、set等都会被转发给leader来执行,并生成新的状态,这个过程在一个事务内,zookeeper为每一个事务分配了一个zxid
。zxid保证了整个zookeeper集群对事务处理的有序性和一致性,选举过程也会用到zxid。
zxid是一个64bit的整数,又分为32bit的两个部分:epoch和counter。
zxid具有以下特性:
1、 zookeeper的状态每变更一次(创建、删除节点、更新节点数据),zxid都会变更
2、 zxid是递增的,如果zxid1 < zxid2,那么zxid1一定先于zxid2发生
3、 epoch表示leader的关系是否改变,每次一个新的leader被选出,都会有一个新的epoch
zookeeper Server的状态
- LOOKING
- LEADING
- FOLLOWING
- OBSERVING
其中前三种状态在选举过程 中用到,而OBSERVING状态表示服务器是一个观察者,观察者不参与投票,但可以接收leader的proposal,观察者可以看做是一个只读的zookeeper服务器。
为啥要选举
在zookeeper集群中,必须要有一个leader来处理更新请求,并通过zab协议 维持整个集群的状态一致。
zookeeper是一个去中心化的集群,也就是说不需要一个单独的管理服务,或者显式指定集群的leader。当集群首次初始化,或leader节点宕机后,zookeeper集群自身即可完成新leader的选举。
选举过程
1、 每个服务器启动后,或leader失联后,进入 LOOKING
状态
2、 向集群内的其他所有服务器发送投票消息,投票消息包括 {sid,zxid}
,其中sid是服务器编号,zxid是最新处理的事务id。首次投票时投给自己
3、 集群中其他机器收到投票,判断投票有效性,如检查是否本轮投票,是否来自LOOKING状态的服务器
4、 将收到的 {sid,zxid}
和自己投出的 {mysid,myzxid}
对比,选出较大的zxid,如果zxid相同则选出较大的sid,更新 {mysid,myzxid}
后重新把票投出
5、 统计投票,每次投票后,服务器都统计投票信息,判断票数是否过半,过半数的服务器成为新的leader
6、 选举结束,leader进入 LEADING
状态,其他参与投票的服务器进入 FOLLOWING
状态
7、 follower尝试连接新的leader,并开始状态同步
其他注意事项
- 选举期间内,zookeeper对外无法提供服务,follower只有在完成状态同步后才可以处理新的请求
- 由于网络请求延迟等原因,有可能选出错误的leader,或者leader未能超过半数选票支持导致选举失败,需要重新发起新一轮选举,导致选举时间过长
例1:网络延迟导致选出错误的leader
有3个服务器s1{1,6}、s2{2,5}、s3{3,5},正常应该选s1作为leader,但如果选举过程中s1与其他机器的网络存在延时,s2和s3在收到s1的选票之前就已经完成了选举,这时候会选出s3作为leader。
为解决这个问题,可以在投出选票之前加一段等待时长(如200ms),虽然每一轮投票的时长有所增加,但可以一定程度避免选票不一致导致的选举轮次过多、以及选出错误的leader等问题。