目录

Zookeeper

Apache Zookeeper is an effort to develop and maintain an open-source server which enables highly reliable distributed coordination.

ZooKeeper实现了一个层次命名空间的数据模型,也可以认为它就是一个小型的、精简的文件系统。它的每个节点称为znode, znode除了本身能够包含一部分数据之外,还能够拥有子节点,当节点上的数据发生变化,或者其子节点发生变化,基于watcher机制,会发出相应的通知给订阅其状态变化的客户端。

Zookeeper启动失败,可以在./zookeeper.out文件查看异常日志

每台机器的myid中内容为服务的唯一标识,不能重复

确保每台机器必须能ping通,必要时请先关闭防火墙

在阿里云服务器上,请使用内网ip进行配置

确保使用的端口未被占用

Zookeeper集群机器数量只能是奇数个

集群

集群中服务器的状态

Zookeeper集群中服务器被划分为以下四种状态

  • LOOKING:寻找Leader状态。处于该状态的服务器会认为集群中没有Leader,需要进行Leader选举;
  • FOLLOWING:跟随着状态,说明当前服务器角色为Follower;
  • LEADING:领导者状态,表明当前服务器角色为Leader;
  • OBSERVING:观察者状态,表明当前服务器角色为Observer。

选票信息

每个选票中包含四个最基本的信息,服务器的ID,数据ID,逻辑时钟以及选举状态:

  • 服务器ID:即myId,服务器的唯一标识,编号越大在选择算法中的权重越大;
  • Zxid:事务 ID,服务器中存放的数据的事务 ID,值越大说明数据越新,在选举算法中数据越新权重越大。在 ZooKeeper 中通常是以事务 id(后面简称 zxid)来标识数据的新旧程度(版本),节点最新的 zxid 越大代表这个节点的数据越新,也就代表这个节点能力越强。zxid 的全称是 ZooKeeper Transaction Id,即 ZooKeeper 事务id。
  • 逻辑时钟 Epoch:Epoch,逻辑时钟,或者叫投票次数,同一轮投票过程时钟值是相同的,每投完一次票这个数据就会增加,然后与接收到的其他服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断;
  • 选举状态:即上文提到的四种状态。

Leader选举的触发时机

  • 集群启动,这个时候需要选举出新的Leader;
  • Leader服务器宕机;
  • Follow服务器宕机后,Leader服务器发现自己已经没有过半的Follow跟随自己了,不能对外提供服务(领导者选举)。

Leader 的选举

  • 服务器初始状态
  • 运行状态下集群 Leader 节点故障

运行期选举与初始状态投票过程基本类似,大致可以分为以下几个步骤:

  • 状态变更。Leader 故障后,余下的非 Observer 服务器都会将自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举过程。
  • 每个 Server 会发出投票。
  • 接收来自各个服务器的投票,如果其他服务器的数据比自己的新会改投票。
  • 处理和统计投票,每一轮投票结束后都会统计投票,超过半数即可当选。
  • 改变服务器的状态,宣布当选。

在运行期选举还可能会遇到脑裂

配置

然后我们来看看这个配置文件里面都有些啥

tickTime=2000
  • 服务器之间或客户端与服务端之间维持心跳的时间。就是每隔tickTime就会发送一次心跳。单位毫秒
initLimit=101
  • 这个是配置Zookeeper接收客户端初始化连接最长能忍受initLimit心跳时间间隔。
syncLimit=51
  • Leader与follow之间发送消息和应答的时间 总时间=syncLimit*tickTime
dataDir
  • zookeeper数据保持路径 默认将log日志也保存在dataDir
dataLogDir
  • zookeeper log日志保存地址 不设置默认是dataDir
clientPort=2181
  • 客户端连接的端口
server.1=192.168.1.130:28881:38881
server.2=192.168.1.130:28882:38882
server.3=192.168.1.130:28883:38883
  • 因为我的集群都是在同一台电脑上配置的,所以这里端口不能一样

Client

首先,实例化一个ZooKeeper对象,指定其三个参数。url为ZooKeeper服务器的地址。sessionTimeOut为会话的超时时间,ZooKeeper的会话超时时间的长度由客户端来确定,但是ZooKeeper的Server端会有两个配置,minSessionTimeout和maxSessionTimeout,minSessionTimeout的值默认为2倍的tickTime, maxSessionTimeout的值默认为20倍的tickTime,单位都是ms. tickTime也是服务端的一个配置项,是Server内部控制时间逻辑的最小时间单位,如果客户端发来的sessionTimeout超过minSessionTimeout~maxSessionTimeout这个范围,Server会自动取minSessionTimeout或者maxSessionTimeout作为sessionTimeout, 然后为这个Client新建一个session对象。最后一个参数为默认的watcher.如果包含boolean watch的读方法中传入true, 则将默认的watcher注册为所关注时间的watcher,如果传入false,则不注册任何watcher,这里暂且定为空。

1
2
ZooKeeper zooKeeper = new ZooKeeper(url,sesssionTimeout,null);
//注意将安装目录下的zookeeper的jar包拷贝出来导入你的project的libs中;zookeeper的默认端口号为21811

1 创建节点

通过ZooKeeper的API新增一个znode节点,节点在被创建时,需要制定节点的路径(此处为/root)包含的字节数据,访问权限(如果不想设置,则制定为Ids.OPEN_ACL_UNSAFE),以及创建的节点类型,节点的类型如表所示:

节点类型 解释
CreateMode.PRESISTENT 持久节点,该节点在客户端断开连接后不会删除
CreateMode.EPHEMERAL 临时节点,该节点在客户端断开连接后删除
CreateMode.PERSISTENT_SEQUENTIAL 持久节点,该节点在客户端断开后不会删除,并将在其名下附加一个单调递增数
CreateMode.EPHEMERAL_SEQUENTIAL 临时节点,该节点在客户端断开后删除,并将在其名下附加一个单调递增数

创建节点实例:

1
2
//创建/root节点,其包含的数据为“root data",访问权限为开放,所有人均可以访问,创建模式为持久化节点
zooKeeper.create("/root","root data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

2 删除节点

当不需要某个节点,或者某个节点上的信息已经失效时,使用delete方法可以将该节点删除,删除时需要制定节点的版本号version,如果设置为-1,则匹配所有的版本,ZooKeeper会比较删除的节点版本是否和服务器上的版本一致,如果不一致则抛出异常。

删除节点实例:

1
zooKeeper.delete("/root",-1);

3 设置和获取节点内容

如果想在已有节点中保存数据,可以通过ZooKeeper的setData方法,将数据保存到节点上,也可以通过ZooKeeper的getData方法来获取该节点保存的数据,一个znode中最多能够保存1MB的数据。 设置和获取节点内容的实例。

1
2
3
4
5
//设置/root节点的数据,版本号为-1,如果匹配不到相应的节点,会抛出异常
zooKeeper.setData("/root","hello".getBytes(),-1);
//取得/root节点的数据,并反回其stat
Stat stat = new Stat();
byte[] data = zooKeeper.getData("/root",false,stat);12345

setData方法设置/root节点的数据为“hello”,getData方法取得root节点上保存的节点数据,false表示不使用默认的watcher,第三个参数为Stat, 表示节点的状态,是一个传出的参数,将会返回该节点当前的状态信息。

4 添加子节点

ZooKeeper支持在已有的节点下添加子节点,同样也是用使用create()方法,但是父节点必须存在,否则会跑出异常。 创建子节点的实例:

1
2
zooKeeper.create("/root","root data".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
zooKeeper.create("/root/child","child data".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

5 判断节点是否存在

当进行系统初始化时,或者当前需要给一个节点创建子节点时,通常需要判断系统中的一些节点是否存在。

1
2
3
4
5
6
Stat stat = zooKeeper.exists("/root/child1",false);
if(stat==null){
System.out.println("节点不存在");
}else{
System.out.println("节点存在");
}

判断/root/child1节点是否存在,如果存在,返回stat不为null,否则为null.

6 watcher的实现

当节点的状态发生变化,通过watcher机制,可以让客户端得到通知,watcher需要实现org.apache.ZooKeeper.Watcher接口。节点的状态变化主要包含如表所示的几种情况。

节点状态变化 解释
EventType.NodeDeleted 删除节点
EventType.NodeChildrenChanged 修改节点的子节点
EventType.NodeCreated 创建节点
EventType.NodeDataChanged 修改节点数据

watcher的实现实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ZKWatcher implements Watcher{
@Override public void process(WacthedEvent event){
if(event.getType()==EventType.NodeDeleted)
//del
if(event.getType()==EventType.NodeChildrenChanged )
//del
if(event.getType()==EventType.NodeCreated)
//del
if(event.getType()==EventType.NodeDataChanged  )
//del
}
}

需要注意的是,ZooKeeper的watcher是一次性的,也就是说,每次在处理完状态变化时间之后,需要重新注册watcher,这一点很让人抓狂。这个特性也使得在处理时间和重新加上watcher这段时间发生的节点状态变化将无法被感知。

异常

ZooKeeer常常发生下面两种系统异常:

  • org.apache.ZooKeeper.KeeperException.ConnectionLossException,客户端与其中的一台服务器socket连接出现异常,连接丢失;
  • org.apache.ZooKeeper.KeeperException.SessionExpiredException, 客户端的session已经超过sessionTimeout,未进行任何操作。

ConnectionLossException异常可以通过重试进行处理,客户端会根据初始化ZooKeeper时传递的服务列表,自动尝试下一个服务端节点,而在这段时间内,服务端节点变更的事件就会丢失。 SessionExpiredException异常不能通过重试解决,需要应用重新创建一个新的客户端(new ZooKeeper()),这时所有的watcher和EPHEMERAL节点都将失效。 一般情况下,不采用原生态的API进行客户端操作,而是采用zkClient或者Curator进行操作。

原因:

  1. 网络不通,端口不可用等;
  2. zookeeper服务程序未正常启动;
  3. 防火墙未关闭;