Zookeeper 系列 (四)
一 、集群搭建
Zookeeper 集群中的角色
设计目的:
1 、 最终一致性: client 不论连接到哪个 Server ,展示个他的都是同一个视图
2 、可靠性:具有简单、健壮、良好的性能,如果消息m 被一台服务器接受,那么它 将被所有的服务器接受。
3、实时性:zookeeper 保证客户端将在一定的时间间隔内获取到服务器更新的消息
4、等待无关(wait-free): 每个client 都能有效等待
5、原子性:更新只能成功或失败!
6、顺序性: 全局有序 和 偏序
测试 伪集群
说明:一台机器上启动 多个 zookeeper 实例组成集群
需要有多个zookeeper 服务 只改变端口 就是伪服务
1、复制配置文件 命名为 zoo-1.cfg 修改配置文件
显示更改配置文件
dataDir=/tmp/zookeeper-1
clientPort=2181
server.1=192.168.245.128:2881:3881
server.2=192.168.245.128:2882:3882
server.3=192.168.245.128:2883:3893
2、复制配置文件 命名为 zoo-2.cfg 修改
dataDir=/tmp/zookeeper-2
clientPort=2182
3、复制配置文件 命名为 zoo-3.cfg 修改
dataDir=/tmp/zookeeper-3
clientPort=2183
4、在 [ /tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-3] 中 创建文件 myid 文件 写入当前实例的 servier id
# cd /tmp/zookeeper-1
# vim myid
1
# cd /tmp/zookeeper-2
# vim myid
2
# cd /tmp/zookeeper-3
# vim myid
3
5、启动三个 zookeeper 实例
# bin/zkServer.sh start conf/zoo-1.cfg
# bin/zkServer.sh start conf/zoo-2.cfg
# bin/zkServer.sh start conf/zoo-3.cfg
6、直接用 zkCli.sh -server IP:PORT 连接 zookeeper 服务端检测
这里是使用了三个客户端
二、zookeeper 分布式锁
参考链接:https://blog.youkuaiyun.com/liyiming2017/article/details/83786331
1.0 版本
思路:
- 用zookeeper中一个临时节点代表锁,比如在==/exlusive_lock下创建临时子节点/exlusive_lock/lock==。
- 所有客户端争相创建此节点,但只有一个客户端创建成功。
- 创建成功代表获取锁成功,此客户端执行业务逻辑
- 未创建成功的客户端,监听/exlusive_lock变更
- 获取锁的客户端执行完成后,删除/exlusive_lock/lock,表示锁被释放
- 锁被释放后,其他监听/exlusive_lock变更的客户端得到通知,再次争相创建临时子节点==/exlusive_lock/lock==。此时相当于回到了第2步。
问题:
- 锁的获取顺序和最初客户端争抢顺序不一致,这不是一个公平锁。每次锁获取都是当次最先抢到锁的客户端。
- 羊群效应,所有没有抢到锁的客户端都会监听/exlusive_lock变更。当并发客户端很多的情况下,所有的客户端都会接到通知去争抢锁,此时就出现了羊群效应。
2.0版本
思路:
- 每个客户端往/exlusive_lock下创建有序临时节点/exlusive_lock/lock_。创建成功后/exlusive_lock下面会有每个客户端对应的节点,如/exlusive_lock/lock_000000001
- 客户端取得/exlusive_lock下子节点,并进行排序,判断排在最前面的是否为自己。
- 如果自己的锁节点在第一位,代表获取锁成功,此客户端执行业务逻辑
- 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点lock_000000002,那么则监听lock_000000001.
- 当前一位锁节点(lock_000000001)对应的客户端执行完成,释放了锁,将会触发监听的客户端(lock_000000002)的逻辑。
- 监听客户端重新执行第2步逻辑,判断自己是否获得了锁。
实现代码 LockSample 类 2.0 版本
分布式锁:
public class LockSampleDme {
//客户端
private ZooKeeper zkClient;
//路径
private static final String LOCK_ROOT_PATH = "/Locks";
//节点名
private static final String LOCK_NODE_NAME = "Lock_";
private static final String IP_HOST = "192.168.245.128:2181";
private String lockPath;
private Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println(event.getPath() + " 前锁释放");
synchronized (this) {
notifyAll();
}
}
};
public LockSampleDme() throws IOException {
zkClient = new ZooKeeper(IP_HOST, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getState() == Event.KeeperState.Disconnected) {
System.out.println("失去连接");
}
}
});
}
/**
* 创建节点
* 先判断锁的根节点/Locks是否存在
* 不存在的话创建
* 然后在/Locks下创建有序临时节点,并设置当前的锁路径变量lockPath
*/
private void createLock() throws KeeperException, InterruptedException {
/*
判断 根节点 /Locks 是否存在
*/
Stat exists = zkClient.exists(LOCK_ROOT_PATH, false);
if (exists == null) {
//不存在 就创建
zkClient.create(LOCK_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/*
然后在/Locks下创建有序临时节点 并设置当前的锁路径变量lockPath
*/
//返回的 是 路径
String path = zkClient.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME,
new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName() + "锁路径:" + path);
this.lockPath = path;
}
/**
*
*/
private void attemptLock() throws Exception {
//获取 /Locks 下的所有的 子节点 list 内部值 Lock_0000000000 类型
List<String> lockPaths = zkClient.getChildren(LOCK_ROOT_PATH, false);
//节点 序号排序 stream 流方式
lockPaths = lockPaths.stream().sorted((o1, o2) -> o1.compareTo(o2)).collect(Collectors.toList());
// 进行获取 自己创建的 锁 的 地址 lockPath 需要去除 头部的 /Locks/ 获取所处位置
int index = lockPaths.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));
if (index == 0) {
//第一位 获取到锁
System.out.println(Thread.currentThread().getName() + " 锁获得 " + lockPath);
return;
} else {
//没有获取到锁 进行 监听 前一个
String prevPath = lockPaths.get(index - 1);
// 判断是否存在进行 监听
Stat exists = zkClient.exists(LOCK_ROOT_PATH + "/" + prevPath, watcher);
//前一个 节点 不存在 或掉线 进行 重新获取锁
if (exists == null) {
attemptLock();
} else {
//存在阻塞 当前 线程 直到 prevPath 锁释放 被watcher 监听 ,notifyAll 唤醒 重新 attemptLock
System.out.println("等待前锁释放 " + prevPath);
synchronized (watcher) {
watcher.wait();
}
attemptLock();
}
}
}
//业务逻辑
public void acquireLock() throws Exception {
//创建 节点
createLock();
//尝试 获取 锁
attemptLock();
}
//释放锁
public void releaseLock() throws KeeperException, InterruptedException {
zkClient.delete(lockPath, -1);
zkClient.close();
System.out.println("锁释放:" + lockPath);
}
}
客户端创建:
class TicketSeller {
public void sell() {
System.out.println("开始售卖:");
//线程 随机休眠 模拟 现实操作
int sleepMillis = (int) (Math.random() * 2000);
try {
Thread.sleep(sleepMillis);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("售票结束!");
}
public void sellTicketWithLock() throws Exception {
LockSampleDme lockSampleDme = new LockSampleDme();
lockSampleDme.acquireLock();//进行排队
sell();//等待
lockSampleDme.releaseLock();//买到 退出
}
public static void main(String[] args) throws Exception {
TicketSeller ticketSeller = new TicketSeller();
for (int i = 0; i < 10; i++) {
ticketSeller.sellTicketWithLock();
}
}
}