概述
目录
- Zookeeper 概述
- Zookeeper 的特点
- Zookeeper 的应用场景
- Zookeeper的数据结构
- ZNode数据类型
- ZNode里面存储的信息
- Zookeeper的选举机制(重要)
- Zookeeper第一次启动选举机制
- Zookeeper非第一次启动选举机制
- Zookeeper 底层如何按照请求的先后顺序来处理的
- Zookeeper 实现分布式锁
- Zookeeper 实现分布式锁使用Java 原生API
- Zookeeper 实现分布式锁使用Curator 架实现Zookeeper分布式锁
- 生产环境下,Zookeeper安装多少台服务器合适
Zookeeper 概述
什么是Zookeeper?
Zookeeper是一个开源的分布式的,为其他分布式监控提供协调服务的Apache项目。
Zookeeper 的特点
Zookeeper 有哪些特点?
-
Zookeeper是由一个Leader,和多个Follower组成的集群。
-
Zookeeper集群中只有半数以上的节点存活,Zookeeper集群才能正常提供服务。
-
Zookeeper集群全局数据统一,每个Zookeeper Server保存一份相同的数据副本,Client无论连接哪一台Server,数据都是一致的。
-
由于有事务的存在,每次写操作都有事务id(zxid),所以数据更新具有原子性,要么成功,要么失败。
Zookeeper 的应用场景
1、提供记录IP地址的域名服务。
如我们可以将www.baidu.com作为父节点,下面的子节点为真实的IP地址,便于标识。
2、统一配置管理。
可将配置信息写入Zookeeper的一个ZNode中,然后各个客户端服务器监听这个ZNode,一旦这个ZNode中的数据发生变化,Zookeeper将通知各个客户端服务器。
3、统一集群管理。Zookeeper可以实现监控集群中各节点的状态变化。
可将集群节点的信息写入Zookeeper的一个ZNode中,然后监听这个ZNode可获得它的实时状态变化。
4、服务器动态上下线。客户端能实时洞察服务器上下线的变化。
5、软负载均衡。
让Zookeeper记录每台服务器的访问数,让访问最好的服务器去处理最小的客户端请求。
Zookeeper的数据结构
Zookeeper的数据结构与文件系统很类似,属于树形结构。
整体可以看做是一颗树,每个节点称为一个ZNode,每个ZNode都可以通过其路径唯一标识,如/znode1/leaf1,就可以找到根目录/下的/znode1下的leaf1。每个ZNode能存储的数据量非常小,大约能够存储1MB的数据,只能用来存储一些简单的配置信息,不能存储海量数据。
ZNode数据类型
ZNode包括四种不同的类型,分别是 持久的、持久有序的、短暂的、短暂有序的。其中持久和短暂是指
1)持久:客户端与服务器断开连接之后,创建的节点不删除。
2)短暂:客户端与服务器断开连接之后,创建的节点被删除。
3)有序:创建有序的ZNode时,ZNode的名称后会附加一个顺序号,顺序号是一个单调递增的计数器,由父节点维护。
ZNode里面存储的信息
ZNode里面包含了存储的数据(data)、访问权限(acl)、子节点引用(child)和节点状态信息(stat)。
其中
访问权限acl是指记录客户端对ZNode节点的访问权限。
子节点引用child是指当前节点的子节点引用。
节点状态信息stat包含Znode节点的状态信息,比如事务id,版本号,时间戳等。
Zookeeper的选举机制(重要)
关于Zookeeper的选举机制,我们先要记住它的半数以上选举机制,而且我们还需要看看是第一次启动还是非第一次启动。
Zookeeper第一次启动选举机制
假设我们的Zookeeper集群有4个节点。而刚开始我们的Zookeeper服务都是关闭的。
(1)节点1启动,会发起一次选举。节点1会投自己一票,不够半数以上(3票),选举无法完成,节点1状态保持为LOOKING状态;
(2)节点2启动,再次发起一次选举。节点1和节点2分别投自己一票然后交换选品信息,此时节点1发现节点2的myid比自己大,所以更改选票为节点2,不够半数以上(3票),选举无法完成,节点1和节点2状态保持为LOOKING状态;
(3)节点3启动,发起一次选举。过程和节点2发起选票一样,最后更改选票为节点3.此次投票结果:节点1和节点2为0票,节点3为3票,由于半数以上选举机制,节点3当选Leader,并更改状态为LEADING,节点1和节点2Follower,并且更改状态为FOLLOWING。
(4)节点4启动,发起一次选票。此时此时节点1/2/3已经半数LOOKING状态,不会更改选票信息。交换选票信息结果:节点3为3票,节点4为1票,此时节点4更改选票为节点3,并更改状态为FOLLOWING。
Zookeeper非第一次启动选举机制
当其中一台机器由于网络分区故障无法与集群和Leader保持连接时,该节点进入选举状态,但是由于半数以上选举机制,该节点无法成为Leader,只能不断与其他节点进行连接,在连接成功之后,集群可能会出现两种情况:
1、集群中本来就存在Leader。对于这种情况,该节点会被当中当前集群的Leader信息,对于该节点来说,只要与Leader重新进行连接并更新状态即可。
2、集群中确实不存在Leader。
这个情况下,我们需要了解几个核心参数:
(1)SID:服务器ID,服务器ID和配置中的myid一致。
(2)ZXID:事务ID,该ID和服务器对于客户端更新请求有关。
(3)Epoch:每个Leader任期的代号,没有Leader时这个在各节点中是相同的,每次投完一次票这个数据就会增加。
假设Zookeeper集群由4个节点组成,SID为1/2/3/4,ZXID为8887,此时节点3为Leader。某时刻,节点4和Leader都挂掉了。
那么只剩下节点1和节点2,他们的(Epoch,ZXID,SID)分别为(1,8,1)和(1,7,2)
非第一次启动并Leader不存在的Leader选举机制为:
(1)Epoch大的胜出
(2)Epoch相同,ZXID大的胜出
(3)ZXID相同,SID大的胜出
综上,节点1当选Leader。
Zookeeper 底层如何按照请求的先后顺序来处理的
Leader收到请求之后,会给每个请求分配一个全局唯一的递增的ZXID,如何把请求放入一个FIFO队列里面,之后就按照FIFO的策略发送给所有的Follower。
Zookeeper 实现分布式锁
分布式锁的概述、由单体锁引申到分布式锁具体见我另一篇博客:分布式锁的实现
分布式锁一般步骤:多个进程抢占同一个共享资源,先抢到互斥锁的,可以继续执行相应的代码,其他进程阻塞在外面,只有等待先抢占到锁的进程操作完之后释放锁(一般是delete),然后其他进程才可以重试抢占。
具体Zookeeper实现分布式锁的过程。
1)多个ZK客户端申请在ZK集群中创建临时顺序节点 (create -e -s /locks/seq-)
2)然后创建完临时节点之后,判断自己是不是当前节点下的最小节点:
(1)是,获取到锁,可以进行执行相应代码。
(2)不是,对前一个节点进行监听(watch)。
3)获取到锁的进程处理完业务之后,delete节点释放锁,然后下面的节点会收到通知,重复步骤(2)。
这就是Zookeeper的大致的实现细节。我们给别人说,说上面的就行了。
但是具体的实现,还需要两个JUC下的减法器,CountDownLatch
一个是为了保证连接的健壮性而设置的,获取到连接就继续让它执行代码。
一个是监听上一个节点发生变化来使用的。
具体细姐看代码:
Zookeeper 实现分布式锁使用Java 原生API
public class DistributedLock {
private final String connectString = "VM102:2181,VM103:2181,VM104:2181";
private final int sessionTimeout = 2000;
private final ZooKeeper zk;
private CountDownLatch connectLatch = new CountDownLatch(1);
private CountDownLatch waitLatch = new CountDownLatch(1);
private String waitPath;
private String currentMode;
public DistributedLock() throws IOException, InterruptedException, KeeperException {
// 获取连接
zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
// connectLatch 如果连接上zk 可以释放
if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
connectLatch.countDown();
}
// waitLatch 需要释放
if (watchedEvent.getType()== Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)){
waitLatch.countDown();
}
}
});
// 等待zk正常连接后,往下走程序
connectLatch.await();
// 判断根节点/locks是否存在
Stat stat = zk.exists("/locks", false);
if (stat == null) {
// 创建一下根节点
zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 对zk加锁
public void zklock() {
// 创建对应的临时带序号节点
try {
currentMode = zk.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// wait一小会, 让结果更清晰一些
Thread.sleep(10);
// 判断创建的节点是否是最小的序号节点,如果是获取到锁;如果不是,监听他序号前一个节点
List<String> children = zk.getChildren("/locks", false);
// 如果children 只有一个值,那就直接获取锁; 如果有多个节点,需要判断,谁最小
if (children.size() == 1) {
return;
} else {
Collections.sort(children);
// 获取节点名称 seq-00000000
String thisNode = currentMode.substring("/locks/".length());
// 通过seq-00000000获取该节点在children集合的位置
int index = children.indexOf(thisNode);
// 判断
if (index == -1) {
System.out.println("数据异常");
} else if (index == 0) {
// 就一个节点,可以获取锁了
return;
} else {
// 需要监听 他前一个节点变化
waitPath = "/locks/" + children.get(index - 1);
zk.getData(waitPath,true,new Stat());
// 等待监听
waitLatch.await();
return;
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 解锁
public void unZkLock() {
// 删除节点
try {
zk.delete(this.currentMode,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
使用Java原生API开发Zookeeper会出现的问题:
(1)会话连接是异步的,需要自己去处理,比如使用CountDownLatch。
(2)Watch 需要重复注册,不然不会生效。
(3)开发复杂性较高。
真正在生产环境下,我们不会使用Java 原生API的方式来实现Zookeeper分布式锁,我们会使用Curator 框架实现Zookeeper分布式锁。实现起来非常简单!
Zookeeper 实现分布式锁使用Curator 架实现Zookeeper分布式锁
导入依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.3.0</version>
</dependency>
public class CuratorLockTest {
public static void main(String[] args) {
// 创建分布式锁1
InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), "/locks");
// 创建分布式锁2
InterProcessMutex lock2 = new InterProcessMutex(getCuratorFramework(), "/locks");
new Thread(new Runnable() {
@Override
public void run() {
try {
lock1.acquire();
System.out.println("线程1 获取到锁");
lock1.acquire();
System.out.println("线程1 再次获取到锁");
Thread.sleep(5 * 1000);
lock1.release();
System.out.println("线程1 释放锁");
lock1.release();
System.out.println("线程1 再次释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock2.acquire();
System.out.println("线程2 获取到锁");
lock2.acquire();
System.out.println("线程2 再次获取到锁");
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("线程2 释放锁");
lock2.release();
System.out.println("线程2 再次释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private static CuratorFramework getCuratorFramework() {
ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("hadoop102:2181,hadoop103:2181,hadoop104:2181")
.connectionTimeoutMs(2000)
.sessionTimeoutMs(2000)
.retryPolicy(policy).build();
// 启动客户端
client.start();
System.out.println("zookeeper 启动成功");
return client;
}
}
生产环境下,Zookeeper安装多少台服务器合适
安装奇数台服务器合适。
这个问题我们来举个例子,加入我们安装了5台机器。1/2、3/4、5。
由于Zookeeper集群中只要有半数以上节点存活,Zookeeper集群就可以正常服务。
假设我们挂掉了1/2/3三台,剩下4/5。机器数2<3(半数以上),Zookeeper集群不能正常工作。
而如果我们再加一台机器6,并且1/2/3还是挂了,那么剩下4/5/6。机器数3<4(半数以上),所以集群还是不能正常工作。
刚才是5台服务器,挂3台,集群是瘫痪。而现在6台服务器,挂3台,集群还是瘫痪。所以说偶数台服务器每什么原因,不仅仅没有提高集群的可靠性,反而浪费了一台服务器,是集群间的通信延迟。
由于生产经验可以知道:
-
10台服务器:3台ZK
-
20台服务器:5台ZK
-
100台服务器:11台ZK
-
200台服务器:11台ZK
最后
以上就是自觉鼠标为你收集整理的Zookeeper的全部内容,希望文章能够帮你解决Zookeeper所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复