ZooKeeper
1.定义
- Zookeeper 是 Apache Hadoop 项目下的一个子项目,是一个树形目录服务。
- 中间件
- 开源
- 分布式
2.作用
- 配置管理
- 分布式锁
- 集群管理
3.场景
- dubbo,管理微服务
- 大数据,管理kafka…
4.原理/架构
-
znode:节点可以保存数据(1M),节点分为四大类
- PERSISTENT 持久化节点
- EPHEMERAL 临时节点 :-e (客服端一断开之后,则节点自动删除)
- PERSISTENT_SEQUENTIAL 持久化顺序节点 :-s (子节点会按照数字自增的方式创建)
- EPHEMERAL_SEQUENTIAL 临时顺序节点 :-es
5.集群
- 多台服务器提供相同的服务,这样的行为称为集群
6.使用
-
命令行
- 服务端
- 启动 ZooKeeper 服务: ./zkServer.sh start
- 查看 ZooKeeper 服务状态: ./zkServer.sh status
- 停止 ZooKeeper 服务: ./zkServer.sh stop
- 重启 ZooKeeper 服务: ./zkServer.sh restart
- 客户端
- zkCli -server IP:PORT
- ls path ,path是从根开始的全路径
- -s , 查看znode详情信息
- create path value,不能创建多级目录
- -e ,临时节点
- -s , 顺序节点
- -es,临时且顺序节点
- delete path ,path是从根开始的全路径
- deleteall path ,path是从根开始的全路径
- get path,获取znode的节点值
- set path value,设置znode的节点值
- help
- 服务端
-
java API
pom.xml
<!--curator--> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency>
- 连接客户端
/* * @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181" * @param sessionTimeoutMs 会话超时时间 单位ms * @param connectionTimeoutMs 连接超时时间 单位ms * @param retryPolicy 重试策略 */ public void conTest(){ ExponentialBackoffRetry retry = new ExponentialBackoffRetry(3000,10); CuratorFramework client = CuratorFrameworkFactory.builder() .connectString("192.168.15.134:2181") .sessionTimeoutMs(1000*60*60) .connectionTimeoutMs(1000*60*10) .retryPolicy(retry) .namespace("itheima") .build(); client.start(); }
- 创建节点
/** * 创建节点:create 持久 临时 顺序 数据 * 1. 基本创建 :create().forPath("") * 2. 创建节点 带有数据:create().forPath("",data) * 3. 设置节点的类型:create().withMode().forPath("",data) * 4. 创建多级节点 /app1/p1 :create().creatingParentsIfNeeded().forPath("",data) */ @Test public void testCreate() throws Exception { //2. 创建节点 带有数据 //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储 String path = client.create().forPath("/app2", "hehe".getBytes()); System.out.println(path); } @Test public void testCreate2() throws Exception { //1. 基本创建 //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储 String path = client.create().forPath("/app1"); System.out.println(path); } @Test public void testCreate3() throws Exception { //3. 设置节点的类型 //默认类型:持久化 String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3"); System.out.println(path); } @Test public void testCreate4() throws Exception { //4. 创建多级节点 /app1/p1 //creatingParentsIfNeeded():如果父节点不存在,则创建父节点 String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1"); System.out.println(path); }
- 查询节点
/** * 查询节点: * 1. 查询数据:get: getData().forPath() * 2. 查询子节点: ls: getChildren().forPath() * 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath() */ @Test public void testGet1() throws Exception { //1. 查询数据:get byte[] data = client.getData().forPath("/app1"); System.out.println(new String(data)); } @Test public void testGet2() throws Exception { // 2. 查询子节点: ls List<String> path = client.getChildren().forPath("/"); System.out.println(path); } @Test public void testGet3() throws Exception { Stat status = new Stat(); System.out.println(status); //3. 查询节点状态信息:ls -s client.getData().storingStatIn(status).forPath("/app1"); System.out.println(status); }
- 修改节点
/** * 修改数据 * 1. 基本修改数据:setData().forPath() * 2. 根据版本修改: setData().withVersion().forPath() * * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。 * * @throws Exception */ @Test public void testSet() throws Exception { client.setData().forPath("/app1", "itcast".getBytes()); } @Test public void testSetForVersion() throws Exception { Stat status = new Stat(); //3. 查询节点状态信息:ls -s client.getData().storingStatIn(status).forPath("/app1"); int version = status.getVersion();//查询出来的 3 System.out.println(version); client.setData().withVersion(version).forPath("/app1", "hehe".getBytes()); }
- 删除节点
/** * 删除节点: delete deleteall * 1. 删除单个节点:delete().forPath("/app1"); * 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1"); * 3. 必须成功的删除:为了防止网络抖动。本质就是重试。 client.delete().guaranteed().forPath("/app2"); * 4. 回调:inBackground * @throws Exception */ @Test public void testDelete() throws Exception { // 1. 删除单个节点 client.delete().forPath("/app1"); } @Test public void testDelete2() throws Exception { //2. 删除带有子节点的节点 client.delete().deletingChildrenIfNeeded().forPath("/app4"); } @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 client, CuratorEvent event) throws Exception { System.out.println("我被删除了~"); System.out.println(event); } }).forPath("/app1"); }
7.Watch事件监听
-
ZooKeeper 允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候 , ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
-
三种Watcher
- NodeCache:只监听特定节点的修改、删除、创建事件
- PathChildenCache:监听特定节点的子节点增加、修改、删除等事件(监听具体哪种事件可参考PathChildrenCacheEvent.Type枚举类)
- TreeCache:监听所有节点的添加、删除、修改等事件(监听具体哪种事件可参考TreeCacheEvent.Type枚举类)
/**
* 演示 NodeCache:给指定一个节点注册监听器
*/
@Test
public void testNodeCache() throws Exception {
//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);
while (true){}
}
/**
* 演示 PathChildrenCache:监听某个节点的所有子节点们
*/
@Test
public void testPathChildrenCache() throws Exception {
//1.创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true);
//2. 绑定监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
System.out.println("子节点变化了~");
System.out.println(event);
//监听子节点的数据变更,并且拿到变更后的数据
//1.获取类型
PathChildrenCacheEvent.Type type = event.getType();
//2.判断类型是否是update
if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("数据变了!!!");
byte[] data = event.getData().getData();
System.out.println(new String(data));
}
}
});
//3. 开启
pathChildrenCache.start();
while (true){}
}
/**
* 演示 TreeCache:监听某个节点自己和它的所有子节点们
*/
@Test
public void testTreeCache() throws Exception {
//1. 创建监听器
TreeCache treeCache = new TreeCache(client,"/app2");
//2. 注册监听
treeCache.getListenable().addListener(new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
System.out.println("节点变化了");
System.out.println(event);
}
});
//3. 开启
treeCache.start();
while (true){}
}
- 1、PathChildenCache能否监听跨级的子节点?
- 不能
- 2、TreeCache监听的是子树还是整个zk树?
- 子树
8.分布式锁
-
定义:协调跨机器(JVM)之间的数据同步
-
原理(面试题):
- 临时:是为了防止偶发宕机情况下,锁对象不能释放而造成的死锁现象
- 顺序:是为对象提供了排队功能
-
核心思想:当客户要获取锁,则创建节点,使用完锁,则删除该节点
- 在客户端获取锁时,在指定节点下(这里举例为lock节点)创建临时顺序节点
- 客户端获取lock节点下的所有子节点,比较自己创建的节点是否是序号最小的,如果是则获取锁
- 如果客户端发现自己不是序号最小的,则去找到比自己小的那个相邻的节点,并对其绑定watch监听事件,监听的是删除事件
- 如果监听到比自己晓得节点被删除了,此时还会再次判断自己是否是最小序号的节点,如果是,则获取锁
-
在单击应用开发是我们利用synchronized或者Lock的方式来解决多线程的并发访问问题,这是是运行在同一个JVM下的.然而实际上我们的应用大多是分布式集群工作的,属于多JVM的工作环境,这就需要一种更加高级的锁,这就是分布式锁.
-
模拟12306购票案例
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(); } } } } }
public class LockTest { public static void main(String[] args) { Ticket12306 ticket12306 = new Ticket12306(); //创建客户端 Thread t1 = new Thread(ticket12306,"携程"); Thread t2 = new Thread(ticket12306,"飞猪"); t1.start(); t2.start(); } }