zookeeper --马士兵

1.zooKeeper介绍

官网文档:https://zookeeper.apache.org/doc/current/zookeeperOver.html

 zookeeper是主从模式

通过zookeeper实现分布式锁要比redis简单效率高(zk leader选举速度快,leader挂掉后集群对外不可用时间短)

 文件格式

ZooKeeper:分布式应用程序的分布式协调服务

ZooKeeper是用于分布式应用程序的分布式,开放源代码协调服务。它公开了一组简单的原语,分布式应用程序可以基于这些原语来实现用于同步,配置维护以及组和命名的更高级别的服务。它的设计易于编程,并使用了按照文件系统熟悉的目录树结构样式设置的数据模型。它以Java运行,并且具有Java和C的绑定。

众所周知,协调服务很难做到。它们特别容易出现诸如比赛条件和死锁之类的错误。ZooKeeper背后的动机是减轻分布式应用程序从头开始实施协调服务的责任。

设计目标

ZooKeeper很简单。ZooKeeper允许分布式进程通过共享的分层名称空间相互协调,该命名空间的组织方式类似于标准文件系统。名称空间由数据寄存器(在ZooKeeper看来,称为znode)组成,它们类似于文件和目录。与设计用于存储的典型文件系统不同,ZooKeeper数据保留在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟数。

ZooKeeper实施对高性能,高可用性,严格有序访问加以重视。ZooKeeper的性能方面意味着它可以在大型分布式系统中使用。可靠性方面使它不会成为单点故障。严格的排序意味着可以在客户端上实现复杂的同步原语。

ZooKeeper已复制。像它协调的分布式进程一样,ZooKeeper本身也可以在称为集合的一组主机上进行复制。

ZooKeeper服务

组成ZooKeeper服务的服务器都必须彼此了解。它们维护内存中的状态图像,以及持久存储中的事务日志和快照。只要大多数服务器可用,ZooKeeper服务将可用。

客户端连接到单个ZooKeeper服务器。客户端维护一个TCP连接,通过该连接发送请求,获取响应,获取监视事件并发送心跳。如果与服务器的TCP连接断开,则客户端将连接到其他服务器。

ZooKeeper已订购。ZooKeeper用一个反映所有ZooKeeper事务顺序的数字标记每个更新。后续操作可以使用该命令来实现更高级别的抽象,例如同步原语。

ZooKeeper很快。在“读取为主”的工作负载中,它特别快。ZooKeeper应用程序可在数千台计算机上运行,​​并且在读取比写入更常见的情况下,其性能最佳,比率约为10:1。

数据模型和分层名称空间

ZooKeeper提供的名称空间与标准文件系统的名称空间非常相似。名称是由斜杠(/)分隔的一系列路径元素。ZooKeeper命名空间中的每个节点都由路径标识。

ZooKeeper的层次命名空间

ZooKeeper的层次命名空间

节点和短暂节点

与标准文件系统不同,ZooKeeper命名空间中的每个节点都可以具有与其关联的数据以及子节点。就像拥有一个文件系统一样,该文件系统也允许文件成为目录。(ZooKeeper旨在存储协调数据:状态信息,配置,位置信息等,因此存储在每个节点上的数据通常很小,在字节到千字节范围内。)我们使用术语znode来明确表示在谈论ZooKeeper数据节点。

Znodes维护一个统计信息结构,其中包括用于数据更改,ACL更改和时间戳的版本号,以允许进行缓存验证和协调更新。znode的数据每次更改时,版本号都会增加。例如,每当客户端检索数据时,它也会接收数据的版本。

原子地读取和写入存储在命名空间中每个znode上的数据。读取将获取与znode关联的所有数据字节,而写入将替换所有数据。每个节点都有一个访问控制列表(ACL),用于限制谁可以执行操作。

ZooKeeper还具有短暂节点的概念。只要创建znode的会话处于活动状态,这些znode就存在。会话结束时,将删除znode。

有条件的更新和监视

ZooKeeper支持手表的概念。客户端可以在znode上设置手表。znode更改时,将触发并删除监视。触发监视后,客户端会收到一个数据包,说明znode已更改。如果客户端和其中一个ZooKeeper服务器之间的连接断开,则客户端将收到本地通知。

3.6.0中的新增功能:客户端还可以在znode上设置永久的,递归的手表,这些手表在被触发时不会被删除,并且会以递归方式触发已注册znode以及所有子znode的更改。

保证金

ZooKeeper非常快速且非常简单。但是,由于其目标是作为构建更复杂的服务(例如同步)的基础,因此它提供了一组保证。这些是:

  • 顺序一致性-来自客户端的更新将按照发送的顺序应用。
  • 原子性-更新成功或失败。没有部分结果。
  • 单个系统映像-无论客户端连接到哪个服务器,客户端都将看到相同的服务视图。也就是说,即使客户端故障转移到具有相同会话的其他服务器,客户端也永远不会看到系统的较旧视图。
  • 可靠性-应用更新后,此更新将一直持续到客户端覆盖更新为止。
  • 及时性-确保系统的客户视图在特定时间范围内是最新的。

简单的API

ZooKeeper的设计目标之一是提供一个非常简单的编程界面。因此,它仅支持以下操作:

  • create:在树中的某个位置创建一个节点

  • delete:删除节点

  • 存在:测试某个位置是否存在节点

  • 获取数据:从节点读取数据

  • 设置数据:将数据写入节点

  • 获取子节点:检索节点的子节点列表

  • sync:等待数据传播

 

3.zookeeper 应用

创建永久节点:

创建临时节点

注意:临时节点下不能创建子节点,如果要创建子节点的节点创建为永久性节点,否则会报错:KeeperErrorCode = NoChildrenForEphemerals for /kafka-2-8/tttXXX

如果客户端A连接zk集群是测试的leader是节点q,客户端通过leader q创建了一个临时节点后。leader q 挂掉后节点w变为leader,此时客户A会自动转换和leader  w节点建立连接,并且端通过leader q创建的临时节点

在leader  w节点一样可以看到。是应为zk集群在和客户端建立连接时会统一视图。

不同客户端创建名字相同的节点

删除操作:

安装四台机子的原因

 zookeeper可靠性最要是体现在?:leader挂掉快速的恢复。

数据同步ZAB协议

ZAB协议保证数据最终一致性

ZAB协议(原子广播协议),作用在集群可用状态及有leader的状态。

ZAB协议是保证最终一致性的前提下,实现leader挂掉快速的恢复。

ZAB协议选举机制

选举:先比较zxid事务ID,再比较myid,超过半数follower投票完成,选举结束。

第一次启动集群选leader:.follwer都是会先给自己投一票,因为此时的zxid都是0,所以谁的myid大谁就leader。

watch(监控,观察)

watch 回调API 

1.zk是有session概念的,没有连接池的概念
2.watch:观察,回调
3.watch的注册值发生在 读类型调用,get,exites。。。

pom依赖

集群用的什么版本,客户端必须用用什么版本

        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.6</version>
        </dependency>

创建节点方法

第一类添加watch:new zk 时候,传入的watch,这个watch,session级别的,,客户端session发生变化会触发回掉跟path 、node没有关系。
final ZooKeeper zk = new ZooKeeper("kafka1:2181,kafka2:2181,kafka3:2181/kafka-2-7",

kafka-2-7就是次链接的根目录了

String pathName1 = zk.create("/kafka-2-8", "olddata".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

 

       //第一类:new zk 时候,传入的watch,这个watch,session级别的,跟path 、node没有关系。
        final CountDownLatch cd = new CountDownLatch(1);
// private static String address = "kafka1:2181,kafka2:2181,kafka3:2181/testLock";
        final ZooKeeper zk = new ZooKeeper("kafka1:2181,kafka2:2181,kafka3:2181",
                3000, new Watcher() {
            //Watch 的回调方法!
            @Override
            public void process(WatchedEvent event) {
                Event.KeeperState state = event.getState();
                Event.EventType type = event.getType();
                String path = event.getPath();
                System.out.println("new zk watch: "+ event.toString());

                //根据state 回调
                switch (state) {
                    case Unknown:
                        break;
                    case Disconnected:
                        break;
                    case NoSyncConnected:
                        break;
                    case SyncConnected:
                        System.out.println("connected");
                        cd.countDown();
                        break;
                    case AuthFailed:
                        break;
                    case ConnectedReadOnly:
                        break;
                    case SaslAuthenticated:
                        break;
                    case Expired:
                        break;
                }
                //type 回调
                switch (type) {
                    case None:
                        break;
                    case NodeCreated:
                        break;
                    case NodeDeleted:
                        break;
                    case NodeDataChanged:
                        break;
                    case NodeChildrenChanged:
                        break;
                }


            }
        });
//等待和zk连接成功
        cd.await();

//查看和zk的连接状态
        ZooKeeper.States state = zk.getState();
        switch (state) {
            case CONNECTING:
                System.out.println("ing......");
                break;
            case ASSOCIATING:
                break;
            case CONNECTED:
                System.out.println("ed........");
                break;
            case CONNECTEDREADONLY:
                break;
            case CLOSED:
                break;
            case AUTH_FAILED:
                break;
            case NOT_CONNECTED:
                break;
        }
        cd.await(); //实现异步同步
        ZooKeeper.States state = zk.getState();
        switch (state) {
            case CONNECTING:
                System.out.println("ing......");
                break;
            case ASSOCIATING:
                break;
            case CONNECTED:
                System.out.println("ed........");
                break;
            case CONNECTEDREADONLY:
                break;
            case CLOSED:
                break;
            case AUTH_FAILED:
                break;
            case NOT_CONNECTED:
                break;
        }

第二类添加watch:在path添加watch, patch发生变化会触发回掉

        String pathName = zk.create("/ooxx", "olddata".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

        final Stat  stat=new Stat();
        //获取/ooxx 此节点路径的同时放入一个watch 来监控这个节点路径,以后这几点路径发生变化就执行process()回调方法
        byte[] node = zk.getData("/ooxx", new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                System.out.println("getData watch: "+event.toString());
                try {
                    //true   default Watch  被重新注册   new zk的那个watch
                    zk.getData("/ooxx",this  ,stat);
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, stat);

//触发回调

        //改数据的时候,会触发刚才getData放进去的watch的回调
        Stat stat1 = zk.setData("/ooxx", "newdata".getBytes(), 0);
        //还会触发吗?回调只会被回调一次,如果想让每次修改都回调,可以在回调方法中再次将watch注册进去
        Stat stat2 = zk.setData("/ooxx", "newdata01".getBytes(), stat1.getVersion());

异步获取 

        System.out.println("-------async start----------");
        zk.getData("/ooxx", false, new AsyncCallback.DataCallback() {
            @Override
            public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
                System.out.println("-------async call back----------");
                System.out.println(ctx.toString());
                System.out.println(new String(data));

            }

        },"abc");
        System.out.println("-------async over----------");

配置中心

。。。。。。

分布式锁

分布式锁实现

public class DefaultWatch  implements Watcher {

    CountDownLatch cc ;

    public void setCc(CountDownLatch cc) {
        this.cc = cc;
    }

    @Override
    public void process(WatchedEvent event) {

        System.out.println(event.toString());

        switch (event.getState()) {
            case Unknown:
                break;
            case Disconnected:
                break;
            case NoSyncConnected:
                break;
            case SyncConnected:
                cc.countDown();
                break;
            case AuthFailed:
                break;
            case ConnectedReadOnly:
                break;
            case SaslAuthenticated:
                break;
            case Expired:
                break;
        }


    }
}
import org.apache.zookeeper.ZooKeeper;

import java.util.concurrent.CountDownLatch;

/**
 * @author: 马士兵教育
 * @create: 2019-09-20 20:08
 */
public class ZKUtils {

    private  static ZooKeeper zk;


    private static DefaultWatch watch = new DefaultWatch();

    private static CountDownLatch init  =  new CountDownLatch(1);


    public static ZooKeeper  getZK(String address){

        try {
            zk = new ZooKeeper(address,1000,watch);
            watch.setCc(init);
            init.await();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return zk;
    }
}

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;


public class WatchCallBack   implements Watcher, AsyncCallback.StringCallback ,AsyncCallback.Children2Callback ,AsyncCallback.StatCallback {

    ZooKeeper zk ;
    String threadName;
    CountDownLatch cc = new CountDownLatch(1);
    String pathName;

    public String getPathName() {
        return pathName;
    }

    public void setPathName(String pathName) {
        this.pathName = pathName;
    }

    public String getThreadName() {
        return threadName;
    }

    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    public ZooKeeper getZk() {
        return zk;
    }

    public void setZk(ZooKeeper zk) {
        this.zk = zk;
    }

    public void tryLock(){
        try {

            System.out.println(threadName + "  create....");
//            if(zk.getData("/"))
            zk.create("/lock",threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this,"abc");

            cc.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void unLock(){
        try {
            zk.delete(pathName,-1);
            System.out.println(threadName + " over work....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }


    @Override
    //creat(),deleted(),修改事件watch的回调方法
    //watch 回调方法
    public void process(WatchedEvent event) {


        //如果第一个哥们,那个锁释放了,其实只有第二个收到了回调事件!!
        //如果,不是第一个哥们,某一个,挂了,也能造成他后边的收到这个通知,从而让他后边那个跟去watch挂掉这个哥们前边的。。。
        switch (event.getType()) {
            case None:
                break;
            case NodeCreated:
                break;
                //删除事件
            case NodeDeleted:
                zk.getChildren("/",false,this ,"sdf");
                break;
            case NodeDataChanged:
                break;
            case NodeChildrenChanged:
                break;
        }

    }

    @Override
    //zk.create() 执行成功后的回调方法
    public void processResult(int rc, String path, Object ctx, String name) {
        if(name != null ){
            System.out.println(threadName  +"  create node : " +  name );
            pathName =  name ;
            //watch=false表示 "/"跟路径的变化不需要watch
            //cb=this,表示回调方法
            zk.getChildren("/",false,this ,"sdf");
        }

    }

    //getChildren  call back
    //zk.getChildren() 执行成功后的回调方法
    @Override
    public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {

        //一定能看到自己前边的。。

//        System.out.println(threadName+"look locks.....");
//        for (String child : children) {
//            System.out.println(child);
//        }

        Collections.sort(children);
        int i = children.indexOf(pathName.substring(1));


        //是不是第一个
        if(i == 0){
            //yes
            try {
                zk.setData("/",threadName.getBytes(),-1);
                cc.countDown();

            } catch (KeeperException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            //no
            //watch 这前面一个节点,删除会触发process()回调,当前面一个节点发生删除事件,执行watch的回调方法
            zk.exists("/"+children.get(i-1),this,this,"sdf");
        }

    }

    @Override
    public void processResult(int rc, String path, Object ctx, Stat stat) {
        //偷懒
    }
}

 测试:模拟10台机器同时获取锁

package com.msb.zookeeper.lock;

import com.msb.zookeeper.config.ZKUtils;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * @author: 马士兵教育
 * @create: 2019-09-20 21:21
 */
public class TestLock {


    ZooKeeper zk ;

    @Before
    public void conn () throws KeeperException, InterruptedException {
        //"kafka1:2181,kafka2:2181,kafka3:2181/testLock"
/*        zk  = ZKUtils.getZK("kafka1:2181,kafka2:2181,kafka3:2181");
        //创建父目录只需要创建一次
       String pathName0 = zk.create("/testLock", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
      System.out.println("======="+pathName0);
        zk.close();
        TimeUnit.SECONDS.sleep(5);*/

        zk  = ZKUtils.getZK("kafka1:2181,kafka2:2181,kafka3:2181/testLock");
    }

    @After
    public void close (){
        try {
            zk.close();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void lock(){

        for (int i = 0; i < 10; i++) {
            new Thread(){
                @Override
                public void run() {
                    WatchCallBack watchCallBack = new WatchCallBack();
                    watchCallBack.setZk(zk);
                    String threadName = Thread.currentThread().getName();
                    watchCallBack.setThreadName(threadName);
                    //每一个线程:
                    //抢锁
                    watchCallBack.tryLock();
                    //干活,业务逻辑代码
                    System.out.println(threadName+" working...");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //释放锁
                    watchCallBack.unLock();


                }
            }.start();
        }
        while(true){

        }


    }




}

猜你喜欢

转载自blog.csdn.net/yangshengwei230612/article/details/113742180