服务中间件Zookeeper

Zookeeper是一种分布式,开源的应用协调服务也即是“服务中间件”;主要提供的功能包括:“配置管理”,“分布式锁”,“集群管理”。

1、配置管理:管理服务对象的信息(IP、端口、服务提供功能接口)。

2、分布式锁:控制服务对象的资源(锁住之后,只能有一个服务对象可操作,其它服务对象等待)。

3、集群管理:管理服务对象的节点(每个节点提供的服务相同,则组成一个集群),组成高可用

Zookeeper原理

Zookeeper是一个树形目录服务,其数据模型拥有一个层次化的结构;从图中可看出:一个节点可以保存自己的数据和节点信息,也可以拥有子节点。
在这里插入图片描述

Zookeeper指令

1.服务端指令
linux指令说明
./zkServer.sh.start启动服务
./zkServer.sh.status服务状态
./zkServer.sh.stop停止服务
./zkServer.sh.restart重启服务
2.客户端命令
linux指令说明
./zkcli.sh -server ip:port连接服务端
quit断开连接
ls path显示指定目录下节点
create -[e/s/es] / path value创建单个节点
set /path value设置节点
get /path获取节点值
delete /path删除单个节点
deleteall /path删除所有节点

Curator 客户端

curator是Zookeeper的java客户端工具,也是阿帕奇基金组织的顶级项目;他封装了Zookeeper一些javaAPI的方法来对节点进行操作。

1.建立连接
public class CuratorTest {

//==============================Connect:建立连接========================================================================

    private CuratorFramework client;

    /**
     * 建立连接
     * 第一种:CuratorFrameworkFactory.newClient
     * 第二种:CuratorFrameworkFactory.builder()
     */
    @Before
    public void testConnect() {
        /*
         * 第一种连接方式
         * CuratorFrameworkFactory.newClient
         * @param connectString       连接字符串。zk server 地址和端口 "192.168.149.135:2181"
         * @param sessionTimeoutMs    会话超时时间 单位ms
         * @param connectionTimeoutMs 连接超时时间 单位ms
         * @param retryPolicy         重试策略
         */

//        重试策略
//        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
//        创建连接对象
//        CuratorFramework client = CuratorFrameworkFactory.newClient("ZKIP:2181",
//                60 * 1000, 15 * 1000, retryPolicy);
//        开启连接
//        client.start();

        /**
         * 第二种连接方式
         * CuratorFrameworkFactory.builder()
         * namespace:/myCurator/创建的节点
         */
        //重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
        //创建连接对象
        client = CuratorFrameworkFactory.builder()
                .connectString("ZKIP:2181")
                .sessionTimeoutMs(60 * 1000)
                .connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy)
                .namespace("myCurator")
                .build();
        //开启连接
        client.start();
    }

2.创建节点
public class CuratorTest {

//==============================Create:创建节点========================================================================

    /**
     * 创建节点:create
     * 节点类型:持久,临时,持久顺序,临时顺序节点
     * 1.基本创建 :create().forPath("")
     * 2.创建节点 带有数据:create().forPath("",data)
     * 3.设置节点的类型:create().withMode().forPath("",data)
     * 4.创建多级节点  /app1/p1 :create().creatingParentsIfNeeded().forPath("",data)
     */
    @Test
    public void testCreate1() throws Exception {
        //1.基本创建:如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储
        String s = client.create().forPath("/app1");
        System.out.println(s);
    }

    @Test
    public void testCreate2() throws Exception {
        //2.创建节点,带数据
        String path = client.create().forPath("/app2", "大王来巡山".getBytes());
        System.out.println(path);
    }

    @Test
    public void testCreate3() throws Exception {
        //3.设置节点类型
//        PERSISTENT:持久化节点
//        PERSISTENT_SEQUENTIAL:持久化顺序节点
//        EPHEMERAL:临时节点
//        EPHEMERAL_SEQUENTIAL:临时顺序节点
//                CONTAINER(4, false, false, true, false),
//                PERSISTENT_WITH_TTL(5, false, false, false, true),
//                PERSISTENT_SEQUENTIAL_WITH_TTL(6, false, true, false, true);
        String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3");
        System.out.println(path);
    }

    @Test
    public void testCreate4() throws Exception {
        //4.创建多级节点
        //creatingParentsIfNeeded():如果父节点不存在,则创建父节点
        String path = client.create().creatingParentContainersIfNeeded().forPath("/app4/p1");
        System.out.println(path);
    }}
3.查询节点
public class CuratorTest {

//==============================Cet:查询节点============================================================================

    /**
     * 查询节点:
     * 1. 查询数据:get: getData().forPath()
     * 2. 查询子节点: ls: getChildren().forPath()
     * 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()
     */
    @Test
    public void testGet1() throws Exception {
        //1.查询数据
        byte[] value = client.getData().forPath("/app2");
        System.out.println(new String(value));
    }

    @Test
    public void testGet2() throws Exception {
        //2.查询子节点
        List<String> path = client.getChildren().forPath("/app4");
        System.out.println(path);
    }

    @Test
    public void testGet3() throws Exception {
        //3.查询节点状态信息
        Stat status = new Stat();
        client.getData().storingStatIn(status).forPath("/app2");
        System.out.println(status);
    }}
4.修改节点
public class CuratorTest {
//==============================Set:修改节点============================================================================

    /**
     * 修改数据
     * 1. 基本修改数据:setData().forPath()
     * 2. 根据版本修改: setData().withVersion().forPath()
     * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。
     */
    @Test
    public void testSet() throws Exception {
        //1.修改基本数据
        client.setData().forPath("/app2", "芒果".getBytes());
    }

    @Test
    public void testSetVersion() throws Exception {
        //1.修改节点版本
        //1.查询当前节点版本
        Stat status = new Stat();
        client.getData().storingStatIn(status).forPath("/app2");
        int vesion = status.getVersion();
        System.out.println(vesion);
        //1.2修改当前节点版本
        client.setData().withVersion(vesion).forPath("/app2", "王老吉".getBytes());
    }}
5.删除节点
public class CuratorTest {
//==============================delete:删除节点=========================================================================

    /**
     * 删除节点: delete deleteall
     * 1. 删除单个节点:delete().forPath("/app1");
     * 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1");
     * 3. 必须成功的删除:为了防止网络抖动。本质就是重试。  client.delete().guaranteed().forPath("/app2");
     * 4. 回调:inBackground
     */

    @Test
    public void testDelete1() throws Exception {
        // 1. 删除单个节点
        client.delete().forPath("/app1");
    }

    @Test
    public void testDelete2() throws Exception {
        // 2. 删除带有子节点的节点
        client.delete().deletingChildrenIfNeeded().forPath("/app4");
    }

    @Test
    public void testDelete13() throws Exception {
        // 3. 必须成功的删除
        client.delete().guaranteed().forPath("/app2");
    }

    @Test
    public void testDelete14() 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("/app2");
    }}
6.关闭连接
 public class CuratorTest {
//==============================close:关闭连接======================================================================
    @After
    public void close() {
        if (client != null) {
            client.close();
        }
    }}

Watch事件监听

Zookeeper的监听机制是指在节点上绑定监听器,当特定事件触发时,Zookeeper服务端会将事件状态变化通知到订阅了该节点的客户端上去。

Watcher实现了发布/订阅功能,能让订阅者(客户端)同时监听某一个节点,当订阅对象自身状态变化时,Zookeeper服务器会通知所有的订阅者。

1.监听分类

watcher的监听实现,是借助于Curator的Cache来实现对Zookeeper服务端事件的监听。而Zookeeper提供了以下三种Watcher:

1.1、NodeCache:监听某个特定节点。

1.2、PathChildrenCache:监听ZNode的子节点。

1.3、TreeCache:监控整个树上的所有节点(类似于NodeCache+PathChildrenCache的集合)。

2.监听实现
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class WatcherTest {
  
    private CuratorFramework client;
//==============================start:开启连接==========================================================================

    //建立连接
    @Before
    public void testConnection() {
        //重试策略
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
        //创建连接对象
        client = CuratorFrameworkFactory.builder()
                .connectString("192.168.213.1:2181")
                .sessionTimeoutMs(60 * 1000)
                .connectionTimeoutMs(15 * 1000)
                .retryPolicy(retryPolicy)
                .namespace("myCurator")
                .build();
        //开启连接
        client.start();
    }
//==============================Watcher:开启监听========================================================================
    @Test
    public void testWatcher1() throws Exception {
        //1.监听一个特定的节点:当节点发生增删改变化时,会将监听到的变化反馈给订阅者

        //1.1创建监听对象
        NodeCache nodeCache = new NodeCache(client, "/app2");
        //1.2注册监听事件
        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("节点发生变化了");
                //1.3获取节点变化后的数据
                byte[] data = nodeCache.getCurrentData().getData();
                System.out.println(new String(data));
            }
        });
        //1.4 开启监听
        nodeCache.start(true);
    }

    @Test
    public void testWatcher2() throws Exception {
        //2.监听Znode节点的子节点
        //2.1 创建监听对象
        PathChildrenCache childrenCache = new PathChildrenCache(client, "/app2", true);
        //2.2 注册监听事件
        childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                System.out.println("子节点发生变化了");
                System.out.println(event);

                //2.3 监听子节点的数据变更,并且获取变更后的数据
                //2.3.1 获取数据类型
                PathChildrenCacheEvent.Type type = event.getType();
                //2.3.2 判断数据类型是否变更
                if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
                    System.out.println("数据发生变更");
                    byte[] data = event.getData().getData();
                    System.out.println(new String(data));
                }
            }
        });
        //2.4 开启监听
        childrenCache.start();
    }

    @Test
    public void testWatcher3() throws Exception {
        //3. 监听某个节点自己和所有子节点们
        //3.1创建监听对象
        TreeCache treeCache = new TreeCache(client, "/app2");
        //3.2注册监听事件
        treeCache.getListenable().addListener(new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
                System.out.println("节点变化了");
                System.out.println(event);
            }
        });
        //3.3开启监听
        treeCache.start();
    }
//==============================close:关闭连接==========================================================================
    //关闭连接
    @After
    public void close() {
        if (client != null) {
            client.close();
        }
    }

分布式锁的机制

分布式锁是协调跨机器的进程之间的数据同步的一种机制。

在这里插入图片描述

1.分布式锁原理

当客户端要获取锁,则会创建一个临时顺序节点,使用完锁后,就删除该节点;临时顺序节点有两点好处:

1)临时:为了防止客户端偶发宕机情况下,lock对象不能释放,形成死锁。
2)顺序:为客户端提供了排队功能,以便于注册监听删除事件,实现子节点序号的比对。

在这里插入图片描述

2.锁的实现过程

客户端在lock节点下创建"临时顺序节点"–>获取所有创建的"临时顺序节点"–>"临时顺序节点"进行比对:

1)当客户端发现自己创建的子节点序号是最小的,就获取锁,使用完锁后就删除该节点。

2)当客户端发现自己创建的子节点序号不是最小,不能获取锁,当前客户端会找比自己小的节点,同时对该节点绑定监听器,监听删除事件;如果客户端被通知该节点被删除,当前客户端会再一次比对自己创建的节点序号是否最小:是,获取锁;不是,重复监听,再次比对直到获取锁。

Zookeeper集群

1. 集群的角色

在这里插入图片描述
1.1、leader(领导者):处理事务请求和集群内部服务器协调(事务处理后的变化结果同步到各内部服务器)。

1.2、Follower(跟随者):处理客户端非事务请求,并将事务请求转发给leader; 参与leader选举投票。

1.3、Observer(观察者): 处理器客户端非事务请求,转发事务请求给leader.

2. 领导者选取

在这里插入图片描述
领导者的选取:Zookeeper集群每个服务器都有自己的Serverid,如果某台Zookeeper获得了超过半数选票,此台Zookeeper就成为领导者。(比如:三台Zookeeper,编号分别为1,2,3,根据半数原则编号为2的Zookeeper成为leader)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值