文章目录
大家有兴趣可以看看3y大佬的文章,讲解什么是Zookeeper,强烈推荐 https://zhuanlan.zhihu.com/p/62526102
一、Zoookeeper简介
为什么需要Zoookeeper
集群、可靠
当信息还没同步完成时,不对外提供服务
同步的时间压缩的更短
Zookeeper诞生历史
无单点问题的分布式协调框架,精力集中在处理业务逻辑
内部很多项目都是使用动物的名字来命名
大型的动物园
Zookeeper是什么?
ZooKeeper: A Distributed Coordination Service forDistributed Applications
Zookeeper是开源的高性能的分布式应用协调系统,一个高性能的分布式数据一致性解决方案
5大特点
顺序一致性
原子性
单一视图
可靠性
及时性
Zookeeper和CAP的关系
CP:一致性+分区容错性
能得到一致的数据结果,同时系统对网络具备容错性
但是它不能保证每次服务请求的可用性
作用
分布式服务注册与订阅
统一配置文件
生成分布式唯一ID
Master节点选举
分布式锁
二、Zookeeper的安装和配置
1、Linux 下安装
1)、安装 wget https://downloads.apache.org/zookeeper/zookeeper-3.6.1/apache-zookeeper-3.6.1-bin.tar.gz
如果下载速度慢、或者下载失败建议本地下载好后,在上传到Linux:
下载地址:http://archive.apache.org/dist/zookeeper/
tar zxvf apache-zookeeper-3.6.0-bin.tar.gz
cd apache-zookeeper-3.6.0-bin
cp conf/zoo_sample.cfg conf/zoo.cfg
2)、配置 zoo.cfg
vi conf/zoo.cfg
把内容修改为:
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
3)、启动
./bin/zkServer.sh start
4)、停止
./bin/zkServer.sh stop
2、windows 下安装
下载 zookeeper
网址 https://zookeeper.apache.org/releases.html
下载 3.6.0
解压 zookeeper
解压运行 zkServer.cmd ,初次运行会报错,没有 zoo.cfg 配置文件
修改 zoo.cfg 配置文件
将 conf 下的 zoo_sample.cfg 复制一份改名为 zoo.cfg 即可。
注意几个重要位置:
dataDir=./ 临时数据存储的目录(可写相对路径)
clientPort=2181 zookeeper 的端口号
修改完成后再次启动 zookeeper,运行 zkServer.cmd
三、基本使用
1、znode节点
节点的性质:
- 树形结构,也可以理解为linux的文件目录
- 每一个节点都是znode,里面可以包含数据,也可以有子节点
- 点分为永久节点和临时节点( session失效,也就是客户端断开后,临时节点消失)
- 每个znode都有版本号,每当数据变化,版本号会累加(乐观锁)
- 删除或修改节点,版本号不匹配的话(版本号已过时),会报错
- 每个节点存储的数据不宜过大,几K即可
- 节点可以设置权限,来限制用户的访问
- Zookeeper保证读和写都是原子操作,且每次读写操作都是对数据的完整读取或完整写入
节点的类型:
- 持久节点
- 临时节点
- 顺序节点
节点的属性:
- dataVersion
- cversion
- aclVersion
2、常用命令
基础命令
启动:
./bin/zkServer.sh start
可能出现的问题:
关闭防火墙即可
systemctl stop firewalld
连接到zk server
./bin/zkCli.sh -server 127.0.0.1:2181
或
./bin/zkCli.sh
Ctrl+c可退出
查看节点的状态 stat /
查看节点ls /
查看节点的数据和状态 get
创建、修改、删除节点
高级命令
create -s的使用:
判断是否为持久节点(stat /):
创建临时节点(create -e):
临时节点呢,当我们结束这次会话的时候,zookeeper且以完成刷新,可以发现这个tmp的节点就已经不存在了。
条件更新(乐观锁):(set -v)
当已经有一个线程通过版本1更改了cx节点的值为6,而另外的节点在同一时间也拿着版本1更改cx节点为7,会出现version No is not valid。
删除delete /:
3、watcher机制
- 触发器、监督者
- 使用场景:统一资源配置
Watcher事件类型
ACL
- access control list 权限控制
- 它使用权限位来允许/禁止对节点及其所作用域的各种操作
- ACL仅与特定的znode有关,与子节点无关
Scheme
- ACL : [scheme采用的权限机制:id用户:permissions权限组合字符串]
- world
- auth
- digest
- ip
- super
权限字符串crdwa
- Create
- Read
- Delete
- Write
- Admin
权限使用场景
- 区分开发/测试/运维环境,防止误操作
- 可以针对不同IP而产生具体的配置,更安全
四、代码实操
- 利用ZK原生的Java的API
- 利用Apache Curator作为客户端来操作ZK
1、Java原生客户端连接到ZK
IDEA创建Maven项目
引入依赖pom.xml:
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
log4j.properties:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss} %p %c{2}: %m%n
ZKConnect .java:
public class ZKConnect implements Watcher {
public static final String SERVER_PATH = "192.168.17.132:2181";
public static final Integer TIMEOUT = 5000;
public static void main(String[] args) throws IOException, InterruptedException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKConnect());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
System.out.println(zk.getState());
}
@Override
public void process(WatchedEvent event) {
System.out.println("收到了通知" + event);
}
}
2、用代码对节点进行操作
ZKOperator.java:
public class ZKOperator implements Watcher {
public static final String SERVER_PATH = "192.168.17.132:2181";
public static final Integer TIMEOUT = 20000;
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKConnect());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
/**
* path:创建的路径
* data:存储的数据
* acl:权限、开放
* createMode:永久、临时、顺序
*/
zk.create("/cx-create-node", "lk".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
@Override
public void process(WatchedEvent event) {
}
}
获取刚才创建节点的内容:
byte[] data = zk.getData("/cx-create-node", null, null);
System.out.println(new String(data));
修改该节点的值:
zk.setData("/cx-create-node", "lkaicx".getBytes(), 0);
当我们再次进行更改但我们不对版本号进行更改时:
删除节点(删除也需要版本号对应):
为了便于我们观察删除节点是否成功,我们需要在多写一个类:
DeleteCallBack.java:
import org.apache.zookeeper.AsyncCallback.VoidCallback;
public class DeleteCallBack implements VoidCallback {
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("删除节点" + path);
System.out.println((String) ctx);
}
}
ZKOperator.java:
String ctx = "删除成功";
zk.delete("/cx-create-node", 1, new DeleteCallBack(), ctx);
// 由于callback往往是异步的,所以为了达到我们想要的效果(打印删除节点和打印我们传过去的删除成功),需要睡上几秒
Thread.sleep(20000);
3、处理Watcher事件
和节点相关:是否存在,获取数据,加上watch
ZKGetNode.java:
public class ZKGetNode implements Watcher {
public static final String SERVER_PATH = "192.168.17.132:2181";
public static final Integer TIMEOUT = 50000;
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKConnect());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(20000);
System.out.println(zk.getState());
Stat exists = zk.exists("/cx-create-node", false);
if (exists != null) {
System.out.println("节点的版本为:" + exists.getVersion());
} else {
System.out.println("改节点不存在");
}
}
@Override
public void process(WatchedEvent event) {
System.out.println("收到了通知" + event);
}
}
ZKGetNode.java:
public class ZKGetNode implements Watcher {
public static final String SERVER_PATH = "192.168.17.132:2181";
public static final Integer TIMEOUT = 50000;
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKConnect());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(20000);
System.out.println(zk.getState());
// Stat exists = zk.exists("/cx-create-node", false);
// if (exists != null) {
// System.out.println("节点的版本为:" + exists.getVersion());
// } else {
// System.out.println("改节点不存在");
// }
zk.getData("/cx-create-node", true, null);
countDownLatch.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == EventType.NodeDataChanged) {
System.out.println("数据被改变");
countDownLatch.countDown();
}
System.out.println("收到了通知" + event);
}
}
程序运行,然后在linux进行人工更改节点cx-create-node的值:
4、用Curator操作ZK
原生的Java的API的缺点
- 不支持连接超时后的自动重连
- Watcher注册一次后会失效
- 不支持递归创建节点
利用Apache Curator
- 解决了Watcher注册一次后会失效的问题
- API更加简单易用,提供了工具类
用Curator来操作ZK实操:
引入pom.xml依赖:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
常规操作:
CuratorTests.java:
public class CuratorTests {
public static void main(String[] args) throws Exception {
// ZK服务器连接
String connectString = "192.168.17.132:2181";
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retry);
client.start();
// 创建节点
String path = "/curator";
String data = "test";
client.create().withMode(CreateMode.PERSISTENT).forPath(path, data.getBytes());
// 获取节点值
byte[] bytes = client.getData().forPath(path);
System.out.println(new String(bytes));
// 删除节点
client.delete().forPath(path);
}
}
添加监听器的形式:
CuratorTests.java:
public class CuratorTests {
public static void main(String[] args) throws Exception {
// ZK服务器连接
String connectString = "192.168.17.132:2181";
String path = "/curator";
ExponentialBackoffRetry retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retry);
client.start();
// 监听器
client.getCuratorListenable().addListener((CuratorFramework c, CuratorEvent event) -> {
switch (event.getType()) {
case WATCHED:
WatchedEvent watchedEvent = event.getWatchedEvent();
if (watchedEvent.getType() == EventType.NodeDataChanged) {
System.out.println(new String(c.getData().forPath(path)));
}
}
});
// 创建节点
String data = "test";
String data2 = "test2";
client.create().withMode(CreateMode.PERSISTENT).forPath(path, data.getBytes());
// 获取节点值
byte[] bytes = client.getData().watched().forPath(path);
System.out.println(new String(bytes));
// 修改节点的值
client.setData().forPath(path, data2.getBytes());
// 删除节点
client.delete().forPath(path);
Thread.sleep(20000);
}
}
Zookeeper的学习就到这里ヾ(✿゚▽゚)ノ