3. Zookeeper
Zookeeper是Google的Chubby一个开源的实现,是Hadoop的分布式协调服务。
Zookeeper包含一个简单的原语集,分布式应用程序可以基于Zookeeper实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、配置维护、名字服务、分布式同步、分布式锁和分布式队列等功能。
Zookeeper 作为 Hadoop 项目中的一个子项目,是 Hadoop 集群管理的一个必不可少的模块,它主要用来控制集群中的数据,如它管理 Hadoop 集群中的 NameNode,还有 Hbase 中 Master Election、Server 之间状态同步等。
Zoopkeeper 提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构,并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型
3.1 为什么使用Zookeeper?
- 大部分分布式应用都需要一个主控、协调或控制器来管理物理分布的子进程(如资源、任务分配等)。
- 目前,大部分应用需要开发私有的协调程序,缺乏一个通用的机制。
- 协调程序的反复编写浪费,而且难以形成通用、伸缩性好的协调器。
- Zookeeper:提供通用的分布式锁服务,用以协调分布式应用。
3.2 Zookeeper能帮我们做什么?
- Hadoop使用Zookeeper的事件处理确保整个集群只有一个Namenode,存储配置信息等。
- HBase,使用Zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机,存储访问控制列表等。
3.3 Zookeeper的特征
Zookeeper的特征:
- 简单的
- 富有表现力的
- 具有高可用性
- 采用松耦合交互方式
- 是一个资源库
3.4 Zookeeper的部署方式
3.4.1 单机模式
下载ZooKeeper:http://labs.renren.com/apache-mirror/zookeeper/zookeeper-3.4.3/zookeeper-3.4.3.tar.gz
解压:tar xzf zookeeper-3.4.3.tar.gz
在conf目录下创建一个配置文件zoo.cfg,
tickTime=2000
dataDir=/Users/zdandljb/zookeeper/data
dataLogDir=/Users/zdandljb/zookeeper/dataLog clientPort=2181
启动ZooKeeper的Server: sh bin/zkServer.sh start, 如果想要关闭,输入:zkServer.sh stop
单机模式:
只运行在一台服务器上,适合测试环境;Zookeeper的启动脚本在bin目录下;启动脚本之前,还有几个基本的配置项需要配置一下:
tickTime:这个时间是作为Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳。
dataDir:是Zookeeper保存数据的目录,默认情况下,Zookeeper将写数据的日志文件也保存在这个目录里。
clientPort:这个端口就是客户端连接Zookeeper服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求。
当这些配置项配置好之后,就可以启动Zookeeper了。
使用命令echo ruok | nc localhost 2181 检查Zookeeper是否已经在服务
3.4.2 集群模式
创建myid文件,server1机器的内容为:1,server2机器的内容为:2,server3机器的内容为:3
在conf目录下创建一个配置文件zoo.cfg,tickTime=2000
dataDir=/Users/zdandljb/zookeeper/data
dataLogDir=/Users/zdandljb/zookeeper/dataLog clientPort=2181
initLimit=5
syncLimit=2
server.1=server1:2888:3888
server.2=server2:2888:3888 server.3=server3:2888:3888
集群模式:
Zookeeper不仅可以单机提供服务,同时也支持多台机器组成集群来提供服务。
InitLimit:这个配置项是用来配置Zookeeper接受客户端(这里所说的客户端不是用户连接Zookeeper服务器的客户端,而是Zookeeper服务器集群中连接到Leader的Follower服务器)初始化连接时最长能忍受多少个心跳时间间隔数,当已经超过10个心跳的时间(也就是tickTime)长度后Zookeeper服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是5*2000=10秒。
syncLimit:这哥配置项指标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 2*2000=4 秒。
server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。
除了修改 zoo.cfg 配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面就有一个数据就是 A 的值,Zookeeper 启动时会读取这个文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较从而判断到底是那个 server。分别在3台机器上启动ZooKeeper的Server:sh bin/zkServer.sh start;运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble)。Zookeeper通过复制来实现高可用性,只要集合体中半数以上的机器处于可用状态,它就能够保证服务继续。为什么一定要超过半数呢?这跟Zookeeper的复制策略有关:zookeeper确保对znode 树的每一个修改都会被复制到集合体中超过半数的机器上。
3.4.3 伪集群模式
伪集群模式:
建了3个文件夹,server1 server2 server3,然后每个文件夹里面解压一个zookeeper的下载包
进入data目录,创建一个myid的文件,里面写入一个数字,server1,就写一个1,server2对应myid文件就写入2,server3对应myid文件就写个3
在conf目录下创建一个配置文件zoo.cfg,tickTime=2000
dataDir=/Users/zdandljb/zookeeper/data
dataLogDir=xxx/zookeeper/server1/ clientPort=2181
initLimit=5
syncLimit=2
server.1=server1:2888:3888 server.2=server2:2888:3888 server.3=server3:2888:3888
在一台机器上部署了3个server;需要注意的是clientPort这个端口如果在1台机器上部署多个server,那么每台机器都要不同的clientPort,比如 server1是2181,server2是2182,server3是2183,dataDir和dataLogDir也需要区分下;
最后几行唯一需要注意的地方就是 server.X 这个数字就是对应 data/myid中的数字。你在3个server的myid文件中分别写入了1,2,3,那么每个server中的zoo.cfg都配 server.1,server.2,server.3就OK了。因为在同一台机器上,后面连着的2个端口3个server都不要一样,否则端口冲突,其 中第一个端口用来集群成员的信息交换,第二个端口是在leader挂掉时专门用来进行选举leader所用。进入zookeeper-3.3.2/bin 目录中,./zkServer.sh start启动一个server,这时会报大量错误?
其实没什么关系,因为现在集群只起了1台server,zookeeper服务器端起来会根据 zoo.cfg的服务器列表发起选举leader的请求,因为连不上其他机器而报错,那么当我们起第二个zookeeper实例后,leader将会被选 出,从而一致性服务开始可以使用,这是因为3台机器只要有2台可用就可以选出leader并且对外提供服务(2n+1台机器,可以容n台机器挂掉)。
3.5 Zookeeper的数据模型
- 层次化的目录结构,命名符合常规文件系统规范。
- 每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识。
- 节点Znode可以包含数据和子节点,但是Ephemeral类型的节点不能有子节点。
- Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本。
- 客户端应用可以在节点上设置监视器。
- 节点不支持部分读写,而是一次性完整读写。
-znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等。。。
3.6 Zookeeper的数据节点(Znode)
Zookeeper的结构其实就是一个树形结构,leader就相当于其中的根结点,其他节点就相当于Follow节点,每个节点都保留自己的内容。
Znode有两种类型,短暂的(ephemeral)和持久的(persistent),Znode的类型在创建时确定并且之后不能再修改。
短暂znode的客户端会话结束时,zookeeper会将该短暂znode删除,短暂znode不可以有子节点。
持久znode不依赖于客户端会话,只有当客户端明确要删除该持久znode时才会被删除。
Znode有四种形式的目录节点,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL
znode 可以是临时节点,一旦创建这个 znode 的客户端与服务器失去联系,这个 znode 也将自动删除,Zookeeper 的客户端和服务器通信采用长连接方式,每个客户端和 服务器通过心跳来保持连接,这个连接状态称为 session,如果 znode 是临时节点,这个 session 失效,znode 也就删除了;持久化目录节点,这个目录节点存储的数据不会丢失;顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;临时自动编号节点
3.7 Zookeeper的角色
领导者(leader),负责进行投票的发起和决议,更新系统状态。·
学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并响应客户端返回结果,在选主过程中参与投票。
Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程。只同步leader的状态,observer的目的是为了扩展系统,提高读取速度。
客户端(client),请求发起方。
3.8 Zookeeper的顺序号
创建znode时设置顺序标识,znode名称后会附加一个值。
顺序号是一个单调递增的计数器,有父节点维护。
在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序。
3.9 Zookeeper的读写机制
- Zookeeper是一个由多个server组成的集群。
- 一个leader,多个follower。
- 每个server保存一份数据副本。
- 全局数据一致。
- 分布式读写。
- 更新请求转发,由leader实施。
3.10 Zookeeper的保证
- 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行。
- 数据更新原子性,一次数据更新要么成功,要么失败。
- 全局唯一数据视图,client无论连接到哪个server,数据视图都是一致的。
- 实时性,在一定事件范围内,client能读到最新数据。
不存在部分数据写入成功或失败情形。
3.11 Zookeeper的API接口
- String create(String path, byte[] data, List<ACL> acl, CreateMode
- Stat exists(String
- void delete(String
- List<String> getChildren(String
- Stat setData(String
- byte[] getData(String path, boolean watch, Stat
- void addAuthInfo(String
- Stat setACL(String path, List<ACL> acl, int version)
- List<ACL> getACL(String path, Stat
- 创建一个给定目录节点的path,并给它设置数据。
- 判断某个path是否存在,并设置是否监控这个目录节点,这里的watcher实在创建Zookeeper实例时指定的watcher,exists方法还有一个重载方法,可以指定特定的watcher。重载方法,这个给某个节点设置特定的watcher。
- 删除path对应的目录节点,version为-1可以匹配任何版本,也就删除了这个目录节点所有数据。
- 获取指定path下的子目录节点,同样getChildren方法也有一个重载方法可以设置特定的watcher监控子节点的状态。
- 给 path 设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本。
- 获取这个 path 对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态。
- 客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。
- 给某个目录节点重新设置访问权限,需要注意的是 Zookeeper 中的目录节点权限不具有传递性,父目录节点的权限不能传递给子目录节点。目录节点 ACL 由两部分组成:perms 和 id。Perms 有 ALL、READ、WRITE、CREATE、DELETE、ADMIN 几种 而 id 标识了访问目录节点的身份列表,默认情况下有以下两种:ANYONE_ID_UNSAFE = new Id(“world”, “anyone”) 和 AUTH_IDS = new Id(“auth”, “”) 分别表示任何人都可以访问和创建者拥有访问权限。
- 获取某个目录节点的访问权限列表
3.12 观察(Watcher)
Watcher在Zookeeper是一个核心功能,Watcher可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相对应的反应。
可以设置观察的操作:exists,getChildren,getData
可以触发观察的操作:create,delete,setData
-znode以某种方式发生变化时,观察(watcher)机制可以让客户端得到通知,可以针对Zookeeper服务的操作来设置观察,该服务的其它操作可以触发观察。比如,客户端可以对某个客户端调用exists操作,同时在它上面设置一个观察,如果此时这个znode不存在,则false,如果一段时间后,这个znode被其它客户端创建,则这个观察会被触发,之前的那个客户端就会得到通知。
3.13 对应关系
3.13.1 写操作与Zookeeper内部事件的对应关系:
3.13.2 Zookeeper内部事件与watcher的对应关系:
3.13.3 写操作与watcher的对应关系:
如果发生session close、authFail和invalid,那么所有类型的watcher都会被触发。
3.14 ACL
每个znode被创建时都会带一个ACL列表,用于决定谁可以对它执行何种操作。
NodeCreated:节点创建事件
NodeDeleted:节点被删除事件
NodeDataChanged:节点数据改变事件
NodeChildrenChanged:节点的子节点改变事件
身份验证模式有三种:
digest:用户名,密码。
host:通过客户端的主机来识别客户端。
ip:通过客户端的ip来识别客户端。
new ACL(Perms.READ,new Id(“host”,”example.com”))。
这个ACL对应的身份验证模式是host,符合该模式的身份是example.com,权限的组合是:READ。
每个ACL都是身份验证模式,符合该模式的一个身份和一组权限的组合。
3.15 Zookeeper的工作原理
Zookeeper的核心是原子广播,这个机制保证了各个server之间的同步。实现这个机制的协议叫做Zab协议。
Zab协议有两种模式,它们分别是恢复模式和广播模式。当服务启动在领导者崩溃后,Zab就进入恢复模式,当领导者被选举出来后,而且大多数server完成了和leader的状态同步之后,恢复模式就结束了。状态同步保证了leader和server具有相同的系统状态。
一旦leader已经和多数的follower进行了状态同步后,他就可以开始广播消息了,即进入广播状态。这时候当一个server加入zookeeper服务中,它会在恢复模式下启动,发现leader,并和leader进行状态同步。待到同步结束,它也参与消息广播。Zookeeper服务一直维持在Broadcast状态,知道leader崩溃了或者leader失去了大部分的followers支持。
Broadcast模式极其类似于分布式事务中的2pc(two-phrase commit两阶段提交):即leader提起一个决议,由followers进行投票,leader对投票结果进行计算决定是否通过该协议,如果通过执行决议(事务),否则什么也不做。
广播模式需要保证proposal被按顺序处理,因此zk采用了递增的事务id号(zxid)来保证。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是一个递增计数。
当leader崩溃或者ledaer失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的server都恢复到一个正确的状态。
3.16 Leader选举
- 每个Server启动以后都询问其它的server它要投票给谁。
- 对于其它server的询问,server每次根据自己的状态都回复自己推荐的leader的id和上一次处理事务的zxid(系统启动时,每个server都会推举自己)
- 收到所有server回复以后,就计算出zxid最大的哪个server,并将这个server相关信息设置成下一个要投票的server。
- 计算这过程中获得票数最多的server为获胜者,如果获胜者的票数,超过半数,则改server被选为leader。否则,继续这个过程,直到leader被选举出来。
- leader就会开始等待server连接。
- Follower连接leader,将最大的zxid发送给leader。
- Leader根据follower的zxid确定同步点。
- 完成同步后通知follower 已经成为uptodate状态。
- Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
- 首先看一下选举的过程,zk的实现中用了基于paxos算法(主要是fastpaxos)的实现。具体如下;此外恢复模式下,如果是重新刚从崩溃状态恢复的或者刚启动的的server还会从磁盘快照中恢复数据和会话信息。(zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复)
- 选完leader以后,zk就进入状态同步过程。
- Observing: 观察状态,这时候observer会观察leader是否有改变,然后同步leader的状态;Following: 跟随状态,接收leader的proposal ,进行投票。并和leader进行状态同步
- Looking: 寻找状态,这个状态不知道谁是leader,会发起leader选举;Leading: 领导状态,对Follower的投票进行决议,将状态和follower进行同步
3.17 示例代码
- 输出的结果如下:
- 已经触发了 None 事件!
- testRootData [testChildPathOne]
- 目录节点状态:[5,5,1281804532336,1281804532336,0,1,0,0,12,1,6]
- 已经触发了 NodeChildrenChanged 事件!
- testChildDataTwo
- 已经触发了 NodeDeleted 事件!
- 已经触发了 NodeDeleted 事件!
//连接zookeeper
package org.my.MyZookeepers;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
public class Query
{
private static String connectString="Slave2:2181";
private static int sessionTimeout=999999;
public static void main(String[] args) throws Exception{
Watcher watcher=new Watcher(){
public void process(WatchedEvent event) {
System.out.println("监听到的事件:"+event);
}
};
final ZooKeeper zookeeper=new ZooKeeper(connectString,sessionTimeout,watcher);
System.out.println("获得连接:"+zookeeper);
final byte[] data=zookeeper.getData("/hbase/hbaseid", watcher, null);
System.out.println("读取的值:"+new String(data));
zookeeper.close();
}
}
//读取zookeeper的配置,并测试
package org.my.MyZookeepers;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
/**
* /**
* 在Zookeeper中读取配置
* @author kenneth
*
*/
public class MyClient implements Watcher{
public static String url = "Slave2:2181";
private final static String root = "/conf";
private final static String UrlNode = root+"/url";
private final static String UserNameNode =root+"/username";
private final static String PasswordNode = root+"/password";
private final static String auth_type = "digest";
private final static String auth_passwd = "username:password";
private static String DBURL ;
private static String DBUserName ;
private static String DBPassword;
ZooKeeper zk =null;
public void initValue(){
try{
DBURL=new String(zk.getData(UrlNode,true,null));
DBUserName=new String(zk.getData(UserNameNode,true,null));
DBPassword=new String(zk.getData(PasswordNode,true,null));
} catch (Exception e){
e.printStackTrace();
}
}
public ZooKeeper getZK() throws Exception{
zk = new ZooKeeper(url,3000,this);
while(zk.getState() != ZooKeeper.States.CONNECTED ){
Thread.sleep(3000);
}
zk.addAuthInfo(auth_type, auth_passwd.getBytes());
return zk;
}
public static void main(String[] args) throws Exception {
MyClient zkTest = new MyClient();
ZooKeeper zk = zkTest.getZK();
zkTest.initValue();
int i = 0;
while(true)
{
System.out.println(zkTest.getDBURL());
System.out.println(zkTest.getDBUserName());
System.out.println(zkTest.getDBPassword());
System.out.println("-------------------------------------");
Thread.sleep(10000);
i++;
if(i==5){
break;
}
}
zk.close();
}
public void process(WatchedEvent event){
if(event.getType()==Watcher.Event.EventType.None){
System.out.println("连接服务器成功");
} else if(event.getType()==Watcher.Event.EventType.NodeCreated){
System.out.println("节点创建成功");
} else if(event.getType()==Watcher.Event.EventType.NodeChildrenChanged){
System.out.println("子节点更新成功");
//读取新的配置
initValue();
}else if(event.getType()==Watcher.Event.EventType.NodeDataChanged){
System.out.println("节点更新成功");
//读取新的配置
initValue();
}else if(event.getType()==Watcher.Event.EventType.NodeDeleted){
System.out.println("节点删除成功");
}
}
public String getDBPassword(){
return DBPassword;
}
public void setDBPassword(String dbapssword){
DBPassword = dbapssword ;
}
public String getDBURL(){
return DBURL;
}
public void setDBURL(String dburl){
DBURL = dburl ;
}
public String getDBUserName(){
return DBUserName;
}
public void setDBUserName(String dbusername){
DBUserName = dbusername ;
}
}
//设置zookeeper配置文件
package org.my.MyZookeepers;
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;
/**
* 在Zookeeper中保存配置
* @author kenneth
*
*/
public class SetConfig {
public static String url = "Slave2:2181";
private final static String root = "/conf";
//数据库配置项
private final static String UrlNode = root+"/url";
private final static String UserNameNode =root+"/username";
private final static String PasswordNode = root+"/password";
//配置值
private final static String DBURL ="192.168.171.120";
private final static String DBUserName = "admin";
private final static String DBPassword = "admin";
private final static String auth_type = "digest";
private final static String auth_passwd = "username:password";
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper(url,3000,new Watcher(){
public void process(WatchedEvent event){
System.out.println("触发了事件:"+event.getType());
}
});
while(ZooKeeper.States.CONNECTED != zk.getState()){
Thread.sleep(3000);
}
zk.addAuthInfo(auth_type, auth_passwd.getBytes());
if(zk.exists(root,true)==null){
zk.create(root, "root".getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
}
if(zk.exists(UrlNode,true)==null){
zk.create(UrlNode, DBURL.getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
}
if(zk.exists(UserNameNode,true)==null){
zk.create(UserNameNode, DBUserName.getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
}
if(zk.exists(PasswordNode,true)==null){
zk.create(PasswordNode, DBPassword.getBytes(),Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
}
zk.close();
}
}
//ls /
//ls /conf
//help
//addauth digest username:password /conf
//ls /conf
//get /conf/username
//get /conf/password
//get /conf/url
3.18 应用场景
3.18.1 统一命名服务
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于人识别和记住,通常情况下用树形的名称结构是一个理想的选择,树形的名称结构是一个有层次的目录结构,即对人友好又不会重复。
Name Service是Zookeeper内置的功能,只要调用Zookeeper的API就能实现。
3.18.2 配置管理
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多台PC Server运行,但是他们运行的时候应用系统的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每台运行这个应用系统的PC Server,这样非常麻烦而且容易出错。
将配置信息保存在Zookeeper的某个目录节点中,然后将所有需要修改的配置信息发生变化,每台应用机器就会收到Zookeeper的通知,然后从Zookeeper获取新的配置信息应用到系统中。
Zookeeper很容易实现这种集中式的配置管理,比如将APP1的所有配置配置到/APP1 znode下,APP1所有机器一启动就对/APP1这个节点进行监控(zk.exist(“/APP1″,true)),并且实现回调方法 Watcher,那么在zookeeper上/APP1 znode节点下数据发生变化的时候,每个机器都会收到通知,Watcher方法将会被执行,那么应用再取下数据即可 (zk.getData(“/APP1″,false,null));
3.18.3集群管理
Zookeeper 能够很容易的实现集群管理的功能,如有多台 Server 组成一个服务集群,那么必须要一个“总管”知道当前集群中每台机器的服务状态,一旦有机器不能提供服务,集群中其它集群必须知道,从而做出调整重新分配服务策略。同样当增加集群的服务能力时,就会增加一台或多台 Server,同样也必须让“总管”知道。
Zookeeper 不仅能够维护当前的集群中机器的服务状态,而且能够选出一个“总管”,让这个总管来管理集群,这就是 Zookeeper 的另一个功能 Leader Election。
- 应用集群中,我们常常需要让每一个机器知道集群中(或依赖的其他某一个集群)哪些机器是活着的,并且在集群机器因为宕机,网络断链等原因能够不在人工介入的情况下迅速通知到每一个机器。Zookeeper同样很容易实现这个功能,比如我在zookeeper服务器端有一个znode叫/APP1SERVERS,那么集群中每一个机器启动 的时候都去这个节点下创建一个EPHEMERAL类型的节点,比如server1创建/APP1SERVERS/SERVER1(可以使用ip,保证不重 复),server2创建/APP1SERVERS/SERVER2,然后SERVER1和SERVER2都watch /APP1SERVERS这个父节点,那么也就是这个父节点下数据或者子节点变化都会通知对该节点进行watch的客户端。因为EPHEMERAL类型节 点有一个很重要的特性,就是客户端和服务器端连接断掉或者session过期就会使节点消失,那么在某一个机器挂掉或者断链的时候,其对应的节点就会消 失,然后集群中所有对/APP1SERVERS进行watch的客户端都会收到通知,然后取得最新列表即可。
Zookeeper 如何实现 Leader Election,也就是选出一个 Master Server;另外有一个应用场景就是集群选master,一旦master挂掉能够马上能从slave中选出一个master,实现步骤和前者一样,只是机器在启动的 时候在APP1SERVERS创建的节点类型变为EPHEMERAL_SEQUENTIAL类型,这样每个节点会自动被编号
- zk.create("/testRootPath/testChildPath1","1".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
- zk.create(“/testRootPath/testChildPath2”,“2”.getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
- zk.create("/testRootPath/testChildPath3","3".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
- zk.create("/testRootPath/testChildPath4","4".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
- System.out.println(zk.getChildren("/testRootPath", false));
- 打印结果:[testChildPath10000000000, testChildPath20000000001, testChildPath40000000003, testChildPath30000000002]
规定编号最小的为master,所以当我们对SERVERS节点做监控的时候,得到服务器列表,只要所有集群机器逻辑认为最小编号节点为master,那么master就被选出,而这个master宕机的时候,相应的znode会消失,然后新的服务器列表就被推送到客户端,然后每个节点逻辑认为最小编号节点为master,这样就做到动态master选举。
3.18.4共享锁
共享锁在同一个进程中很容易实现,但是在跨进程或者在不同 Server 之间就不好实现了。Zookeeper 却很容易实现这个功能,实现方式也是需要获得锁的 Server 创建一个 EPHEMERAL_SEQUENTIAL 目录节点,然后调用 getChildren方法获取当前的目录节点列表中最小的目录节点是不是就是自己创建的目录节点,如果正是自己创建的,那么它就获得了这个锁,如果不是那么它就调用 exists(String path, boolean watch) 方法并监控 Zookeeper 上目录节点列表的变化,一直到自己创建的节点是列表中最小编号的目录节点,从而获得锁,释放锁很简单,只要删除前面它自己所创建的目录节点就行了。
3.18.5队列管理
Zookeeper 可以处理两种类型的队列:当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列;队列按照 FIFO 方式进行入队和出队操作,例如实现生产者和消费者模型
创建一个父目录 /synchronizing,每个成员都监控目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列(创建 /synchronizing/member_i 的临时目录节点),然后每个成员获取 / synchronizing 目录的所有目录节点,判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建 /synchronizing/start。