顾名思义,表示动物管理员。我们将多个服务器看成不同的动物,而Zookeeper就是一个管理员,用来观察这些动物的状态,而我们客户端每次在进行观赏动物之前,都要跟管理员交互,一旦管理员发现有死亡动物,便会通知想观赏该动物的客户。
当然这只是一个普通的比喻,实际上,ZK是由一个领导者和多个从者组成的集群。
1)在该集群中,如果存在半数或超过半数以上的节点死亡,该集群便不能工作。
2)集群全局数据是一致,即每个节点保存的数据都是一致的,但这不影响服务器的压力。
3)数据更新支持原子性,数据读取支持实时性,虽然有多个节点,但数据同步的事件很短。
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 官网下载 jdk-8u171-linux-x64.tar.gz 中间8u171可以不一致,但是要下载.tar.gz结尾的,表示压缩包。
将文件传到 /usr/local目录下。可以在该目录新建一个java文件夹,然后再java文件里再新建两个java和jdk文件夹,最后将压缩包转移到和jdk平级的java目录下。
连接xsheel工具,打开客户端,输入rpm -qa | grep java 命令查看是否安装过 jdk,若什么都没有显示则表示安装过。
若有显示,可能是系统自带的,可通过rpm -e xxx --nodeps xxx 表示jdk的文件名
通过cd ~ 返回根目录
然后通过cd /usr/local 进入local目录。然后cd java 进入java目录 ,cd jdk 进入带有jdk压缩包的目录。
通过 tar -xvf jdk-8u171-linux-x64.tar.gz解压文件
解压后,rm -rf jdk-8u171-linux-x64.tar.gz 删除该压缩包,最后ll看只剩下解压后的jdk
cd jdk1.8.0_171/ 进入jdk目录 pwd 显示该目录的全包名,复制,待会配置环境要用
yum -y install vim 命令来安装vim文本编辑器
vim /etc/profile 命令来编辑环境变量的配置文件,进入文件中,键盘向下的键可一直到达文件末端,然后点击键盘字母i进入输入模式,在文件最后加上以下变量
JAVA_HOME=/usr/local/java/jdk1.8.0_171 # 刚才复制的路径
CLASSPATH=.:$JAVA_HOME/lib.tools.jar
PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME CLASSPATH PATH
修改完成后,点击esc退出编辑模式,然后英文模式下输入:wq 可退出该文件。
退出文件编辑后,source /etc/profile 命令使更改的配置立即生效
最后 java -version 命令和 javac -version 命令来查看 jdk 是否安装成功
ZK 的学习环境一般要给三个客户端,所以开通三个客户端都要安装上jdk环境。
官网下载apache-zookeeper-3.5.7-bin.tar.gz,然后最好在linux的opt目录下新建一个software目录,然后将该压缩包转移到该目录下。
然后登录xsheel客户端,进入该目录,输入 tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz -C /opt/model 该为解压命令,后者为解压到某个目录。
解压完毕后 cd /opt 进入opt目录,cd model 进入model目录,ls查看是否有加压后的bin包。
输入mv apache-zookeeper-3.5.7-bin/ zookeeper-3.5.7-bin 修改文件名
cd zookeeper-3.5.7-bin/ 进入该目录,cd conf进入该目录进行配置
mv zoo_sample.cfg zoo.cfg 将该conf目录中该文件修改名字
vim zoo.cfg 进入该文件中
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/opt/model/zookeeper-3.5.7-bin/zkData
dataDir 该属性设置zk数据存储的位置,zk官方不建议将数据存放在原始的路径中,因为linux会在一定时间刷新数据,到时候会造成数据丢失,所以要更改为自己的路径,这里我在model/zookeeper-3.5.7-bin目录下和zkbin文件的同级目录中新建了一个目录来保存数据。
修改完毕后,点击esc退出编辑模式,然后:wq退出文件。
退出后cd … 返回上一级,之后bin/zkServer.sh start 启动服务端zk,jps -l 或者jps 查看启动情况。
之后 bin/zkCli.sh 启动服务端 启动后出现[zk: localhost:2181(CONNECTED) 0] ,输入ls可查看当前zk的节点,只有一个zk。quit可退出服务端。
退出后bin/zkService.sh status 可查看zk状态 对应bin/zkService.sh stop可停止zk。
ZooKeeper JMX enabled by default
Using config: /opt/model/zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
# Be sure to read the maintenance section of the
# The number of milliseconds of each tick
tickTime=2000
服务器与客户端心跳时间,单位毫秒
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
初始化通信时间,第一次建立连接的时候,表示十个心跳时间为超时
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
除了第一次连接,其他次的连接最大通信时间是五个心跳。
数据存放位置和端口号属性略。
在开发中,利用集群分发脚本来复制zk到其他服务器上,不用手动再次配一次。
首先,在第一个zk的zookeeper-3.5.7-bin包中新建一个文件夹名为zkData,用来存储数据。
其次,在根目录~中,cd /bin进入bin包,然后vim xsync 文件,然后写入一下代码,不同的服务器需要更改一些配置
#2. 遍历集群所有机器
for host in 192.168.188.99 192.168.188.100 192.168.188.101
do
echo ==================== $host ====================
#3. 遍历所有目录,挨个发送
for file in $@
do
#4 判断文件是否存在
if [ -e $file ]
then
#5. 获取父目录
pdir=$(cd -P $(dirname $file); pwd)
#6. 获取当前文件的名称
fname=$(basename $file)
ssh $host "mkdir -p $pdir"
rsync -av $pdir/$fname $host:$pdir
else
echo $file does not exists!
fi
done
done
编辑完毕后,要使该文件剩下,在当前目录下bin下,输入chmod 777 xsync分配权限,便可使用。
然后,我们来到 model目录,输入xsync zookeeper-3.5.7-bin/ 该命令将会将服务器的配置全部复制到其他服务器中。zkData包里的myid文件也是一样的,所以我们需要分配进入其他服务器中,去修改myid文件,修改成不同端口,比如,最原始的myid里是2,那么接下来的机器按顺序便可以为3,4
接下来,进入conf目录,进入zoo.cfg文件,然后在最低端增加如下配置
server.2=192.168.188.99:2888:3888
server.3=192.168.188.100:2888:3888
server.4=192.168.188.101:2888:3888
server.a=b.c.d
a便是刚才配置的myid里的值,该文件是作为zk启动文件。b是该zk所在服务器的端口号,c是这个服务器的follower和leader交换的端口号。d是当其中的服务器出现失灵的情况。需要一个端口号重新进行选举,选举新的端口号。
接下来,便是在第一台服务器中,zookeeper-3.5.7-bin包中输入bin/zkServer.sh start命令,启动zk,然后输入bin/zkServer.sh status就会发现error字样,这是因为其他服务器还未开启,我们按照这个方法启动剩下的服务器,系统就会通过一个选举机制选出谁是follwer和leader
最后,是命令
zk.sh start
zk.sh status
zk.sh stop
配置地址映射,能有效加快与zk的连接。
在windows中,找到C:\Windows\System32\drivers\etc 下的hosts文件,如下
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
#127.0.0.1 activate.navicat.com
192.168.188.99 hadoop1
192.168.188.100 hadoop2
192.168.188.101 hadoop3
如上配置地址映射。
配置完地址映射,更换zoo.cfg配置
server.2=hadoop1:2888:3888
server.3=hadoop2:2888:3888
server.4=hadoop3:2888:3888
这里只需要更换一台服务器便可,然后集群分发脚本,分发出去。但是分发完毕后,记得修改zkDtat里的myid的序列号。
到这里还不完,因为我们是在外面修改的,所以这里只对windows和linux操作系统有效,而对linux系统之间不有效,我们需要在linux系统之间互相配置
比如这里我登录Hadoop1,然后vim /etc/hosts进入hosts文件,在文件的最后面,加入和windows一样的地址映射信息。这样退出后,我们需要用集群分发机制,将这个配置分发去其他服务器。
最后,需要注意的是,既然我们修改的Linux的地址映射,应该就有关输入的ip地址的文件配置都变成映射名,这样才可以生效。
#!/bin/bash
case $1 in
"start"){
for i in 服务器ip hadoop103 hadoop104
do
echo ---------- zookeeper $i 启动 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop104
do
echo ---------- zookeeper $i 停止 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
done
};;
"status"){
for i in hadoop102 hadoop103 hadoop104
do
echo ---------- zookeeper $i 状态 ------------
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
done
};;
esac
退出编辑之后,chmod 777 脚本名字 将脚本变得可用 我这里叫的是zk.sh
之后,配置java统一路径,进入zookeeper的bin目录,然后进入zkEnv.sh文件。
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
JAVA="$JAVA_HOME/bin/java"
elif type -p java; then
JAVA=java
在该代码的上面,为JAVA_HOME变量配置上你的jdk安装路径。
JAVA_HOME="/usr/local/java/jdk/jdk1.8.0_301"
完成后,关闭各个服务器的防火墙。
然后输入zk.sh start 便可以一键启动三台服务器,zk.sh status 查看三台服务器的状态,如果确实三台都启动了,而且都出现error这个报错,首先检查各个服务器的myid文件的端口是否和zoo.cfg这个文件设置的server.id一致。其次是检查zoo.cfg中配置server.id=A:B:C这个A的地址是否对应。最后是检查防火墙是否关闭,注意,防火墙关闭后要重启才可以生效。
启动三台服务器后,便可以启动客户端了,bin/zkCli.sh -server 192.168.188.99:2181
配置好启动停止脚本后,发现每次访问其他服务器都要输入密码,很影响效率,所以可以使用ssh免密登录。
这里我们对hadoop1进行配置,需要访问自己,和Hadoop2,Hadoop3。
首先,来到自己的~目录下,输入
[root@dboop1 ~]# ls -al
这个时候会出现所有文件,看是否有.sh文件,如果没有,表示该服务器没有用ssh ip的命令访问过其他服务器,可以尝试访问一次便会出现该文件了。
进入到隐藏文件中cd .ssh/ 便会发现有known_hosts这么一个文件,cat known_hosts 该文件便可以知道该服务器被谁访问过
如果我们在.ssh 文件中发现了其他文件比如:
-rw-------. 1 root root 393 12月 26 03:17 authorized_keys
-rw-------. 1 root root 1679 12月 26 03:16 id_rsa #私钥
-rw-r--r--. 1 root root 393 12月 26 03:16 id_rsa.pub #公钥
就不用输入ssh -keygen -t rsa,若是没有得输入该命令,生成公钥和密钥。
有了公钥和私钥,可以在这台服务器上将公钥和私钥都分发给被授权的服务器
ssh-copy-id hadoop1 ssh-copy-id hadoop2 ssh-copy-id hadoop3 记住,对自己也要分发一次。
然后我们如果站在hadoop1的角度去登录其他服务的话,就不用去输入密码了。
这样,我们站在Hadoop2的角度也做Hadoop1的操作,hadoop3也做统一的操作,这样任何一台服务器都可以互相访问
启动后,出现下面信息便是启动客户端完毕
[zk: 192.168.188.99:2181(CONNECTED) 1]
help命令可查看客户端命令
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl] #创建一个节点,默认创造永久节点,-e 为创造临时节点,-s为创造的节点时带上序列号。
delete [-v version] path #删除一个节点
deleteall path #删除多个节点
delquota [-n|-b] path
get [-s] [-w] path #加上-s 能获取节点的值,-w可监听数值的变化
getAcl [-s] path
history
listquota path
ls [-s] [-w] [-R] path#查看节点信息 -w可监听节点数量的变化
ls2 path [watch]
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
rmr path
set [-s] [-v version] path data # 修改对应节点的值
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b val path
stat [-w] path
sync path
ls -s / 命令出现下面信息
cZxid = 0x0 #创建节点的事务id
ctime = Thu #Jan 01 08:00:00 CST 1970 #节点被创建的毫秒数,从1970年开始
mZxid = 0x0 #最后更新的事务id
mtime = Thu #Jan 01 08:00:00 CST 1970 #节点最后修改的毫秒数从1970年开始
pZxid = 0x0 #最后更新的子节点事务id
cversion = -1 #版本号,子节点变化后,子节点的修改次数
dataVersion = 0 #节点的数据变化号
aclVersion = 0 #节点访问控制列表的变化号
ephemeralOwner = 0x0 #如果是临时节点,这个是临时节点的session id,如果不是临时节点这个值为0
dataLength = 0 #节点的数据长度
numChildren = 1 #子节点的数量
在zk中,一台客户端可以注册一个监听器,来观察一些节点的变化。
监听数据的变化:get -w /节点
get -w /zyl监听该节点数据的变化,前提是该节点必须存在
一旦该节点的数据发生变化,监听器所在客户端就会报出下面的话 WatchedEvent state:SyncConnected type:NodeDataChanged path:/zyl
监听子节点的变化:
ls -w /节点 监听该节点的子节点数的变化。
一旦该节点下有子节点创建或删除,监听器所在的客户端就会报如监听该数据那样的变化
虽然两者都会报出警告,但是这种警告只会在第一次监听的时候出现。
下面展示在IDEA开连接zookeeper。
首先新建一个maven工程,加入以下依赖。
<dependencies>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.8.2version>
dependency>
<dependency>
<groupId>org.apache.zookeepergroupId>
<artifactId>zookeeperartifactId>
<version>3.5.7version>
dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>compilescope>
dependency>
dependencies>
然后新建一个类
package com.hyb.zk;
import org.apache.zookeeper.*;
import org.junit.Before;
import org.junit.Test;
public class ZkClient {
private String connectString="hadoop1,hadoop2,hadoop3";
//连接后,zk发送会话的超时时间
private int sessionTimeOut=200000;
private ZooKeeper zooKeeper;
@Before
public void init() throws Exception {
zooKeeper = new ZooKeeper(connectString, sessionTimeOut, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
@Test
public void create()throws Exception{
// 创建一个节点,data为该节点的数据,ZooDefs为访问权限,这里设置面向所有人开启,最后的实参为创建什么类型的节点:持久型
// 注意,创建节点的时候,如果用到@Test注解启动,要在初始化连接的方法上将@Test注解更换为@Before注解,不然会报空指针异常
zooKeeper.create("/hyb3","hyb".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
@Test
public void getChildren()throws Exception{
// 获取该节点,true表示默认监听,会走init()中的process()方法,
// 如果你需要自定义监听,可以传入一个匿名类
List<String> children = zooKeeper.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
// 让控制台延迟持续输出
Thread.sleep(Long.MAX_VALUE);
}
然后写init()的process方法
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("-----------------");
// 监听的机制是只有第一次监听的时候才会给你提示,及children变量只有在第一次监听的时候才会输出,
// 所以,如果你需要监听器持续输入节点的变化,需要再监听一次
List<String> children = null;
try {
children = zooKeeper.getChildren("/", true);
for (String child : children) {
System.out.println(child);
}
Thread.sleep(Long.MAX_VALUE);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------");
}
@Test
public void exit() throws InterruptedException, KeeperException {
//第一个为节点,第二为是否开启监听
Stat exists = zooKeeper.exists("/hyb", false);
System.out.println(exists==null?"no exit":"exit");
}
因为leader有写权限,所以自己先写一份数据,然后通知其他follower开始写数据。
如果整个集群中,包括leader,超过半数的服务器写完了数据,这个信息便可以回馈给客户。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXna3uEQ-1653707450528)(C:\Users\46894\AppData\Local\Temp\WeChat Files\8c3bed4e7cd3e4b8099a1731b2a0e94.png)]
follower没有写的权限,这个时候会先发通知给leader,leader先写一份,然后通知其他follow开始写,写完的服务器超过一半,leader便可以发送可以反馈的命令给与客户端直接交互的follower,这个follower便会与客户反馈。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xb4AiIf5-1653707450530)(C:\Users\46894\AppData\Local\Temp\WeChat Files\056e433b42192b1b57def528f619587.png)]
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知
到主节点服务器的上下线。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-06E4M1ZK-1653707450530)(C:\Users\46894\AppData\Local\Temp\WeChat Files\8a284512afde0ca3248fda02f68093e.png)]
下面来模拟该场景,我们模拟服务器真实上线的过程,而客户端则通过zk集群实时监听服务器的状态。
服务器的真实上线过程,便是在zk下创建节点。而客户端便是监听节点。
首先是服务端
package com.hyb.zk;
import org.apache.zookeeper.*;
import java.io.IOException;
public class ZkTest1 {
private ZooKeeper zooKeeper;
private String connectString="hadoop1:2181,hadoop2:2181,hadoop3:2181";
private int sessionId=2000;
public static void main(String[] args) throws Exception {
ZkTest1 zkTest = new ZkTest1();
// 获取zk连接
zkTest.getConnect();
// 注册zk
zkTest.register("hadoop1");
// 延迟控制台输出(业务逻辑)
zkTest.sleep();
}
private void sleep() throws InterruptedException {
Thread.sleep(Long.MAX_VALUE);
}
private void register(String hostName) throws Exception {
zooKeeper.create("/server/"+hostName,hostName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostName+"上线");
}
private void getConnect() throws IOException {
zooKeeper = new ZooKeeper(connectString, sessionId, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
}
});
}
}
其次是客户端。
package com.hyb.zk;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ZkTestClient1 {
private String connectString="hadoop1:2181,hadoop2:2181,hadoop3:2181";
private int sessionId=20000;
private ZooKeeper zk;
public static void main(String[] args) throws Exception {
ZkTestClient1 zkTestClient1 = new ZkTestClient1();
// 获取连接
zkTestClient1.getConnect();
// 监听
zkTestClient1.watch();
// 业务逻辑,让控制台延迟输出
zkTestClient1.sleep();
}
private void sleep() throws Exception {
Thread.sleep(Long.MAX_VALUE);
}
private void watch() throws InterruptedException, KeeperException {
// 监听器
List<String> server = zk.getChildren("/server", true);
List<String> list=new ArrayList<>();
for (String s :
server) {
// 获取数据
byte[] data = zk.getData("/server/" + s, false, null);
list.add(new String(data));
}
System.out.println(list);
}
private void getConnect() throws IOException {
zk=new ZooKeeper(connectString, sessionId, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
// 再次监听,时刻观察节点变化
watch();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
测试:首先,我们先开启客户端监听,然后再开启服务端,服务端会创造节点,而实时的,便可以在控制台观察到客户端监听器的节点变化。
多个分布式进程同时共享一个资源,但有且仅有一个进程可以独占,当一个进程用完,另一把锁又开始独占。
分布式锁的实现:创造多个同等级的短暂的带序列号的节点,规定序列号越小,访问资源的权限最高,最容易独占资源,序列号比它大的节点监听独占资源的节点,一旦独占资源的节点释放,权限和其最相似的节点会判断自身权限是否为最高的,一旦是最高的,便独占资源。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EUHd0hr-1653707450531)(C:\Users\46894\AppData\Local\Temp\WeChat Files\1c00871a41f32e2bff07cb07fd3049c.png)]
package com.hyb.zk;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class DistributedLock {
private ZooKeeper zk;
private int sessionTimeout=20000;
private String connectString="hadoop1:2181,hadoop2:2181,hadoop3:2181";
// 前一个节点路径
private String upPath;
// 等待
private CountDownLatch countDownLatch=new CountDownLatch(1);
private CountDownLatch upCountDownLatch=new CountDownLatch(1);
private String node;
public DistributedLock() throws Exception {
// 获取连接
zk=new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
// 开始监听
// countDownLatch 检测状态
if (watchedEvent.getState()==Event.KeeperState.SyncConnected){
// 如果已经是连接状态,释放
countDownLatch.countDown();
}
// upCountDownLatch 检测
// 如果前一个节点在删除,前一个节点的路径和upPath相同
if (watchedEvent.getType()==Event.EventType.NodeDeleted&&watchedEvent.getPath().equals(upPath)){
upCountDownLatch.countDown();
}
}
});
// 等待连接后,才执行下一步操作
countDownLatch.await();
// 判断根节点是否存在
if (zk.exists("/locks",false)==null){
// 根节点不存在便创建一个永久节点
zk.create("/locks","locks".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 上锁
public void setLock()throws Exception{
// 创建节点,临时的,有序列号的
node = zk.create("/locks/lock", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 判断该节点是否是根节点下序列号最小的节点,权限最高
List<String> children = zk.getChildren("/locks", false);
// 如果该集合是1,那么便是这个节点可以获取锁
int size=children.size();
if (size==1){
System.out.println(children.get(0)+"获取了锁");
return;
}else {
// 将该集合排序
Collections.sort(children);
// 获取节点在已经排序的集合中的位置如何
// 1.拿到该节点的名字
String thisNode=node.substring("/locks/".length());
// 2.获取thisNode节点的index
int index = children.indexOf(thisNode);
// 如果该索引在第一位,那么就不用监听,它自己便可以获取锁了
if (index==-1){
System.out.println("出错了");
return;
}else if (index==0){
System.out.println(children.get(0)+"获取锁了");
return;
}else {
// 前一个的路径
upPath="/locks/"+children.get(index-1);
// 监听数据的变化
zk.getData(upPath,true,null);
// 等待监听完毕
upCountDownLatch.await();
return;
}
}
}
// 释放锁
public void freeLock() throws Exception {
// 删除节点
zk.delete(node,-1);
}
}
package com.hyb.zk;
public class DistributedLockTest {
public static void main(String[] args) throws Exception {
final DistributedLock distributedLock1 = new DistributedLock();
final DistributedLock distributedLock2 = new DistributedLock();
// 创建两个互不影响的线程
new Thread(new Runnable() {
@Override
public void run() {
try {
distributedLock1.setLock();
System.out.println("线程一启动");
// 模拟线程占用
Thread.sleep(5*1000);
distributedLock1.freeLock();
System.out.println("线程一释放");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
distributedLock2.setLock();
System.out.println("线程二启动");
Thread.sleep(5*1000);
distributedLock2.freeLock();
System.out.println("线程二释放");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>4.3.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>4.3.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-clientartifactId>
<version>4.3.0version>
dependency>
package com.hyb.zk;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
public class CuratorLockTest {
public static void main(String[] args) {
// 创建分布式锁1
final InterProcessMutex interProcessMutex1 = new InterProcessMutex(getCuratorWork(), "/locks");
final InterProcessMutex interProcessMutex2 = new InterProcessMutex(getCuratorWork(), "/locks");
// 创建两个不相干的线程
new Thread(new Runnable() {
@Override
public void run() {
try {
interProcessMutex1.acquire();
System.out.println("1");
interProcessMutex1.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
interProcessMutex2.acquire();
System.out.println("2");
interProcessMutex2.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
private static CuratorFramework getCuratorWork() {
// 连接失败后重置
ExponentialBackoffRetry ebr = new ExponentialBackoffRetry(3000, 3);
// connectionTimeoutMs 连接超时时间 sessionTimeoutMs连接后,发送会话的超时时间
CuratorFramework build = CuratorFrameworkFactory.builder().connectString("hadoop1:2181,hadoop2:2181,hadoop3:2181")
.connectionTimeoutMs(20000)
.sessionTimeoutMs(20000)
.retryPolicy(ebr).build();
// 客户端启动
build.start();
Systemt.println("zk启动成功!");
return build;
}
问:zk的选举机制是什么?
首先,是第一次选举:每次选举的过程中都会先给自己投一票,然后跟前面的服务器比较,id小的服务器会将自身的票数给id大的,一旦服务器的票数有超过服务器数量半数的,则选出leader
zk当不是第一次初始化的时候,选举策略有所改变,这里设计到三个值:SID 和myid一样,不能重复,用来唯一标识zk的服务器id。ZXID,事务ID,用来标识一次服务器状态的变更,在某一时刻,集群中的每台机器不一定完全一致,这和服务器对于客户端的更新要求的处理逻辑有关。EPOCH 每个leader任期的代号,没有leader时,同一轮的逻辑时钟值是一致的,每进行一轮投票后,该值都会增加。当不是第一次初始化的时候,选举策略便是比较这三个值,先比较EPOCH,大的胜出,若相同,比较事务id,大的胜出,若相同,比较myid,大的胜出
生产集群安装多少合适:不能过多,过多虽然有提高可靠性的好处,但过多了会让通讯延迟,这是不好的.
经验来说有下面这些比列:
10:3 20:5 100:11 200 :11