一. Zookeeper的介绍.
Zookeeper帮助我们做统一命名空间,状态的同步,集群节点的管理,分布式应用的配置…
Zookeeper就是一个文件系统 + 监听通知机制.
分布式应用服务的管理和分布式锁.
二. Zookeeper的架构.
1. Zookeeper的架构图.
2. znode的节点.
znode分为四种节点.
- 持久节点.
只要存储,就会永久的保存在zookeeper中. - 持久有序节点.
只要存储,就会永久的保存在zookeeper中,每个保存的znode都会在节点名后追加一个当前节点坐在位置的唯一序号. - 临时节点.
客户点连接着zookeeper-server时,节点正常的存储,当客户端断开连接之后,自动删除当前节点. - 临时有序节点.
客户点连接着zookeeper-server时,节点正常的存储,当客户端断开连接之后,自动删除当前节点,节点的名称后会追加一个当前所在位置唯一的序号.
3. 监听通知机制.
可以有多个客户端监听这zookeeper的一个或多个znode节点.
每当没点听的节点发生了增删改操作之后,zookeeper会通知监听当前节点的客户端,客户端就可以通过zookeeper的消息做出相应变化.
三. Zookeeper集群特点.
1. Zookeeper集群中节点的角色.
- Leader. (领导)
Leader管理着整个Zookeeper集群中心的全部节点,主要负责读写操作以及数据的同步.
当Leader执行写操作时,会对外广播,当其他节点收到了整个消息后,也会开始在本地写数据,其他节点会给Leader一个ack回调,当Leader发现超过半数的节点写操作完毕后,才会再次对外广播,可以commit了. - Follower. (追随者)
追随Leader的从节点,主要负责读写操作.
当Follower接收到一个写操作时,会第一时间交给Leader处理.
当Zookeeper的集群中没有Leader角色时,Follower会参与投票.选举出一位全新的Leader. - Observer. (观察者)
追随Leader的从节点,主要负责读写操作.
当Observer接收到一个写操作时,会第一时间交给Leader处理.
Observer不会参与选举Leader的投票. - Looking. (启动全新的zookeeper节点,Leader挂掉时,会出现.)
当一个全新的zookeeper节点启动时,会寻找Leader.
如果Leader不存在,Looking会发起一次投票,让所有非observer节点参与投票,并选举出票数最高的节点作为全新的Leader.
如果Leader存在,直接变为follower追随当前的Leader.
如果Zookeeper出现了多个Leader,这种情况被称为假死脑裂.
这种问题已经在zookeeper3.4左右的版本已经处理了,添加了一个超时时间去解决的.
2. Zookeeper存储数据的有序性.
- Zookeeper在执行写数据时,每一个节点都有自己的一个FIFO的队列.
- Zookeeper可以保证每一个写的数据不会出现顺讯紊乱的问题,Zookeeper还会为每一个数据添加一个全局唯一的zxid,
zxid就是一个64位的数字,后存储的数据zxid会大于先存储的数据的zxid. - 每一个Zookeeper节点,都会被分配一个全局唯一的myid,myid就是一个数字.
- 选举Leader的过程.
- 先找在节点中zxid最大的节点(数据最新的节点).
(如果有一个zxid最大的,会被选举为Leader). - 发现zxid都一样,会通过全局唯一的myid的数字来角色,谁的myid的越大,谁就作为Leader.
3. CAP定理.
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。
CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
Zookeeper集群保证的是CP.
Zookeeper必须保证都能找到Leader才能对外提供服务.
Zookeeper是在保证半数节点数据一致的情况下,才会对外提供服务.
AP是可用性,即便只有一台服务的数据是正确的,他也会对外提供全套服务.
四. 搭建Zookeeper集群.
1. 准备docker-compose.yml文件.
version: '3.1'
services:
zk1:
image: zookeeper
restart: always
container_name: zookeeper1
ports:
- 2182:2181
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=zk3:2888:3888;2181
zk2:
image: zookeeper
restart: always
container_name: zookeeper2
ports:
- 2183:2181
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=zk3:2888:3888;2181
zk3:
image: zookeeper
restart: always
container_name: zookeeper3
ports:
- 2184:2181
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=zk3:2888:3888;2181
# server.myid=zookeeper所在ip:节点间通讯端口:节点间投票端口;zookeeper占用的端口
# server.1 =服务名 :2888 :3888 ;2181
一. Zookeeper的常用命令.
1. 查询节点.
ls 节点名称 -> 例子: ls / 查看根节点下有哪些节点.
get 节点名称 -> 例子: get /zookeeper 查看zookeeper节点中的数据
2. 创建节点.
create [-s] [-e] znode名称 znode数据
固定语法 [有序] [临时] /名称 数据
例子:
持久有序节点: create -s /heiheihei hehehe
临时有序节点: create -s -e /hahaha xixixi
临时节点: create -e /xxx yyy
3. 修改节点数据.
set znode名称 新数据
例子:
修改/qf中的数据: set /qf zzzz
4. 删除节点.
delete znode名称 -> 删除没有子节点的znode.
deleteall znode名称 -> 递归删除当前节点和其所有子节点.
5. 查看节点状态.
stat znode名称. -> 查看节点状态
可以查看到当前节点的信息.可以查看到zxid和一些权限等等信息.
二. 通过Java操作Zookeeper.
1. Java连接Zookeeper.
1. 创建maven项目.
...
2. 导入依赖.
1. curator-recipes
2. zkclient
3. junit
3. 模板代码.
public CuratorFramework cf(){
// 设置重试策略对象
RetryPolicy rp = new ExponentialBackoffRetry(3000,2);
// 创建CuratorFramework对象.
CuratorFramework cf = CuratorFrameworkFactory.builder()
.connectString("192.168.98.98:2182,192.168.98.98:2183,192.168.98.98:2184")
.connectionTimeoutMs(6000)
.retryPolicy(rp)
.build();
// 开启连接
cf.start();
// 返回连接对象
return cf;
}
2. Zookeeper监听节点.
@Test
public void listen() throws Exception {
//first. 获取连接对象.
CuratorFramework cf = ZkConfig.cf();
//1. 指定监听的节点.
NodeCache nodeCache = new NodeCache(cf,"/qf");
nodeCache.start();
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
byte[] data = nodeCache.getCurrentData().getData();
Stat stat = nodeCache.getCurrentData().getStat();
String path = nodeCache.getCurrentData().getPath();
System.out.println("监听的是" + path + "节点");
System.out.println(path + "的节点数据现在为:" + new String(data));
System.out.println("节点信息为:" + stat);
}
});
System.out.println("开始监听/qf节点.");
System.in.read();
//end. 断开连接
cf.close();
}
三. 实现Zookeeper的分布式锁.
1. 搭建一个模拟秒杀的场景.
@RestController
public class SecondKillController {
private static String item_name = "月饼";
private static Long item_stock = 10000L;
private static Long order_number = 0L;
/*
模拟秒杀的案例.
*/
@GetMapping(value = "/sk",produces = "text/html;charset=utf-8")
public String sk(){
System.out.println("开始秒杀月饼..");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
item_stock--;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
order_number++;
String result = "元宵节秒杀月饼, 月饼库存剩余数为:" + item_stock + ",月饼的订单数为:" + order_number + "!!";
return result;
}
}
安装Apache服务,使用其中的ab做压力测试.
2. 传统锁的问题.
解决秒杀的超卖问题.
添加了传统的synchronized同步代码块解决.
1. 效率贼慢.
2. 在分布式环境下,传统的锁,已经失效.
....
3. Zookeeper分布式锁实现的原理.
使用zookeeper实现分布式锁.
分布式锁的要求:
1. 在同一时间,只能有一个客户端获取到锁资源. (互斥锁)
2. 锁在一定时间内,需要被释放. (避免死锁)
3. 保证获取和释放锁资源的操作必须是高可用的,性能也不能太差.
zookeeper在写数据时,会通过一个FIFO的队列,每个写操作时有执行顺序的.
临时有序节点.
zookeeper分布式锁的原理:
当有客户端需要获取锁资源时,会再zookeeper的指定znode下,生成一个临时有序节点.
如果生成的临时有序节点的序号是最小的一个,那么他就获取了锁资源.
如果生成的临时有序节点的序号不是最小的一个,没有获取到锁资源.
这个时候或监听比自己的序号小一点的那个有序节点,如果监听到比自己小一点的序号被删除了,那就再去判断一次自己当初创建的节点是不是变成了序号中最小的内一个,如果是最小的了,获取到锁资源,如果不是最小的,重复刚才的操作.
释放锁就是删除掉创建的临时有序节点.
4. Zookeeper分布式锁的实现.
// 获取锁对象
InterProcessMutex lock = new InterProcessMutex(cf,"/locker");
try {
// 加锁
lock.acquire();
// 具体业务逻辑代码
}catch (Exception e){
}finally {
// 释放锁资源.
lock.release();
}