参考链接:https://my.oschina.net/ifraincoat/blog/870162
Apache ZooKeeper为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 Zookeeper的架构通过冗余服务实现高可用,因此,如果第一次无应答,客户端就可以询问另一台ZooKeeper主机。ZK节点将它们的数据存储在一个分层的命名空间,类似于一个文件系统或者一个前缀树结构。客户端可以在节点读写,从而以这种方式拥有一个共享的配置服务。更新是全序的。
1.基本概念
1.1 角色
Lerader:负责写服务
Follower:直接为客户端提供服务,参与投票,同时与Leader进行数据交换(同步)
Observer:为客户端提供服务,但不参与投票,同时与Leader进行数据交换
1.2 设计目的
1.最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
2 .可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
3 .实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
4 .等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
5.原子性:更新只能成功或者失败,没有中间状态。
6 .顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
2.Zk分布式一致性的实现 基于ZAB协议
Zk集群是一主多从的结构。在更新数据时,首先更新到主节点(这里节点是指服务器,而不是指ZNode),再同步到从节点。在读取数据时,直接读取任意从节点。
为了保证主从节点的数据一致性,Zk使用了ZAB协议,这种协议类似于一致性算法Paxos和Raft,有效解决了Zk集群崩溃恢复以及主从同步数据的问题。
ZAB协议定义的三种节点状态:
1.Looking:选举状态
2.Following:Follower节点所处的状态
3.Leading:Leader节点所处状态
2.1 ZAB下的选举 FastLeader算法
ZK fastleader选举算法链接:http://codemacro.com/2014/10/19/zk-fastleaderelection/
通俗点就是:一开始每个节点都相当于一个选民(Observer除外,它不参与投票),每个节点都有自己的推荐人,最开始它们都推荐自己,谁更适合成为Leader有一个简单的规则,例如sid够大(配置)、持有的数据够新(zxid够大)。每个选民都告诉其它选民自己目前的推荐人是谁,每一个选民发现有比自己更适合的人时就推荐这个更适合的人。 最后,大部分人意见一致时,就可以结束选举。
2.2 ZAB下的崩溃恢复
崩溃恢复的三个阶段
① Leader election
② Discovery
③ Synchronization
ZAB怎么实现写入数据?写入数据,涉及到ZAB协议的Broadcast阶段。什么是Broadcast呢?简单来说,就是Zookeeper常规情况下更新数据的时候,由Leader广播到所有Follower。其过程如下:
① 客户端发出写入数据请求给任意Follower
② Follower把写入数据请求转发给Leader
③ Leader采用二阶段提交方式,先发送Propose广播给Follower。
④ Follower接到Propose消息,写入日志成功后,返回ACK消息给Leader
⑤ Leader接到半数以上ACK消息,返回成功给客户端,并且广播Commit请求给Follower。
小结:Zab协议既不是强一致性,也不是弱一致性,而是处于两者之间的单调一致性。它依靠事务ID和版本号,保证了数据的更新和读取是有序的。
3.Zk 应用场景
3.1 基于Watch的Zk监控
Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量、结构体声明也在 zookeeper.h 中,如果如果你在使用 C API 是遇到不明白的地方,最好看看 zookeeper.h,或者自己使用 doxygen 生成 Zookeeper C API 的帮助文档。
Zookeeper 中最有特色且最不容易理解的是监视(Watches)。Zookeeper 所有的读操作——getData(), getChildren(), 和 exists() 都 可以设置监视(watch),监视事件可以理解为一次性的触发器, 官方定义如下: a watch event is one-time trigger, sent to the client that set the watch, which occurs when the data for which the watch was set changes。对此需要作出如下理解:
(一次性触发)One-time trigger
当设置监视的数据发生改变时,该监视事件会被发送到客户端,例如,如果客户端调用了 getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。
(发送至客户端)Sent to the client
Zookeeper 客户端和服务端是通过 socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
(被设置 watch 的数据)The data for which the watch was set
这意味着 znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表:数据监视和子节点监视(data watches and child watches) getData() and exists() 设置数据监视,getChildren() 设置子节点监视。 或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode 节点的相关信息,而 getChildren() 返回子节点列表。因此, setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete() 操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。
Zookeeper 中的监视是轻量级的,因此容易设置、维护和分发。当客户端与 Zookeeper 服务器端失去联系时,客户端并不会收到监视事件的通知,只有当客户端重新连接后,若在必要的情况下,以前注册的监视会重新被注册并触发,对于开发人员来说 这通常是透明的。只有一种情况会导致监视事件的丢失,即:通过 exists() 设置了某个 znode 节点的监视,但是如果某个客户端在此 znode 节点被创建和删除的时间间隔内与 zookeeper 服务器失去了联系,该客户端即使稍后重新连接 zookeeper服务器后也得不到事件通知。
3.2 分布式锁
参考文章:https://www.cnblogs.com/linjiqin/p/6052031.html
大致就是在zk已有能力上,为每一个竞争资源的客户端创建临时节点,临时节点有一个id,按从小到大的排序,后一个节点watch前一个节点,当前一个节点释放锁后立即通知后一个节点。
4.基于Zk的服务发现
参考文章:https://www.cnblogs.com/shijingxiang/articles/5239179.html
基于Zookeeper的服务注册与发现架构
在此架构中有三类角色:服务提供者,服务注册中心,服务消费者。
服务提供者
服务提供者作为服务的提供方将自身的服务信息注册到服务注册中心中。服务信息包含:
▪ 隶属于哪个系统
▪ 服务的IP,端口
▪ 服务的请求URL
▪ 服务的权重等等
服务注册中心
服务注册中心主要提供所有服务注册信息的中心存储,同时负责将服务注册信息的更新通知实时的Push给服务消费者(主要是通过Zookeeper的Watcher机制来实现的)。
服务消费者
服务消费者主要职责如下:
-
服务消费者在启动时从服务注册中心获取需要的服务注册信息
-
将服务注册信息缓存在本地
-
监听服务注册信息的变更,如接收到服务注册中心的服务变更通知,则在本地缓存中更新服务的注册信息
-
根据本地缓存中的服务注册信息构建服务调用请求,并根据负载均衡策略(随机负载均衡,Round-Robin负载均衡等)来转发请求
-
对服务提供方的存活进行检测,如果出现服务不可用的服务提供方,将从本地缓存中剔除
服务消费者只在自己初始化以及服务变更时会依赖服务注册中心,在此阶段的单点故障通过Zookeeper集群来进行保障。在整个服务调用过程中,服务消费者不依赖于任何第三方服务。
用zk服务发现和其它服务发现的对比:https://blog.csdn.net/dengyisheng/article/details/71215234