一、简介

1.概述

        Zookeeper 是一个分布式的、开源的分布式应用程序的协调服务。 

2.功能

        Zookeeper 提供的主要功能包括: 
  • 配置管理
            将多个模块的公共配置放到配置中心统一管理。
            
  • 分布式锁
  • 集群管理

3.安装与配置

        见Dubbo笔记。

二、Zookeeper的操作

1.Zookeeper数据模型

        
        ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似,拥有一个层次化结构。 这里面的每一个节点都被称为 ZNode,每个节点上都会保存自己的数据和节点信息。 
        节点可以拥有子节点,同时也允许少量(1MB)数据存储在该节点之下。 
        节点可以分为四大类:
  • PERSISTENT 持久化节点(服务器断开再重启还会存在)
  • EPHEMERAL 临时节点 :-e(服务器重启就没了,即当前会话有效)
  • PERSISTENT_SEQUENTIAL 持久化顺序节点 :-s
  • EPHEMERAL_SEQUENTIAL 临时顺序节点 :-es

2.常用命令操作

(1)Zookeeper服务端常用命令
        在bin目录下使用:
  • 启动 ZooKeeper 服务:./zkServer.sh start 
  • 停止 ZooKeeper 服务:./zkServer.sh stop 
  • 重启 ZooKeeper 服务:./zkServer.sh restart
  • 查看 ZooKeeper 服务状态:./zkServer.sh status
(2)Zookeeper客户端常用命令
  • 连接ZooKeeper服务端:./zkCli.sh –server ip地址:端口号
  • 断开连接:quit
  • 查看命令帮助:help
  • 显示指定目录下的节点:ls /目录
  • 显示指定目录下的节点及详细信息:ls –s /目录
  • 创建节点:create [-e/-s/-es]  /节点名 value
  • 获取节点值:get /节点路径
  • 设置节点值:set /节点路径 value
  • 删除空节点:delete /节点
  • 删除非空节点:deleteall /节点
【tips】①连接本机的zookeeper服务不用加-server及其后面的。
             ②默认创建的节点都是持久化节点。
(3)节点详细信息总结
        

3.Zookeeper JavaAPI操作——Curator

(1)Curator介绍

        Curator 是 Apache ZooKeeper 的Java客户端简化了ZooKeeper 客户端的使用,官网:http://curator.apache.org/
        常见的ZooKeeper Java API :
  • 原生Java API
  • ZkClient
  • Curator

(2)Curator API常用操作

        1)导入坐标
<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>
        2)建立连接
法一:
//1.创建客户端对象client
/*
newClient方法的四个形参分别为:
 * connectString:服务器url及端口号,多个用,隔开
 * sessionTimeoutMs:会话超时时间ms
 * connectionTimeoutMs:连接超时时间ms
 * retryPolicy:重试策略
 */
CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.152.100:2181",60*1000,
        15*1000,retryPolicy);
//2.开启连接
client.start();
法二:链式编程
//1.创建客户端对象client
CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.152.100:2181").sessionTimeoutMs(60 * 1000).connectionTimeoutMs(15 * 1000)
        .retryPolicy(retryPolicy).namespace("test").build();
//2.开启连接
client.start();
【tips】namespace("test")创建一个名为test的名称空间,会创建在根目录下,但是在idea中的"/"指的就是这个test目录,所有创建的新节点都会放在test目录下。
        3)创建新节点
//1.基本创建
client.create().forPath("/test1");

//2.创建带有数据的节点
client.create().forPath("/test2","data".getBytes(StandardCharsets.UTF_8));

//3.设置节点类型
client.create().withMode(CreateMode.EPHEMERAL).forPath("/test3");

//4.创建多级节点
client.create().creatingParentsIfNeeded().forPath("/test4/test4_1");
【tips】①创建的节点默认值为服务器ip地址;
             ②数据要是byte[]类型;
             ③创建多级节点时,父节点不存在的话会报错,因此需要加creatingParentsIfNeeded(),这样父节点不存在时会自动创建父节点;
             ④目录必须要以/开头。
        4)删除节点
//1.删除空节点
client.delete().forPath("/test1");

//2.删除非空节点
client.delete().deletingChildrenIfNeeded().forPath("/test2");

//3.保证删除成功,本质上就是重试机制
client.delete().guaranteed().forPath("/test1");

//4.删除回调
client.delete().inBackground(new BackgroundCallback() {
    @Override
    public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
        System.out.println("/test1被删除了");
        System.out.println(curatorEvent);
    }
}).forPath("/test1");
        5)修改节点数据
//1.修改数据
client.setData().forPath("/test1","setdata".getBytes(StandardCharsets.UTF_8));
//2.根据数据版本修改数据(推荐)
Stat status=new Stat();
client.getData().storingStatIn(status).forPath("/test1");
int version= status.getVersion();
client.setData().withVersion(version).forPath("/test1","haha".getBytes(StandardCharsets.UTF_8));
【tips】根据版本修改数据是为了避免其他客户端修改数据。
        6)查询节点
//1.获取数据
byte[] data = client.getData().forPath("/test1");
System.out.println(new String(data));

//2.查询子节点
List<String> nodes = client.getChildren().forPath("/");
System.out.println(nodes);

//3.查询节点状态信息
Stat status=new Stat();
client.getData().storingStatIn(status).forPath("/test1");
System.out.println(status);

(3)三种Watch事件监听

        ZooKeeper 允许某客户端在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。 
        ZooKeeper 中引入了Watcher机制来实现了发布/订阅功能,能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化时,会通知所有订阅者。 
        ZooKeeper 原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册Watcher,比较繁琐。
        Curator引入了Cache来实现对 ZooKeeper 服务端事件的监听。
        ZooKeeper提供了三种Watcher:
  • NodeCache:只是监听某特定节点
  • PathChildrenCache:监控某个ZNode的子节点们
  • TreeCache:可以监控整个树上的所有节点=PathChildrenCache+NodeCache
        1)NodeCache
//1.创建NodeCache对象
//三个形参分别为:客户端对象、想注册NodeCache的节点、是否压缩数据
NodeCache nodeCache = new NodeCache(client, "/testNode", true);
//2.注册监听器Listener
nodeCache.getListenable().addListener(new NodeCacheListener() {
    @Override
    public void nodeChanged() throws Exception {
        System.out.println("/testNode节点变化了");
        //获取节点变化后的信息(数据、详细信息等)
        byte[] data = nodeCache.getCurrentData().getData();
        System.out.println("修改后的数据为:" + new String(data));
    }
});
//3.开启监听,形参默认为false,若设置为true,开启监听时会加载缓冲数据。
nodeCache.start(true);
        2)PathChildrenCache
//1.创建监听对象
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/testNode", true);//true表示要缓存该节点数据
//2.注册监听器
pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
    @Override
    public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
        //根据子节点的状态(被修改、被删除等)进行操作
        //1)获取子节点状态
        PathChildrenCacheEvent.Type type = event.getType();
        //2)判断是不是被修改
        if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
            //3)获取修改后的数据
            byte[] data = event.getData().getData();
            System.out.println("修改后的数据为:" + new String(data));
        }
    }
});
//3.开启监听
pathChildrenCache.start();
        3)TreeCache
//1.创建监听对象
TreeCache treeCache=new TreeCache(client,"/testNode");
//2.注册监听器
treeCache.getListenable().addListener(new TreeCacheListener() {
    @Override
    public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
    }
});
//3.开启监听
treeCache.start();

(4)分布式锁★

        1)分布式锁介绍
                在同一台服务器开发涉及并发同步时,可以采用synchronized或者Lock的方式来解决多线程间的代码同步问题。 但当应用是分布式集群工作的情况下,属于多JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题,因此处理这种跨机器的进程之间的数据同步问题就是分布式锁。
                
        2)核心原理★
               
                当客户端想要获取锁时,则创建节点,使用完锁,则删除该节点。 
  • 客户端获取锁时,在lock节点下创建临时顺序节点
  • 然后每个客户端都会获取lock的所有子节点,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
  • 如果发现自己创建的节点并非最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件
  • 如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁, 如果不是则重复以上步骤继续获取到比自己小的一个节点,并注册监听。
        3)Curator中的五种分布式锁
  • InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
  • InterProcessMutex:分布式可重入排它锁
  • InterProcessReadWriteLock:分布式读写锁
  • InterProcessMultiLock:将多个锁作为单个实体管理的容器
  • InterProcessSemaphoreV2:共享信号量

4.Zookeeper集群

(1)Zookeeper集群介绍

        1)Leader选举
                Serverid:服务器ID。比如有三台服务器,编号分别是1,2,3。编号越大在选择算法中的权重越大。
                Zxid:数据ID。服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新权重越大。
                在Leader选举的过程中,如果某台ZooKeeper 获得了超过半数的选票, 则此ZooKeeper就可以成为Leader了。
        2)集群角色
                在ZooKeeper集群服中务中有三个角色:
  • Leader 领导者 :处理事务请求,是集群内部各服务器的调度者。
  • Follower 跟随者 :处理客户端非事务请求,转发事务请求给Leader服务器,可以参与Leader选举投票。
  • Observer 观察者: 处理客户端非事务请求,转发事务请求给Leader服务器。
  • 【tips】事务性请求包括更新、新增、删除操作;非事务性请求包括查询操作、exist操作等这些不影响数据的操作,因此不需要集群来保持事务性。

(2)Zookeeper集群搭建

        搭建三个节点的Zookeeper集群。
        1)准备工作:
  • 安装jdk
  • 将zookeeper压缩包上传到服务器
  • 将zookeeper解压 ,建立/usr/local/zookeeper-cluster目录,将解压后的zookeeper复制到以下三个目录 
            /usr/local/zookeeper-cluster/zookeeper-1
            /usr/local/zookeeper-cluster/zookeeper-2
            /usr/local/zookeeper-cluster/zookeeper-3
  • 在三个目录中分别创建data目录,并且将conf下zoo_sample.cfg 文件改名为 zoo.cfg
  • 配置每一个Zookeeper 的数据目录和客户端端口分别为2181、2182、2183
vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg

clientPort=2181
dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data
        2)配置集群
  • 在每个zookeeper的data目录下创建一个 myid 文件,内容分别是1、2、3,用来记录每个服务器的ID。
  • 在每一个zookeeper的zoo.cfg配置客户端访问端口(clientPort)和集群服务器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
    
    //每一个服务器的cfg中都要配置以下三条信息!
    server.1=192.168.149.135:2881:3881
    server.2=192.168.149.135:2882:3882
    server.3=192.168.149.135:2883:3883
【tips】server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口
        3)启动集群
  • 启动集群就是分别启动每个zookeeper
【tips】启动后,可以查看每个zookeeper的状态(status),可以看到其在集群中的角色:Modefollower表示是跟随者(从),Modeleader表示是领导者(主)。

(3)模拟集群异常

        当领导者产生后,再次有新服务器加入集群,不会影响到现任领导者。但当集群中的主服务器(领导者)挂了,集群中的从服务器(跟随者)会自动进行选举,然后产生新得leader。