1.Zookeeper的介绍
介绍:
zookeeper原本是Hadoop中的一个组件,后独立出来,在apache上维护
zookeeper译名:动物园管理员
管理着大数据中的各个组件
hdfs + mapreduce + hbase + hive
特点:
- 分布式锁
- 集群管理
- 分布式节点管理
- 统一管理配置信息
2.Zookeeper的架构
1.zookeeper的架构图
2.Znode的特点
znode分为四种节点
1.持久节点
只要存储,会永远保存在zookeeper中
2.持久有序节点
只要存储,会永久的保存在zookeeper中,并且会携带一个唯一并且有序的id,先进来的节点值相对较小
3.临时节点
正常存储数据,当客户端断开连接后,自动删除节点信息
4.临时有序节点
正常存储数据,当客户端断开连接后,自动删除节点信息,并且会携带一个唯一并且有序的id,先进来的节点值相对较小
3.监控机制
可以有多个客户端监听zookeeper的某一个znode
每当zookeeper被监听的节点值增删改后,会通知全部监听当前节点的客户端
3.Zookeeper的集群特点
1.zookeeper集群中的角色
1.leader
leader管理着整个zookeeper集群中的全部节点,leader主要负责读写操作,和数据同步。
当leader执行写操作后,会对外广播,当节点接收后,在本地写数据,并回调给leader-ack,当超过半数的从节点持久化完毕后,leader再次广播,可以commit了
2.follower
追随leader的从节点,主要负责读操作
当follower接收写操作后,会第一时间交给leader处理
follower会发起leader选举的投票
3.observe
追随leader的从节点,主要负责读操作
当observe接收写操作后,会第一时间交给leader处理
等待leader出现,直接追随,不会参与投票
4.looking
looking节点会直接发起一次选举leader的投票
如果leader不存在,让每一个非observe的节点参与投票,票数最高作为leader
从looking变成follower追随leader的节点
如果leader存在,直接变成follower追随当前leader节点
2.zookeeper存储数据的有序性(选举leader的关键性)
1.zookeeper在执行写操作时,是不会阻塞IO的。zookeeper为了保证有序性,会给每一个写操作的数据,编写一个全局唯一的zxid
2.zxid是一个64位的数字,前32位是由当前节点参与选举次数决定的,后32位是存储数据的全局唯一id,后存储的数据的zxid肯定大于先存储的
哪个节点的zxid越大,数据越新,会被选举为leader
在每一个zookeeper节点启动时,都会有一个全局唯一的myid(数字)
在选举leader时,如果zxid一致,根据myid的值,决定选举的leader是谁
4.搭建zookeeper集群
1.搭建三台zookeeper(在一台虚拟机中)
在虚拟机中解压三个文件分别为zk1,zk2,zk3
2.修改配置文件
1.进入zk1/conf/,复制一份zoo-siample.cfg,并改名为zoo.cfg
2.vi zoo.cfg,配置以下参数
dataDir=../data
dataLogDir=../log
server.1=114.55.219.117:7001:8001
server.2=114.55.219.117:7002:8002
server.3=114.55.219.117:7003:8003
3.进入zk1/data目录下
vi myid 创建该文件并在里面添加数字1
...
zk2和zk3和上述一致,只是myid中的值分别改为2和3,并且zoo.cfg中的client.port该为2182和2183
3.启动
启动zk1
cd zk1/bin/
./zkServer.sh start
./zkServer.sh status
因为当前集群只有一个节点,再启动zk2
cd /usr/local/zk-cluster/zk2/bin/ #进入zk2的bin目录
./zkServer.sh start #启动zk2
./zkServer.sh status #查看zk2的服务状态
我们和上述一样启动zk3的服务并查看zk1的状态和zk3的状态
这时停掉zk2的服务,再查看一下zk1和zk3的状态
我们可以发现当zk2的服务挂掉之后,zk3被选举为leader,因为在没有zxid的基础上,zk3的myid的值大于zk2的myid
此时,启动zookeeper客户端创建一个节点 如:
这时退出客户端并且将zk3的服务关闭
查看zk1的状态,发现这时zk1为leader了,因为zk1已经参与了节点的操作了,zxid大于zk2
注意:
1. 启动集群时,如果先启动一台,那么集群是瘫痪的,无法使用
2. 当集群中节点超过半数存货时,zk集群才可以正常运行
3. 投票机制
测试效果:
1.启动zk1和zk2 ->zk2为leader
2.启动zk3 ->zk2为leader
3.关闭zk2 ->zk3为leader
4.添加一条数据 -----------
5.关闭zk3,启动zk2 ->zk1为leader
5.Zookeeper的命令操作
1.查询节点
ls 节点名称 -> 查询当前节点下的字节点
get 节点名称 -> 查看当前节点的名称
2.创建节点
create [-s] [-e] path data acl
创建 [有序] [临时] 节点路径 节点数据 节点访问权限
3.修改节点
set path data -> 修改节点的数据
4.删除节点
delete path -> 删除没有字节的节点
rmr path -> 直接递归删除节点(慎用)
5.查看节点状态
stat path -> 查看节点的状态
6.通过Java的API操作
1.创建项目
…
2.导入依赖
<!-- https://mvnrepository.com/artifact/com.101tec/zkclient -->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.1.0</version>
</dependency>
3.连接zookeeper
public class TestConnect {
@Test
public void connect(){
//重试策略,每隔三秒重试一次,一共重试两次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
//连接zookeeper的客户端
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("114.55.219.117:2181,114.55.219.117:2182,114.55.219.117:2183")
.connectionTimeoutMs(6000)
.retryPolicy(retryPolicy)
.build();
//开启连接
cf.start();
}
}
4.CRUD
public class TestConnect {
@Test
public void connect(){
//重试策略,每隔三秒重试一次,一共重试两次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
//连接zookeeper的客户端
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("114.55.219.117:2181,114.55.219.117:2182,114.55.219.117:2183")
.connectionTimeoutMs(6000)
.retryPolicy(retryPolicy)
.build();
//开启连接
cf.start();
//======================================================
//1.创建
//创建节点
String s = cf.create().forPath("/bb", "bilegebi".getBytes());
System.out.println(s);
//创建多级目录
String s = cf.create().creatingParentsIfNeeded().forPath("/cc/dd", "hello world".getBytes());
System.out.println(s);
/**
* 创建临时节点和有序数据
* PERSISTENT:持久
* PERSISTENT_SEQUENTIAL:持久有序
* EPHEMERAL:临时
* EPHEMERAL_SEQUENTIAL:临时有序
*/
cf.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/ab/ac","xxxx".getBytes());
//2.删除
//删除单个节点
Void aVoid = cf.delete().forPath("/aa");
//删除多级节点
Void aVoid1 = cf.delete().deletingChildrenIfNeeded().forPath("/aa");
System.out.println(aVoid);
System.out.println(aVoid1);
//3.修改
//修改
Stat stat = cf.setData().forPath("/aa", "哇哈哈哈哈哈".getBytes());
System.out.println(stat);
//4.查询
//获取子节点
List<String> list = cf.getChildren().forPath("/");
list.forEach(System.out::println);
//获取节点中的数据
byte[] bytes = cf.getData().forPath("/aa");
String s = new String(bytes);
System.out.println(s);
//检查节点是否存在,存在返回状态信息,不存在返回null
Stat stat = cf.checkExists().forPath("/aa");
System.out.println(stat);
//======================================================
}
}
5.监听
@Test
public void listener() throws Exception {
//重试策略,每隔三秒重试一次,一共重试两次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
//连接zookeeper的客户端
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("114.55.219.117:2181,114.55.219.117:2182,114.55.219.117:2183")
.connectionTimeoutMs(6000)
.retryPolicy(retryPolicy)
.build();
//开启连接
cf.start();
//1.创建一个节点缓存
NodeCache nodeCache = new NodeCache(cf,"/aa");
nodeCache.start();
//2.开始监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
//当监听的节点发生变化调用该方法
@Override
public void nodeChanged() throws Exception {
String path = nodeCache.getPath();
System.out.println("变化的节点路径:"+path);
Stat stat = nodeCache.getCurrentData().getStat();
byte[] data = nodeCache.getCurrentData().getData();
String path1 = nodeCache.getCurrentData().getPath();
System.out.println("stat:"+stat);
System.out.println("bytes"+new String(data));
System.out.println("path1:"+path1);
}
});
//关闭连接
// cf.close();
System.in.read();
}
7.Zookeeper的分布式锁
1.创建一个config
@SpringBootConfiguration
public class CuratorFrameworkConfig {
@Bean
public CuratorFramework cf(){
//重试策略,每隔三秒重试一次,一共重试两次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
//连接zookeeper的客户端
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("114.55.219.117:2181,114.55.219.117:2182,114.55.219.117:2183")
.connectionTimeoutMs(6000)
.retryPolicy(retryPolicy)
.build();
//开启连接
cf.start();
//关闭连接
cf.close();
return cf;
}
}
编写锁的测试类
@RestController
public class ZkLockController {
private static int i = 10000;
private static int j = 0;
@Autowired
private CuratorFramework cf;
//公平锁
@RequestMapping("/lock")
public String lock(){
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
/**
* zookeeper公平锁是基于它的临时有序节点实现的,当并发访问某一个接口后,
* 会在zookeeper中创建一个临时有序节点,并且这些节点名称都是一样的,
* 当时zookeeper会在名称后加上一个序号,每一个节点只监听比他序号小的,
* 当节点发现他是最小 的就会获取锁资源,如果不是最小的就会一直在那等着。
* @return
*/
try {
lock.acquire();
System.out.println("获取锁对象");
i--;
j++;
//释放锁
lock.release();
return "i:"+i+",j:"+j;
} catch (Exception e) {
System.out.println("获取锁失败");
return "i:"+i+",j:"+j;
}
}
/**
* 非公平锁使用的是临时节点。公平锁使用的是临时有序节点
* 用户想获取到锁资源,用户就尝试创建指定的一个节点,如果节点存在,说明锁资源正在占用,直接抛出异常,try-catch中给用户提示,如果创建节点成功,线程成功的获取到了锁资源.
* 如果出现了死锁?
* 因为创建的时临时节点,如果出现异常,那么客户端自动断开连接,临时节点自动被删除.
* @return
*/
@RequestMapping("/nlock")
public String nlock(){
//重试策略,每隔三秒重试一次,一共重试两次
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
//连接zookeeper的客户端
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("114.55.219.117:2181,114.55.219.117:2182,114.55.219.117:2183")
.connectionTimeoutMs(6000)
.retryPolicy(retryPolicy)
.build();
//开启连接
cf.start();
try {
//加锁
cf.create().withMode(CreateMode.EPHEMERAL).forPath("/nlock");
i--;
j++;
//释放锁
cf.delete().deletingChildrenIfNeeded().forPath("/nlock");
return "i:"+i+",j:"+j;
} catch (Exception e) {
System.out.println("获取锁失败");
return "cool cool";
} finally {
//关闭连接
cf.close();
}
}
}