写在前面:
1.常量以_开通,所有字母大写
2.zookeeper的服务器通信端口和投票端口怎么设置/查看
1. 安装
下载 bin.tar.gz结尾的安装包,此后缀是编译后的安装包
tar -xvf xxxxx.tar.gz -C PATH_NAME #解压到指定文件夹
cd conf/
touch zoo.cfg
cat zoo_sample.cfg >> zoo.cfg
vim zoo.cfg #修改相关配置,重要是dataDir 端口等
2. 终端命令
2.1 服务端
./zkServer.sh start #开启 stop终止 restart重启
./zkServer.sh status #查看状态
zookeeper是一个分布式文件系统,可以理解成一个数据库,采用和Unix类似的树形目录;zookeeper存储的表现形式是ZNode,ZNode的名称即当前的path,ZNode可以存储小于1M的数据。
ZNode组成:
1)stat 状态
2)data 数据
3)children 子node列表
连接服务端的方式有两种:
1)zkCli.sh -server _IP:_PORT
2)java API: curator
节点分类(持久性&有序性):
1)PERSISTENT 持久化节点
2)EPHEMERAL 临时节点 -e 临时指仅在当前会话中有效,不能有子node
3)PERSISTENT_SEQUENTIAL 持久化顺序节点 -s
4) EPHEMERAL_SEQUENTIAL 临时顺序节点 -es
2.2 客户端
增删改查:create delete/deleteall set get/ls
./zkCli.sh -server _IP:_PORT
help #查看命令
quit #断开
########节点操作###########
create _NODE_PATH _NODE_VALUE #增 创建节点并赋值
delete _NODE_PATH # 删 删除单节点
deleteall _NODE_PATH #删 递归删除节点
set _NODE_PATH _NODE_VALUE #改 设置节点值
ls _DIR #查 查看目录下的节点
ls -R _DIR #查 递归查看
get _NODE_PATH #查 查看节点值
3. java API : curator
3.1 坐标
注意curator版本和zookeeper的版本兼容问题,zookeeper版本迭代较快,容易出兼容性问题
<!--坐标-->
<!--curator 注意zookeeper的版本不要过高,否则不兼容-->
<!--zookeeper的底层API-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<!--zookeeper的高级特性API如:Cache事件监听、选举、分布式锁、分布式计数器等-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
<!--最好导入日志的坐标,不然可能出现报错-->
3.2 节点操作
- 连接
public class CuratorTest{
CuratorFramework client;
/*方式一
*@param connecString zkserver的IP和端口,多个用逗号隔开
*@param sessionTimeoutMs 会话超时时间 ms
*@param connectionTimeoutMs 连接超时时间 ms
*@param retryPolicy 重试连接策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10);
client = CuratorFrameworkFactory.newClient(
"127.0.0.1:2181,192.168.0.1:2181",
1000,
2000,
retryPolicy
);
*/
//方式二 工厂模式
client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(1000)
.connectionTimmeoutMs(1000)
.retryPolicy(retryPolicy)
.namespace("zookeeperCluster")
.build(); //namespace相当于指定一个父ZNode,在一段时间内一直没有子节点的话会自动删除
//开启连接
client.start();
}
- 创建节点
//不指定节点数据则将client的IP作为数据
String path1 = client.create().forPath("/app1");
//创建带数据的节点
String path2 = client.create().forPath("/app1","casey".getBytes());
//创建带类型的节点,默认类型是persistent
String path23 = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app1","casey".getBytes());
//创建多级节点
String path4 = client.create().creatingParentsIfNeeded().forPath("/app1/a","casey".getBytes());
- 删除节点 delete deleteall
//删除单个节点 delete
client.delete().forPath("/app1");
//递归删除节点 deleteall
client.delete().deletingChildrenIfNeeded().forPath("/app1");
//必须成功删除节点,防止网络抖动删除节点失败
client.delete().guaranteed().forPath("/app1");
//删除后回调,删除后回调方法,相当于删除触发事件
client.delete().guaranteed().inBackground((CuratorFramework client, CuratorEvent event)->{System.out.println("ok");}).forPath("/app1");//inBackground方法的参数是 BackgroundCallback接口的实现类
- 修改节点
//直接修改数据
client.setData().forPath("/app1","ccc".getBytes());
//【推荐】根据版本(dataVersion)修改数据,解决数据的同步问题
Stat status = new Stat();
client.getData().storingStatIn(status)
.forPath("/app1");
int version = status.getVersion(); client.setData()
.withVersion(version)
.forPath("/app1","ccc".getBytes());
- 查询节点
//查数据 get
byte[] data = client.getDate().forPath("/app1");
String s = new String(data);
System.out.println(s);
//查询子节点 ls [-R]
List<String> paths = client.getChildren().forPath("/app1");
//查询节点状态 zk旧版直接shell用get就可以,新版要用ls -s
Stat status = new Stat();
client.getData().storingStatIn(status)
.forPath("/app1");
- 释放资源
if(client != null ){
client.close();
}
3.2.5 watch机制
ZooKeeper 允许用户(节点)在服务端指定节点上注册一些Watcher,并且在一
些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制
是 ZooKeeper 实现分布式协调服务的重要特性,client收到通知后回去获取最新数据。
应用:
发布订阅系统
监听主机存活状态
watcher类型:
- NodeCache 监听一个节点
- PathChildrenCache: 节点的子节点(不包括该节点)
- TreeCache: 前两个的并集
get _ZNode watch #给节点添加watch机制,一次性的,用完后有需求要再添加
/*与上面的增删改查使用一个连接和资源释放*/
//NodeCache
//1.创建NodeCache对象
NodeCahe nodeCache = new NodeCache(client , "/app1");
//2.注册监听
nodeCache.getListenable().addListener(new NodeCacheListener(){
@override
public void nodeChange() throws Exception{
System.out.println("NODE CHANGED");
//获取修改后的数据
bytes newData = nodeCache.getCurrentData().getData();
System.out.println(new String(newData));
}
})
//3.开启监听
nodeCache.start(true); //true表示开启监听时加载缓冲数据
//=================================================================
//PathChildrenCache
//1.创建PathChildrenCache对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client , "/app1", true);
//2.注册监听
PathChildrenCache.getListenable().addListener(new PathChildrenCacheListener(){
@override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception{
System.out.println("Children Node CHANGED");
//使用event
PathChildrenCacheEvent.Type type = event.getType(); // Type是一个枚举
if(PathChildrenCacheEvent.Type.CHILD_UPDATED.equals(type)){
byte[] data = event.getData().getData();
System.out.println(new String("变更后数据为:" + data));
}
}
})
//3.开启监听
pathChildrenCache.start(); //true表示开启监听时加载缓冲数据
//=================================================================
//TreeCache
//1.创建TreeCache对象
TreeCache treeCache = new TreeCache(client,"/app1");
//2.注册监听
treeCache.getListenable().addListener(new TreeCacheListener(){
@override
public void childEvent(CuratorFramework client,TreeCacheEvent event) throws Exception{
System.out.println("xxx Node CHANGED");
}
})
//开启监听
treeCache.start();
触发watch后,watcher会发给client一个信息,封装了:keeper state、event type、触发条件,详细附录二
3.3 分布式锁
通过节点进行锁的控制,•核心思想:节点!
当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
1.客户端获取锁时,在lock节点(节点名字随便起)下创建临时(断开会话后自动删除,防止client挂掉无法主动删或忘了删)& 顺序(该每个请求的锁排优先级)节点。
2.然后客户端获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小(创建时机最靠前),那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
3.如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
4.如果发现比自己小的那个节点被删除,则客户端的
- Watcher会收到相应通知,此时再次判断自己创建的节点
- 是否是lock子节点中序号最小的,如果是则获取到了锁,
- 如果不是则重复以上步骤继续获取到比自己小的一个节点,并注册监听。
-
InterProcessSemaphoreMutex:分布式排它锁(非可重入)
-
InterProcessMutex:分布式可重入排它锁,可重入即获得过一次锁,会再次获得,不用竞争
-
InterProcessReadWriteLock:分布式读写锁
-
InterProcessMultiLock:将多个锁作为单个实体管理的容器,多个锁组合成一个小组
-
InterProcessSemaphoreV2:共享信号量
public class Ticket12306 implements Runnable{
private int tickets = 10;//数据库的票数
private InterProcessMutex lock ; //可重入排他锁
public Ticket12306(){//构造方法
//重试策略
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//2.第二种方式
//CuratorFrameworkFactory.builder();
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("192.168.149.135: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);
Thread.sleep(100);
tickets--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放锁
try {
lock.release();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
4. zookeeper集群
4.1 相关概念
架构如下:
角色描述如下:
observer 可以不配置,一般只配置leader和follower。
4,2 相关机制
4.3 集群搭建
- 在每个zookeeper的配置文件的dataDir对应目录下添加当前服务器id即myid ,用于投票和服务器编号,myid越大,选取权重越高
echo _MY_ID > _dataDIR_PATH/myid
- 在集群的服务端配置文件注册
每个zookeeper的zoo.cfg中配置集群服务器的IP、服务器通信端口(注意不是ClientPort,而是服务器之间使用的端口)、投票端口
dataDir=/export/servers/zookeeper-3.4.9/zkdatas
# 保留多少个快照
autopurge.snapRetainCount=3
# 日志多少小时清理一次
autopurge.purgeInterval=1
# 集群中服务器地址
server.1=192.168.174.101:2888:3888 # server.myid=IP:serverPort:votePort
server.2=192.168.174.102:2888:3888
server.3=192.168.174.103:2888:3888
- 开启集群每个服务端
./zkServer.sh start
./zkServer.sh status #查看是否产生leader等
附录一 zkCli相关shell命令
附录二 watch机制触发的返回值