一、zk的watcher机制
客户端的Watcher
public class ZkWatchManager
{
dataWatches:Map<String,Set<Watcher>>
existWatches: Map<String,Set<Watcher>>
childWatches: Map<String,Set<Watcher>>
defaultWatches: Watcher
}
String就是节点的path,有三大类。
dataWatches:数据watcher
existWatches:节点是否存在的watcher
childWatches:节点的子节点个数变化,包括:0->1,1->2等等,只要数量变化就触发。
对于zk的watcher机制,有几个疑问?
-
假如ZK客户端调用getDate(path,watcher)的request时,watcher设置了。但是由于request在序列化的时候,仅仅序列化一个flag。传递给服务端时,怎么知道客户端感兴趣的watcher是啥呢?
答疑:我觉得所有通过getDate方法注册的watcher,肯定就是对path节点数据的变更感兴趣,有且只有它。如果是对path节点时候存在感兴趣,就会调用exist接口了。如果是对path节点的子节点感兴趣,就去调用getChildren接口。 -
ZK是如何执行watch的呢?
答疑:我理解是ZK客户端在请求的时候,先发送request到服务端。然后服务端把watcher注册到自己的watcherManager,然后立即返回。如果成功,客户端可以安全把watcher注册到自己的watcherMananger。然后服务端在某一个时刻,恰好触发了相应事件,再通知客户端。比如:客户端在getDate设置了watcher,然后某个时刻,服务端恰好收到了setData的请求。这个set方法体里面,就会触发该节点的watcherMananger,去看看有没有对自己感兴趣的watcher。如果有,那么就执行watcher的process方法。服务端的watcher处理其实很简单,仅仅把事件封装起来,通过网络发送给客户端。 -
有人可能疑问,watcher机制的客户端和服务端的tcp连接,是长连接还是短连接?
答疑:我理解应该是短连接。如果是长连接,由于watcher啥时候触发是未知的,不至于这么浪费tcp吧。毕竟维持这种心跳,开销也不小。
二、watcher机制的特点:
- 一次性
无论是客户端还是服务端,watcher都是一次性的,注册过,只要触发一次,两边的watcher都会删除。这有效的减轻了服务端的通信压力,
- 客户端串行执行
客户端同一个节点,可能有多个watcher。服务端通知响应事件,触发watcher执行时,客户端是由EventThread单线程执行。单线程保证了watcher执行的顺序行。但是缺点是,每个watcher处理时需要处理异常,绝对不能影响到其他watcher。
- 轻量
通知事件很轻,只包含三部分数据。通知状态,事件类型和节点路径。也就是说事件发生的内容,需要客户端重新去服务器拿,通知里面是没有的。
三、ZK的通信协议
分两类:请求和响应协议。是基于TCP/IP的应用层协议,其实就是规定了应用层报文的格式,以及每个字段的含义。客户端和服务端双方都遵守这种约定,脱离了ZK,其他框架是无法识别这种通信协议的。
请求和响应报文格式,网上搜一下一大堆。这里不再赘述
二、zk是一个可靠的分布式协调框架。
- 可靠是怎么实现的呢?
zk本身就是一个分布式系统,至少三台机器组成的系统,用来保证数据可靠和服务可靠。只要是多台机器,就一定会引入一致性问题(多台机器存储同一份数据,就一定会有延迟,或者网络不通等等意外情况)。为了实现一致性,zk实现了Paxos算法的变种ZAB协议。该协议不仅实现了一致性,也实现了选举规则,它是一个协议,内容远远不止实现一个算法,协议包含了很多其他内容。
-
什么是分布式协调?
先上一个官网的图。
image.png
对zk来说,所有连接它的服务都是client角色,并没有service和consumer。这种提供者和消费者的角色仅仅只是dubbo或其他RPC框架这种业务场景下的区分而已,绝没有限制zk的使用场景。真正的zk使用场景要比RPC的使用,要宽泛通用的多。
那么回到什么是分布式协调呢?我们就以RPC这种场景来说:
比如我们可以理解上面的图,一半client是service角色,一半client是consumer角色,然后zk提供了这些client之间的协调 -- 充当consumer角色的client必须依赖于充当service角色提供的服务,这可不就是分布式协调的一种嘛(协调其中一半的client依赖另一半)。当然我们可以继续扩展:分布式配置中心Disconf,有个client它的角色可以是管理员(配置员),它是一个进程。它向zk某一个节点配置了XX数据,然后其他所有client同时收到最新配置。这可不就是另外一种协调了嘛(其他client依赖这个配置的值执行业务)。
三、应用场景
ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能
1. 统一命名服务
乍一看这个定义挺抽象的,一时半会没理解。其实举个例子就容易理解的多,比如你有个资源,我们先简单把该资源当做一个本地文件或本地电影。你为了保证该资源的可靠性,你需要把该资源copy到3台机器上。那么这三个资源,在互联网上就会有三个url地址:机器1/xxx/xxx/xxx.pdf,机器2/xxx/xxx/xxx.pdf,机器3/xxx/xxx/xxx.pdf。请求方为了访问该资源,肯定不会把这三个地址硬编码到自己本地,这样不易拓展。所以这个时候,就需要一个统一命名服务来封装该资源的三个真实地址,对外只提供一个地址并且永远可以不变。那么这个统一命名服务,我们当然可以用http协议来实现,也可以用zk协议(ZAB协议)来实现。
现在理解了上面的内容,那么我们就容易拓展这个资源的定义了。上面所说的资源,我们可以拓展为一个服务 xxx.xxx.xxx.UserService,该服务的提供方可以有很多个,但是为了封装真实提供方,我们只能对外暴露一个地址那就是xxx.xxx.xxx.UserService。是不是很熟悉?Dubbo的ZK注册中心,就是这么干的。
2. 数据发布与订阅(配置中心)
理解定义我们就不说了,这个很好理解。实际案例咱们可以参考Dubbo的ZK,提供者发布或修改服务真实地址后,消费者可以实时收到变化的通知,然后再去ZK拉取最新数据。这可不就是ZK的发布与订阅嘛,通过Listener观察者模式来实现。应用场景很多:RPC服务发现,配置中心的配置分发等等。
3. 统一集群管理
所谓集群管理无在乎两点:是否有机器退出和加入、选举 Master。
不就是ZK的发布订阅嘛,没啥好说的。
4.Master选举
这里的maser选举,可不是ZK内部的master选举(两者相差十万八千里)。这里的master选举,是指业务调用方,属于client角色的进程,进行选举。
业务master选举其实很简单,完全没必要像ZK内部选举那样,还要实现一套可靠的选举算法(paxos算法包含了一个提案选举,paxos算法可不止一个选举算法)。ZK本身提供了一个可靠创建唯一节点的API接口,这个接口保证了并发调用只会有一个成功。那么业务master选举,是不是可以转化为多个业务进程并发调用该API接口,谁创建唯一节点成功了,谁就是master了,其他进程就是slave。怎么感觉这么像分布式锁的实现呢?除了少了释放锁的通知操作。。。
5.软负载均衡
软负载均衡也没啥好说的,就是注册与发现的变种,或者说发布与订阅的变种。获取统一命名服务的所有提供者列表,然后通过负载均衡算法在这一堆列表中选择一个,策略可以是随机选择,轮训选择,Hash一致性选择等等。。。Dubbo的负债均衡是实现在消费者调用逻辑里面的,并不在获取列表接口里面,ZK不做负载均衡,基于ZK的应用可以做。
6.分布式协调/通知
和上面的数据发布与订阅 一模一样,不在赘述。
7.分布式锁
和上面说的Master选举很像,都是一堆client角色的业务调用方,调用创建某唯一节点的API。谁创建成功,谁就拿锁。只不过ZK的底层实现是严格按照顺序来的,所以必然是ZK接收请求的时候,谁第一个谁拿锁。注意可不是client谁先请求谁拿锁。client请求还要经过网络,同时请求是没法确定哪个client先到达的。
但是光创建唯一节点成功还不叫锁,锁天然包含了两个功能:可重入和释放锁唤醒其他等待的client节点。可重入ZK是实现不了,需要业务方自己实现。释放锁的唤醒ZK是通过Listener的通知唤醒的。
我们看下上面的所有应用场景,其实本质上都是调用zk的两个最核心的接口:创建节点和监听节点变化。
其实,ZK 并非天生就是为这些应用场景设计的,都是后来众多开发者根据其框架的特性,利用其提供的一系列API接口(或者称为原语集),摸索出来的典型使用方法。
四、ZK的存储
ZooKeeper 将数据保存在内存中,这也就保证了高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。ZK集群加机器可不能增加内存,为啥呢?因为ZK集群里面的所有数据,都是每台机器保存一份副本。整个集群内存最小的机器,决定整个集群的内存大小。
ZooKeeper 是高性能的。在“读”多于“写”的应用程序中尤其的高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
ZooKeeper 底层其实只提供了两个功能:
- 管理(存储、读取)用户程序提交的数据
- 为用户程序提交数据节点监听服务
网友评论