ZooKeeper(二)ZooKeeper能做什么?
上一节介绍了ZooKeeper的一些基础知识,这一节主要讲ZooKeeper有哪些用途。
命名服务(Name Service)
主要是作为分布式命名服务,通过调用zk的create node api,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。这些paht具有层级结构,非常便于理解和管理。
配置管理(Configuration Management)
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台Server 运行,但该应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行该系统的Server上的配置,这样非常麻烦而且容易出错。
像这样的配置信息完全可以交给 Zookeeper 来管理,将配置信息保存在 Zookeeper 的某个节点中,然后将所有需要修改的应用机器监控配置信息的状态,一旦配置信息发生变化,每台应用机器就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中。
配置管理示意图:
集群管理(Group Membership)
ZooKeeper的集群管理主要在两点:监控集群是否有机器退出和加入、选举master。
对于第一点,过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报“我还活着”。 这种做法可行,但是存在两个比较明显的问题:1)集群中机器有变动的时候,牵连修改的东西比较多。2)有一定的延时。
利用ZooKeeper有两个特性,就可以实时另一种集群机器存活性监控系统:所有机器约定在父目录(比如/GroupMembers
)下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知:某个目录被删除,即有一台机器挂掉了。新机器加入也是类似。
对于第二点,在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算,网络I/O处理),往往只需要让整个集群中的某一台机器进行执行, 其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是这个master选举便是这种场景下的碰到的主要问题。利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建 ·/currentMaster
节点,最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很轻易的在分布式环境中进行集群选取了。
另外,这种场景演化一下,就是动态master选举。这就要用到EPHEMERAL_SEQUENTIAL
类型节点的特性了。
动态master选举可以用来解决分布式系统中的单点故障。什么是分布式系统中的单点故障:通常分布式系统采用主从模式,就是一个主控机连接多个处理节点。主节点负责分发任务,从节点负责处理任务,当我们的主节点发生故障时,那么整个系统就都瘫痪了,那么我们把这种故障叫作<font color=#D2691E>单点故障</font>。
传统的解决方案是采用一个备用节点,这个备用节点定期给当前主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack,当备用节点收到回复的时候就会认为当前主节点还活着,让它继续提供服务。如图所示:
当主节点挂了,这时候备用节点收不到回复了,然后他就认为主节点挂了接替他成为主节点:
但是这种方式就是有一个隐患,就是网络问题,来看一网络问题会造成什么后果,如下图所示:
也就是说我们的主节点的并没有挂,只是在回复的时候网络发生故障,这样我们的备用节点同样收不到回复,就会认为主节点挂了,然后备用节点将他的Master实例启动起来,这样我们的分布式系统当中就有了两个主节点也就是---双Master, 出现双Master以后从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了备用节点,这样服务就全乱了。为了防止出现这种情况,我们引入了 ZooKeeper,它虽然不能避免网络故障,但它能够保证每时每刻只有一个Master。
具体方案是:
1.master启动
在引入了Zookeeper以后我们启动了两个主节点,"主节点-A"和"主节点-B"他们启动以后,都向ZooKeeper去注册一个节点(EPHEMERAL_SEQUENTIAL类型节点)。我们假设"主节点-A"锁注册地节点是"master-00001","主节点-B"注册的节点是"master-00002",注册完以后进行选举,规定选举序号最小的节点作为为主节点,也就是我们的"主节点-A"将会成为主节点,然后"主节点-B"将成为一个备用节点。通过这种方式就完成了对两个Master进程的调度。
2.master故障
如果"主节点-A"挂了,这时候他所注册的节点将被自动删除,ZooKeeper会自动感知节点的变化,然后再次发出选举,这时候"主节点-B"将在选举中获胜,替代"主节点-A"成为主节点。
3.master恢复
如果主节点恢复了,他会再次向ZooKeeper注册一个节点,这时候他注册的节点将会是"master-00003",ZooKeeper会感知节点的变化再次发动选举,这时候"主节点-B"在选举中会再次获胜继续担任"主节点","主节点-A"会担任备用节点。
分布式锁
什么是分布式锁?
- 一般的锁:一般我们说的锁是单进程多线程的锁,在多线程并发编程中,用于线程之间的数据同步,保护共享资源的访问
- 分布式锁:分布式锁指的是在分布式环境下,保护跨进程,跨主机,跨网络的共享资源,实现互斥访问,保证一致性
分布式锁主要得益于ZooKeeper为我们保证了数据的一致性,即用户只要完全相信每时每刻,zk集群中任意节点(一台zk server)上的相同Znode的数据是一定都是相同的。
分布式锁的架构图:
解释: 左边的整个区域表示一个ZooKeeper集群,locker是ZooKeeper的一个持久节点,node_1、node_2、node_3是locker这个持久节点下面的临时顺序节点。client_1、client_2、client_n表示多个客户端,Service表示需要互斥访问的共享资源。
分布式锁的总体思路
需要获得锁的 client 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。
分布式队列
Zookeeper 可以处理两种类型的队列:
- 队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型。
- 当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
第一类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。实现起来也非常简单,就是在特定的目录下创建 SEQUENTIAL 类型的子目录/queue_i
,这样就能保证所有成员加入队列时都是有编号的,出队列时通过getChildren( )
方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。
第二类,通常可以在/queue
这个Znode下预先建立一个/queue/num
节点,并且赋值为n(或者直接给/queue赋值为n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行 了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList
下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当/taskList
发现自己下面的子节点满足指定个数,就可以进行下一步处理了(比如创建一个/task/start
节点)。