zookeeper思维导图:
zookeeper分布式协调框架
1.请简单介绍下Zookeeper?(重要)
ZooKeeper是一个分布式的,开放源码的,用于分布式应用程序的协调服务。zookeeper服务端有两种模式:单机的独立模式和集群的仲裁模式,所谓仲裁是指一切事件只要满足多数派同意就执行,不需要等到集群中的每个节点反馈才执行。Zookeeper本身也是服从主从架构的,在仲裁模式下会有一个主要的节点作为Leader(领导者),而其余集群中的节点作为Follower(公民),对某一事件是否执行,leader都会先征询各个follower的反馈信息再做决定,如果多数派同意,leader就将命令下发到所有的follower去执行。
2.Leader选举:
zookeeper的leader选举,leader的选举会发生在集群启动时和运行中leader挂了,概括选举过程也是少数服从多数选出新leader。
Zookeeper所提供的服务主要是通过它以下三部分组成:
Zookeeper = Znode(数据节点,也是简约版文件系统)+ 原语(可以理解成Zookeeper的命令) + Watcher(通知机制,类似监听器)
Znode可以分为持久节点和临时节点,在用Zookeeper的命令create创建文件时默认时一个持久节点,而临时节点是会随着会话关闭而删除。另外也可以创建为有序节点,在创建时追加一个自增数字的标识
Watcher通知机制:
Watcher通知机制类似于监听器的过程,即有注册 + 监听事件+ 回调函数,客户端在znode上注册一个Watcher监视器,当znode上数据出现变化,watcher监测到此变化就会通知客户端。
3.Zookeeper读写流程
在Client向Follwer发出一个读写的请求
Follwer把请求发送给Leader,Leader接收到以后开始发起投票并通知每个Follwer进行投票
Follwer把投票结果发送给Leader
Leader将结果汇总后如果需要读取或写入,则开始执行同时把读写操作通知给Follwer,然后commit
Follower执行并把请求结果返回给Client
4.加入Zookeeper中某个Follower出故障了怎么办?(重要)
这会启动Zookeeper的状态同步过程。具体来说如下:
在完成leader选举后,各Follower和leader进行连接通信,并在每一次事务执行时,Follower都会把自己的最大事务ID发送给leader,当某个Follower出故障后,leader就根据原先该Follower发送的zxid确定同步点,向它同步记录最大zxid之后的内容。
当完成同步后,会通知Follower已成为为update状态,Follower受到update消息后,就可以重新接受客户端的请求继续工作。
zookeeper应用场景
以下各应用场景实码:https://github.com/suzhida/demo_zookeeper/tree/master/src/main/java/com/demo
1.数据发布与订阅(配置中心)
Zookeeper 实现数据的发布和订阅
数据发布与订阅,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和动态更新。对于:数据量通常比较小。数据内容在运行时动态变化。集群中各机器共享,配置一致。
这样的全局配置信息就可以发布到 ZooKeeper上,让客户端(集群的机器)去订阅该消息。
发布/订阅系统一般有两种设计模式,分别是推(Push)和拉(Pull)模式。
推模式:服务端主动将数据更新发送给所有订阅的客户端
拉模式:客户端主动发起请求来获取最新数据,通常客户端都采用定时轮询拉取的方式
ZooKeeper 采用的是推拉相结合的方式:
客户端想服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送 Watcher 事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据
发布方Provider的代码:
package com.zhuyun.release.subscribe; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; public class Provider { public static void main(String[] args) throws Exception { Watcher watcher = new Watcher() { @Override public void process(WatchedEvent event) { System.out.println("触发了" + event.getPath() + "的" + event.getType() + "事件!"); } }; ZooKeeper zk = new ZooKeeper("192.168.10.203:2181", 20, watcher); Stat stat = zk.exists("/message", watcher); if (stat == null) { //假如节点不存在,则先创建节点 zk.create("/message", "hello".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } //向该节点发送消息 zk.setData("/message", "hello world".getBytes(), -1); zk.close(); } }
订阅方Consumer的代码:
package com.zhuyun.release.subscribe; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.ZooKeeper; public class Consumer implements Watcher{ ZooKeeper zk; String hostPort; String znode; public Consumer(String hostPort,String znode) throws Exception{ this.hostPort = hostPort; this.znode = znode; zk = new ZooKeeper(hostPort, 3000, this); //第一次获取节点消息,同时添加watcher System.out.println("消息内容:" + new String(zk.getData(znode, true, null))); } @Override public void process(WatchedEvent event) { if (event.getType() == EventType.NodeDataChanged) { try { //当节点消息变化时,触发该操作:获取变化后的消息,同时再添加watcher System.out.println("你有新的消息:" + new String(zk.getData("/message", true, null))); } catch (KeeperException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String[] args) throws Exception { new Consumer("192.168.10.203:2181","/message"); System.in.read(); } }
首先启动订阅方监听,再启动发布方发布消息,每次发布一条消息,订阅方都能收到该消息,结果如下:
消息内容:我是谁?
你有新的消息:hello
你有新的消息:你好
你有新的消息:hello world
2.命名服务
命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务,远程对象等等——这些我们都可以统称他们为名字。其中较为常见的就是一些分布式服务框架(如 RPC)中的服务地址列表。通过在 ZooKeepr 里创建顺序节点,能够很容易创建一个全局唯一的路径,这个路径就可以作为一个名字。ZooKeeper 的命名服务即生成全局唯一的 ID。
3.分布式协调服务/通知
ZooKeeper 中特有 Watcher 注册与异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端如果机器节点发生了变化,那么所有订阅的客户端都能够接收到相应的 Watcher 通知,并做出相应的处理。ZooKeeper 的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。
4.Master选举
Master 选举可以说是 ZooKeeper 最典型的应用场景了。比如 HDFS 中 Active NameNode 的选举、YARN 中 Active ResourceManager 的选举和 HBase 中 Active HMaster 的选举等。针对 Master 选举的需求,通常情况下,我们可以选择常见的关系型数据库中的主键特性来实现:希望成为 Master 的机器都向数据库中插入一条相同主键 ID 的记录,数据库会帮我们进行主键冲突检查,也就是说,只有一台机器能插入成功,那么,我们就认为向数据库中成功插入数据的客户端机器成为 Master。依靠关系型数据库的主键特性确实能够很好地保证在集群中选举出唯一的一个 Master。但是,如果当前选举出的 Master 挂了,那么该如何处理?谁来告诉我 Master 挂了呢? 显然,关系型数据库无法通知我们这个事件。但是,ZooKeeper 可以做到!利用 ZooKeepr 的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即 ZooKeeper 将会保证客户端无法创建一个已经存在的数据单元节点。也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行 Master 选举了。成功创建该节点的客户端所在的机器就成为了 Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的 Watcher,用于监控当前 Master 机器是否存活,一旦发现当前的 Master 挂了,那么其他客户端将会重新进行 Master 选举。这样就实现了 Master 的动态选举。
5.分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。分布式锁又分为排他锁和共享锁两种
(1)排它锁:
ZooKeeper 如何实现排它锁?
定义锁
ZooKeeper 上的一个机器节点可以表示一个锁
获得锁
把 ZooKeeper 上的一个节点看作是一个锁,获得锁就通过创建临时节点的方式来实现。ZooKeeper 会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到 /exclusive_lock 节点上注册一个子节点变更的 Watcher 监听事件,以便实时监听到 lock 节点的变更情况。
释放锁
因为锁是一个临时节点,释放锁有两种方式
当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁。
正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁。
无论在什么情况下移除了 lock 节点,ZooKeeper 都会通知所有在 /exclusive_lock 节点上注册了节点变更 Watcher 监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复获取锁过程。
(2)共享锁:
共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren 方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。