个人博客:www.hellocode.top
Java知识导航:Java-Navigate
⭐想获得更好的阅读体验请前往Java-Navigate
本文专栏:《流行框架》
如没有JavaWEB基础,请先前往《Java Web从入门到实战》专栏学习相应知识
⚡如有问题,欢迎指正,一起学习~~
安装与启动参看Dubbo注册中心部分
节点可以分为四大类
./zkServer.sh start
./zkServer.sh status
./zkServer.sh stop
./zkServer.sh restart
基本CRUD
./zkCli.sh -server ip:2181
(连接本机的话不需要-server ip:端口
)quit
ls /
ls /dubbo/config
help
create /节点名 [节点数据]
get /节点名
set /节点名 数据
delete /节点名
当被删节点含有子节点时,不能使用delete删除,需要使用
deleteall
命令
临时顺序节点
create -e /节点名
create -s /节点名
create -es /节点名
ls2 /路径
(已经被淘汰,替换为:)ls -s /路径
简介
Curator 是 Apache ZooKeeper 的Java客户端库
常见的ZooKeeper Java API
Curator项目的目标是简化ZooKeeper客户端的使用
Curator 最初是Netfix 研发的,后来捐献了Apache基金会,目前是Apache的顶级项目
官网:http://curator.apache.org/
常用操作
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>4.0.0version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-recipesartifactId>
<version>4.0.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.21version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
建立连接
@Test
public void testConnect(){
/*
connectString 连接字符串。zk server 地址和端口(多个用逗号隔开) "192.168.23.129:2181,192.131.34.168:2181"
sessionTimeoutMs 会话超时时间 单位ms
connectionTimeoutMs 连接超时时间 单位ms
retryPolicy 重试策略
*/
// 第一种方式
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
// CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.23.129:2181",
60 * 1000, 15 * 1000, retryPolicy);
// 开启连接
// client.start();
// 第二种方式
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.23.129:2181")
.sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000).retryPolicy(retryPolicy).namespace("hellocode").build();
client.start();
}
第二种方式指定namespace可以简化操作,让指定的名称空间作为根目录
添加节点
public class CuratorTest {
private CuratorFramework client;
@Before
public void testConnect(){
/*
connectString 连接字符串。zk server 地址和端口(多个用逗号隔开) "192.168.23.129:2181,192.131.34.168:2181"
sessionTimeoutMs 会话超时时间 单位ms
connectionTimeoutMs 连接超时时间 单位ms
retryPolicy 重试策略
*/
// 第一种方式
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
// CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.23.129:2181",
// 60 * 1000, 15 * 1000, retryPolicy);
// // 开启连接
// client.start();
// 第二种方式
client = CuratorFrameworkFactory.builder().connectString("192.168.23.129:2181")
.sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000).retryPolicy(retryPolicy).namespace("hellocode").build();
client.start();
}
/**
* 创建节点:create 持久 临时 顺序
* 1. 基本创建:create().forPath("")
* 2. 创建节点 带有数据:create().forPath("",data)
* 3. 设置节点的类型:create().withMode(节点类型).forPath("",data)
* 4,创建多级节点:create().withMode(节点类型).creatingParentsIfNeeded().forPath("",data)
*/
@Test
public void testCreate() throws Exception {
// 1. 基本创建:create().forPath("")
// 如果创建节点没有指定数据,则默认当前客户端的ip作为数据存储
String path = client.create().forPath("/app1");
System.out.println(path);
}
@Test
public void testCreate2() throws Exception {
// 2. 创建节点 带有数据:create().forPath("",data)
// 如果创建节点没有指定数据,则默认当前客户端的ip作为数据存储
String path = client.create().forPath("/app2","Hello World".getBytes());
System.out.println(path);
}
@Test
public void testCreate3() throws Exception {
// 3. 设置节点的类型:create().withMode(节点类型).forPath("",data)
// 如果创建节点没有指定数据,则默认当前客户端的ip作为数据存储
String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
System.out.println(path);
}
@Test
public void testCreate4() throws Exception {
// 4,创建多级节点:create().withMode(节点类型).creatingParentsIfNeeded().forPath("",data)
// 如果创建节点没有指定数据,则默认当前客户端的ip作为数据存储
String path = client.create().creatingParentsIfNeeded().forPath("/app3/p1");
System.out.println(path);
}
@After
public void close(){
if(client != null){
client.close();
}
}
}
查询节点
/**
* 查询节点:
* 1. 查询数据:get:getData().forPath()
* 2. 查询子节点:ls:getChildren().forPath()
* 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
*/
@Test
public void testGet1() throws Exception {
// 1. 查询数据:get:getData().forPath()
byte[] bytes = client.getData().forPath("/app2");
System.out.println(new String(bytes));
}
@Test
public void testGet2() throws Exception {
// 2. 查询子节点:ls:getChildren().forPath()
List<String> childrens = client.getChildren().forPath("/");
System.out.println(childrens);
}
@Test
public void testGet3() throws Exception {
// 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath("/app1");
System.out.println(stat);
}
修改节点
/*
* 修改数据
* 1. 修改数据 :setData().forPath()
* 2. 根据版本修改:setData().withVersion(version).forPath()
* version是通过查询出来的。目的是为了让其他客户端或者线程不干扰当前操作
* */
@Test
public void testSet() throws Exception {
Stat stat = client.setData().forPath("/app1", "hehe".getBytes(StandardCharsets.UTF_8));
System.out.println(stat);
}
@Test
public void testForVersion() throws Exception {
Stat stat = new Stat();
client.getData().storingStatIn(stat).forPath("/app1");
client.setData().withVersion(stat.getVersion()).forPath("/app1", "haha".getBytes(StandardCharsets.UTF_8));
}
删除节点
/*
* 删除节点 delete deleteall
* 1. 删除单个节点
* 2. 删除带有子节点的节点
* 3. 必须成功的删除(防止网络抖动)
* 4. 回调
* */
@Test
public void testDelete() throws Exception {
// 1. 删除单个节点
client.delete().forPath("/app1");
}
@Test
public void testDelete2() throws Exception {
// 2. 删除带有子节点的节点
client.delete().deletingChildrenIfNeeded().forPath("/app3");
}
@Test
public void testDelete3() throws Exception {
// 3. 必须成功的删除(防止网络抖动)
client.delete().guaranteed().forPath("/app2");
}
@Test
public void testDelete4() throws Exception {
// 4. 回调
client.delete().guaranteed().inBackground(new BackgroundCallback() {
@Override
public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
System.out.println(curatorEvent);
}
}).forPath("/app2");
}
NodeCache
@Test
public void testNodeCache() throws Exception {
// 1. 创建NodeCache对象
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));
}
});
nodeCache.start();
}
PathChildrenCache
@Test
public void testPathChildrenCache() throws Exception {
PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
System.out.println("子节点变化了~~~");
if(pathChildrenCacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("子节点更改了~~~");
System.out.println(new String(pathChildrenCacheEvent.getData().getData()));
}
}
});
pathChildrenCache.start();
while (true){
}
}
TreeCache
@Test
public void testTreeCache() throws Exception {
TreeCache treeCache = new TreeCache(client,"/app2");
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
System.out.println("节点变化了");
System.out.println(treeCacheEvent);
}
});
treeCache.start();
while (true){
}
}
Curator实现分布式锁API
Curator中有五种锁方案:
模拟12306售票案例
Ticket12306
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年08月18日 11:18
*/
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("192.168.23.129:2181")
.connectionTimeoutMs(15 * 1000)
.sessionTimeoutMs(60 * 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--);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
LockTest
/**
* @author HelloCode
* @site https://www.hellocode.top
* @date 2022年08月18日 17:11
*/
public class LockTest {
public static void main(String[] args) {
Ticket12306 ticket = new Ticket12306();
// 创建客户端
Thread t1 = new Thread(ticket,"携程");
Thread t2 = new Thread(ticket,"飞猪");
t1.start();
t2.start();
}
}
要求
真实的集群是需要部署在不同的服务器上的,但是在我们测试时同时启动很多个虚拟机内存会吃不消,所以我们通常会搭建伪集群,也就是把所有的服务都搭建在一台虚拟机上,用端口进行区分
配置集群
在每个zookeeper的data目录下创建一个myid文件,内容分别是1、2、3。这个文件就是记录每个服务器的id
echo 1 > /usr/local/zookeeper-cluster/zookeeper-1/data/myid
echo 2 > /usr/local/zookeeper-cluster/zookeeper-2/data/myid
echo 3 > /usr/local/zookeeper-cluster/zookeeper-3/data/myid
在每一个zookeeper的zoo.cfg配置客户端访问端口(clientPort)和集群服务器IP列表。
集群服务器IP列表如下
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg
server.1=192.168.23.129:2881:3881
server.2=192.168.23.129:2882:3882
server.3=192.168.23.129:2883:3883
server.服务器id=服务器IP地址:服务器之间通信端口(默认2881):服务器之间投票选举端口(默认3881)
真实环境搭建集群时的2881和3881就直接使用默认值即可
启动集群
启动集群就是分别启动每个实例
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
启动后查询一下运行状态
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status
在ZooKeeper集群服务中有三个角色: