zookeeper概念
zookeeper是Apache Hadoop项目下的一个子项目,是一个树形目录服务,zookeeper翻译过来就是动物园管理员,他是用来管 Hadoop (大象)、Hive(蜜蜂)的管理员,简称zk
zookeeper主要功能
zookeeper是一个分布式的、开源的分布式应用程序的协调服务zookeeper 提供的主要功能包括:
- 配置中心
- 分布式锁
- 集群管理(注册中心)
zookeeper安装
这里选用的是阿里云服务安装(环境为centos7.6),安装方式为docker安装
拉取并查看镜像
此处不建议拉取最新镜像,后边会有坑,运行容器的时候会出现奇葩的错误:
/docker-entrypoint.sh: line 43: /conf/zoo.cfg: Permission denied
bash
docker pull zookeeper:3.5.7
配置云服务器防火墙
bash
firewall-cmd --add-port=2181/tcp --permanent
firewall-cmd --reload
运行容器并修改配置
需要挂载配置、数据、和日志文件,可以自行建立文件夹或者不建立,运行时自动创建
bash
docker run -p 2181:2181 --name zookeeper --privileged=true -v /usr/local/docker/zookeeper/data:/data -v /usr/local/docker/zookeeper/conf:/conf -v /usr/local/docker/zookeeper/logs:/datalog -itd 36c607e7b14d
配置如下:基本不用修改
dataDir=/data
dataLogDir=/datalog
clientPort=2181
tickTime=2000
initLimit=5
syncLimit=2
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
maxClientCnxns=60
standaloneEnabled=true
admin.enableServer=true
server.1=localhost:2888:3888;2181
zookeeper数据模型
zookeeper 是一个树形目录服务,其数据模型和linux的文件系统目录树很类似,拥有一个层次化结构,这里面的每一个节点都被称为: ZNode ,每个节点上都会保存自己的数据和节点信息,节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下
节点可以分为四大类:
- PERSISTENT持久化节点
- EPHEMERAL临时节点:-e
- PERSISTENT SEQUENTIAL持久化顺序节点:-s
- EPHEMERAL SEQUENTIAL临时顺序节点:-es
临时 -e 顺序-s 属于是编号的顺序节点,临时顺序节点也是实现分布式锁的基础所在
zookeeper客户端操作常用命令
进入客户端
进入容器内部
docker exec -it zookeeper /bin/bash
进入bin目录
./zkCli.sh
常用客户端命令
显示指定目录下节点
ls 目录
创建节点
create /节点path value
获取节点值
get /节点path
设置节点值
set /节点path value
删除单个节点
delete /节点path
删除带有子节点的节点
deletea1l /节点path
创建临时节点
create -e /节点path value
创建顺序节点
create -s /节点path value
查看节点详情
Is -s /节点path
Java操作zookeeper
Curator介绍
Curator是ApacheZooKeeper的Java客户端库,Curator 项目的目标是简化ZooKeeper客户端的使用,封装了原生javaAPI,Curator最初是Netfix 研发的,后来捐献了Apache基金会目前是Apache的顶级项目。
Curator操作zookeeper
引入依赖
XML
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
创建连接
创建连接有两种方式:
- CuratorFrameworkFactory.newClient
- CuratorFrameworkFactory.builder()
java
//重试策略
Reol+=cetrypolicy = new ExponentialBockoffretry(300,10);CuratorFramework
// 第一种方式创建连接
client = CuratorFrameworkFactory,newClient("192.168.xx.xx:2181"6 * 1000,15*1000,retryPolicy);*
// 第二种方式
client = CuratorFrameworkFactory.builder()
.connectString("localhost:2181") // 连接字符串。zk server 地址和端口 "192.168.xx.xx:2181,192.168.xx.xx:2181"
.sessionTimeoutMs(60 * 1000) // 会话超时时间 单位ms
.connectionTimeoutMs(15 * 1000) // 连接超时时间 单位ms
.retryPolicy(retryPolicy) // 重试策略
.namespace("ceshi") // 根目录,后续的操作都在/ceshi下进行
.build();
//开启连接
client.start();
创建节点
创建节点的多种方式:
- 基本创建 :create().forPath("") 无数据,值存储ip
- 创建节点 带有数据:create().forPath("",data)
- 设置节点的类型:create().withMode().forPath("",data) 默认持久化,可指定
- 创建多级节点 /app1/p1 :create().creatingParentsIfNeeded().forPath("",data) 父节点不存在,创建父节点
java
//基本创建
String path = client.create().forPath("/app1");
System.out.println(path);
//创建节点 带有数据
String path = client.create().forPath("/app2", "HelloWorld".getBytes());
System.out.println(path);
//设置节点的类型(默认类型:持久化)
// 创建临时节点
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
//创建多级节点 /app1/p1
//creatingParentsIfNeeded():如果父节点不存在,则创建父节点
String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1");
System.out.println(path);
查询节点
查询节点的多种方式:
- 查询数据:getData().forPath()
- 查询子节点: getChildren().forPath()
- 查询节点状态信息:getData().storingStatIn(状态对象).forPath()
java
//查询数据
byte[] data = client.getData().forPath("/app1");
System.out.println(new String(data));
// 查询子节点
List<String> path = client.getChildren().forPath("/");
System.out.println(path);
//查询节点状态信息
Stat status = new Stat();
client.getData().storingStatIn(status).forPath("/app1");
System.out.println(status);
修改节点数据
修改数据:
- 基本修改数据:setData().forPath()
- 根据版本修改: setData().withVersion().forPath()。 version 是通过查询出来的。目的就 是为了让其他客户端或者线程不干扰
java
//基本修改数据:setData().forPath()
client.setData().forPath("/app1", "ceshi".getBytes());
// 根据版本修改
Stat status = new Stat();
int version = status.getVersion();
System.out.println(version);
client.setData().withVersion(version).forPath("/app1", "ceshi".getBytes());
删除节点
删除节点 delete deleteall
- 删除单个节点:delete().forPath("/app1");
- 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");
- 必须成功的删除:为了防止网络抖动,就是重试,可以添加回调,回调是inBackground client.delete().guaranteed().forPath("/app2");
java
// 删除单个节点
client.delete().forPath("/app1");
//删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app4");
//必须成功的删除
client.delete().guaranteed().forPath("/app2");
//回调
client.delete().guaranteed().inBackground(new BackgroundCallback(){
@Override
public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
System.out.println("我被删除了~");
System.out.println(event);
}
}).forPath("/app1");
Curator监听Watch
之ookeeper允许用户在指定的事件注册Watcher,当一些特定的事件zookeeper服务端将通知感兴趣的客户端上,该机制是Zoookeeper实现分布式协调服务的重要特性
Zookeeper引入了Watcher机制来实现发布和订阅功能,能够让多个订阅者同时监听某个对象,当对象发生改变时会通知所有订阅者
Curator引入了 Cache 来实现对 ZooKeeper 服务端事件的监听。提供了三种Watcher:
- NodeCache : 只是监听某一个特定的节点
- PathChildrenCache : 监控一个ZNode的子节点.
- TreeCache : 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合
java
//1. 创建NodeCache对象
final NodeCache nodeCache = new NodeCache(client,"/app1");
//2. 注册监听
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
System.out.println("节点变化了~");
//获取修改节点后的数据
byte[] data = nodeCache.getCurrentData().getData();
System.out.println(new String(data));
}
});
//3. 开启监听.如果设置为true,则开启监听,加载缓冲数据
nodeCache.start(true);
zookeeper实现分布式锁
zookeeper实现分布式锁概述
多个进程共享同一个资源的时候,需要添加分布式锁来解决问题,模拟12306卖票通过curator实现分布式锁,核心思想就是,客户端需要获取锁,创建临时顺序节点,使用完删除
在Curator中有五种锁方案:
- InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
- InterProcessMutex:分布式可重入排排它锁
- InterProcessReadWriteLock: 分布式读写锁
- InterProcessMultilock: 将多个锁作为单个实体管理的容器
- InterProcessSemaphoreV2: 共享信号量
zookeeper分布式锁实现原理
- 客户端获取锁时,在lock节点下创建临时顺序节点(默认创建好了/lock节点)
- 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
- 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
- 如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。
总结:创建临时节点的意义在于,节点宕机之后可自行释放,创建顺序节点的意义在于顺序最小的节点就是锁,如果不是最小的则监听删除事件
zookeeper实现分布式锁
java
package com.dingjiaxiong.curator;
import org.apache.curator.RetryPolicy;
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;
import java.util.concurrent.TimeUnit;
/**
* ClassName: Ticket12306
* date: 2023/8/18 13:18
*
* @author sl
*/
public class Ticket12306 implements Runnable {
private int tickets = 10; //数据库的票数
private InterProcessMutex lock;
public Ticket12306() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("zookeeper的IP :2181")
.sessionTimeoutMs(60 * 1000)
.connectionTimeoutMs(15 * 1000)
.retryPolicy(retryPolicy)
.build();
client.start();
lock = new InterProcessMutex(client, "/lock");
}
@Override
public void run() {
//一直对外提供服务
while (true) {
try {
//获取锁
lock.acquire(3, TimeUnit.SECONDS);
if (tickets > 0) {
System.out.println(Thread.currentThread() + ":" + tickets);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
// 释放锁
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
开启两个线程去买票,不会出现超卖或者重复卖票的现象
zookeeper集群搭建
zookeeper集群选举leader策略
Serverid: 服务器ID
比如有三台服务器,编号分别是1,2;3编号越大在选择算法中的权重越大
Zxid: 数据ID
服务器中存放的最大数据D.值越大说明数据 越新在选举算法中数据越新权重越大
选定leader
在Leader选举的过程中,如果某台zooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了,有新的节点加入不会重新选举leader
zookeeper集群配置
由于服务器内存有限,此处只列举配置,不做真实搭建
在每个zookeeper的 data 目录下创建一个 myid 文件,内容分别是123,这个文件就是记录每个服务器的ID
echo 1 >/usr/1ocal/docker/zookeeper-1/data/myid
echo 2 >/usr/1ocal/docker/zookeeper-2/data/myid
echo 3 >/usr/1ocal/docker/zookeeper-3/data/myid
在每一个zookeeper 的 zoo.cfg配置客户端访问端口 (clientPort) 和集群服务器IP列表.集群服务器IP列表如下
server.1=192.168.xx.xx:2881:3881
server .2=192.168.xx.xx:2882:3882
server .3=192.168.xx.xx:2883:3883
分别启动zookeeper即可
zookeeper集群角色
在ZooKeeper集群服中务中有三个角色:
Leader领导者
- 处理务请求
- 集群内部各服务器的调度者
Follower跟随者
- 处理客户端非事务请求,转发事务请求给Leader服务器
- 参与Leader选举投票
Observer观察者
- 处理客户端非事务请求,转发事务请求给Leader服务器,分担Follower的压力