ZooKeeper基础学习和API编写

一、Zookeeper学习要点

        Zookeeper是一个基于观察者模式设计的分布式管理框架,为分布式框架提供协调服务的 Apache 项目。


1、Zookeeper工作机制

1)分为三部分:

        ①ZooKeeper集群(负责管理)

        ②服务器(具有业务功能) 

        ③客户端

本质上②和③对于ZooKeeper 集群而言都是一样的,是客户。只是需要存储和反馈的信息不同。

  2)    ZooKeeper = 文件系统 + 通知系统

文件系统:负责存储和管理大家都关心的数据。存储的数据量很小,只存储节点的路径,名称等关键信息。

通知系统:接受观察者的注册,一旦数据的状态发生变化,ZooKeeper将负责通知已经在ZooKeeper上注册的那些观察者做出相应的反应。

             


2、Zookeeper特点

1)一个领导者(Leader),多个跟随者(Follower)组成的集群。

2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所 以Zookeeper适合安装奇数台服务器。(半数以上决定了Zookeeper适合安装奇数台服务器,偶数台会造成服务器资源的浪费

3)全局数据一致性,写入数据需要Leader的权限。每个服务器都保持一份相同的数据副本。

4)更新数据顺序执行,同一个客户端(Client)的更新请求按照请求的顺序进行。

5)数据更新原子性,要么成功,要么失败,不存在部分更新。

6)实时性,一定时间内Client读取到的数据最新,服务器上下线的状态最新。

7)ZooKeeper的数据结构是树形结构,每个节点看作一个ZkNode,可存1MB数据,且路径唯一,路径作为唯一标识。 


3、应用场景

统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。


4、选取机制(假设共5台服务器

1)第一次启动集群进行Leader选举

        严格遵循半数原则:

        ①   服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(半数以上为3台),选举无法完成,服务器1状态保持为 LOOKING;

        ②   服务器2启动,再发起一次选举。服务器12分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器12状态保持LOOKING;

        ③   服务器3启动,发起一次选举。此时服务器12都会更改选票为服务器3。此次投票结果:服务器10票,服务器20票,服务器33票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器12更改状态为FOLLOWING,服务器3更改状态为LEADING

        ④   服务器4启动,发起一次选举。此时服务器123已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器33票,服务器4为 1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING

        ⑤   服务器5启动,同4一样当小弟。

2)非第一次选取   

       <1> 当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:

                • 服务器初始化启动。
                • 服务器运行期间无法和Leader 保持连接。
        <2>选举Leader规则:
                ①EPOCH大的直接胜出。
                ②EPOCH相同,事务id大的胜出。
                ③事务id相同,服务器id大的胜出。
        【注】:
                      ·EPOCH是指每个Leader的代号。
                      · 事务id(ZXID)在某个时刻,每台服务器的ZXID不一定一致,跟更新请求后的更新逻辑有关系,操作或则数据在某一时刻不是绝对一致的。
                      ·服务器id(SID),用来唯一标识Zookeeper集群中的服务器,在集群中每台服务器SID唯一。

5、客户端向服务器端写数据的流程

        1)情况一:客户端(Client)连接的是ZooKeeper集群中的Leader服务器
                ①Client向Leader发送请求,Leader先自己更新数据。
                ②随后Leader向别的服务器 Follower发送更新数据的命令, Follower更新后向Leader回馈消息已经更新完数据。
                ③Leader检查是否已经更新完 半数 服务器的数据,如果 超过半数 ,向Client回馈消息,已经更新完Zookeeper集群中的数据。
                ④如果没有超过半数Leader选择别的Follower继续执行②,直到超过半数执行③,最后继续执行更新剩下的Follower服务器。
        2)情况二:客户端(Client)连接的是ZooKeeper集群中的Follower服务器
                ①Client向 Follower发送请求, Follower向Leader发送更新请求。
                ②Leader收到请求后,先更新自己的数据。更新后,向Client请求的 Follower服务器发送更新请求,Follower接收到请求更新数据,然后向Leader反馈已更新数据。
                ③如果此时Zookeeper中超过半数服务器更新完数据,Leader向Client连接的Fllower反馈信息,集群数据已更新完毕,此Fllower向Client回馈已更新完数据。
                ③如果没有超过半数,Leader选择别的Follower继续执行更新请求,如果超过半数时执行③,随后继续更新剩下的Fllower的数据。

二、API编写


6、服务器动态上下线监听案例

1)先在集群上创建/servers 节点
create         /servers         "servers"
(2)服务器端向 Zookeeper 注册代码


import org.apache.zookeeper.*;

import java.io.IOException;

public class DistributeServer {
    private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
    private int sessionTimeout=2000;
    private ZooKeeper zk;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {

        //服务端在Zookeeper上进行注册

        //1、获取zk链接
        //用DistributeServer类对整体进行封装
        DistributeServer server = new DistributeServer();
        server.getconnect();

        //2、利用zk链接注册服务器信息(创建节点)
        server.registServer(args[0]);//args[0] 手动传 实参
        //3、启动业务功能(目前没有功能,我们们执行sleep()睡觉)
        server.business();
    }


    //业务功能
    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    //在zk注册服务器(就是在父节点下面创建新的子节点)
    private void registServer(String hostname) throws InterruptedException, KeeperException {
        String create = zk.create("/servers/"+hostname, hostname.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        //创建完之后提示 已经上线
        System.out.println(hostname + "  is online.");
    }

    //链接zk集群
    private void getconnect() throws IOException {
        //把zk做全局变狼
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {

            }
        });

    }

}//DistributeServer
3)客户端代码

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

//客户端监听服务器的上下线
public class DistributeCliient {
    private String connectString="hadoop102:2181,hadoop103:2181,hadoop104:2181";
    private int sessionTimeout=2000;
    private ZooKeeper zk;

    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        DistributeCliient Client = new DistributeCliient();
        //1、获取zk链接
        Client.getconnect();
        //2、获取servers的子节点信息。从中得到服务器信息的列表
        Client.getServersList();
        //3、业务进程启动
        Client.business();
    }

    //业务进程
    private void business() throws InterruptedException {
        Thread.sleep(Long.MAX_VALUE);
    }

    //获取服务器列表的信息
    private void getServersList() throws InterruptedException, KeeperException {
        //1获取服务器节点信息,对父节点(指定的节点"/servers",需要先在ZK集群上创建该父节点)进行监听
        List<String> children = zk.getChildren("/servers", true);//监听
        //2存储服务器的列表信息
        ArrayList<Object> servers = new ArrayList<>();

        //3遍历所有节点,获取节点中的主机名称信息
        for (String child : children) {
            //不在监听,已经监听父节点,不返回状态信息
            byte[] data = zk.getData("/servers/" + child, false, null);
            //添加到列表中,循环添加
            servers.add(new String(data)); //注意类型转换
        }
        //4打印列表
        System.out.println(servers);
    }

    //链接ZK集群
    private void getconnect() throws IOException {
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            //多次监听
            @Override
            public void process(WatchedEvent watchedEvent) {
                try {
                    getServersList();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } catch (KeeperException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }
}

7、ZooKeeper 分布式锁案例 (Java API实现)

·什么叫做分布式锁呢?
        比如说 " 进程 1" 在使用该资源的时候,会先去获得锁, " 进程 1"获得锁以后会对该资源
保持独占,这样其他进程就无法访问该资源, " 进程 1" 用完该资源以后就将锁释放掉,让其
他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的
访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。
·实现逻辑:多个客户端Client连接ZooKeeper集群,发送申请锁的请求
       ① 服务器 接收到请求后,在 /locks 节点下创建一个临时顺序节点。
       ②判断自己是不是当前节点下最小的节点:是,获取到锁;不是,对前一个节点进行监听。
       ③获取到锁,处理完业务后,delete 节点释放锁,然后下面的节点将收到通知,重复第二步判断。
1)分布式锁实现


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

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class DistrabuteLocks {
    private final String connectString="hadoop102:2181,hadoop103:2181,hadoop104:2181";
    private final int sessionTimeout=2000;
    private final ZooKeeper zk;
    //增加代码的健壮性
    private CountDownLatch connectLatch = new CountDownLatch(1);
    private CountDownLatch waitLatch =new CountDownLatch(1);
    private String lastNodePath; //当前节点的前一个节点的路径
    private String currentNode;

    //1、获取链接,连接上ZK
    public DistrabuteLocks() throws IOException, InterruptedException, KeeperException {
        //1)获取链接
        zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                //执行监听操作
                //connectLatch如果连接上ZK,需要释放,执行下面的操作,进行判断
                //如果监听的状态是 链接 ,就释放
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected){
                    connectLatch.countDown();//释放
                }

                //waitLatch 监听到前一个节点删除, 需要释放
                //监听到事件    节点删除 && 是前一个节点
                if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(lastNodePath))
                    waitLatch.countDown();//释放
            }
        });
        //2)等待连接上zk,再进行后续操作,相当于阻塞,等待前序进程完成
        connectLatch.await();

        //3)判断根节点"/locks"是否存在,不需监听,获取到status(状态)
        Stat stat = zk.exists("/locks", false);
        if (stat == null){
            //需要创建根节点
            zk.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }

    //2、对ZK加锁
    public void zklock(){
        try {
            //创建临时带序号的节点
            currentNode = zk.create("/locks/seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            //判断创建的节点是不是最小序号的节点,如果是获取到锁,如果不是需要监听当前序号的前一个节点
            List<String> children = zk.getChildren("/locks", false);
            //如果"/locks"下只有一个节点,直接获取锁,如果多个节点,找出最小的序号获取锁
            if (children.size() == 1)
                return;//直接返回就是,获取锁
            else {
                //先排序
                Collections.sort(children);
                //获取当前节点currentNode的定位是第几个,先获取当前节点的名称
                String thisNodeName = currentNode.substring("/locks/".length());
                int index = children.indexOf(thisNodeName);
                //判断thisNodeName是哪个位置
                if (index == -1 ){
                    System.out.println("数据异常~~~~");
                }else if (index == 0) {
                    return;//只有一个数据,就是当前节点获取锁
                }else {
                    //不是第一个节点,需要监听前一个节点的状态
                    //前一个节点的路径
                    lastNodePath = "/locks/"+ children.get(index -1);
                    //获取前一个节点的数据变化,达到监听的效果
                    zk.getData(lastNodePath,true,null);
                    //增加代码的健壮性,等待监听
                    waitLatch.await();
                    //接听结束
                    return;//获取锁
                }
            }

        } catch (KeeperException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    //3、解锁
    public void unzklock(){
        //删除节点
        try {
            zk.delete(currentNode,-1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (KeeperException e) {
            throw new RuntimeException(e);
        }
    }

}

2)分布式锁测试



import org.apache.zookeeper.KeeperException;

import java.io.IOException;

//对DistrabuteLocks进行测试,创建两个节点,查看运行状态
public class LocksTest {
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        //先创建需要测试的DistrabuteLocks 类
       final DistrabuteLocks locks1 = new DistrabuteLocks();
       final DistrabuteLocks locks2 = new DistrabuteLocks();
       final DistrabuteLocks locks3 = new DistrabuteLocks();

       //线程1
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   //加锁(创建节点)
                   locks1.zklock();
                   System.out.println("线程1 启动~~,获取到锁。");
                   //增加延时5s
                   Thread.sleep(5*1000);
                   //延时过后释放锁
                   locks1.unzklock();
                   System.out.println("线程1 结束~~,释放锁。");

               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       }).start();

        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //加锁(创建节点)
                    locks2.zklock();
                    System.out.println("线程2 启动~~,获取到锁。");
                    //增加延时5s
                    Thread.sleep(5*1000);

                    //延时过后释放锁
                    locks2.unzklock();
                    System.out.println("线程2 结束~~,释放锁。");

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

        //线程3
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //加锁(创建节点)
                    locks3.zklock();
                    System.out.println("线程3 启动~~,获取到锁。");
                    //增加延时5s
                    Thread.sleep(5*1000);

                    //延时过后释放锁
                    locks3.unzklock();
                    System.out.println("线程3 结束~~,释放锁。");

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

    }//main
}

8、Curator 框架实现分布式锁案例

1 )原生的 Java API 开发存在的问题
1 )会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
2 Watch 需要重复注册,不然就不能生效
3 )开发的复杂性还是比较高的
4 )不支持多节点删除和创建。需要自己去递归
2) Curator 案例实操


import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.BoundedExponentialBackoffRetry;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorLockTest {
    private static String NodePath= "/locks";
    private static String connectAdress="hadoop102:2181,hadoop103:2181,hadoop104:2181";

    public static void main(String[] args) {
        //1、创建分布式锁
        //使用Curator的API进行编写

        //创建分布式锁1,使用getCuratorClient()方法创建客户端
        InterProcessMutex lock1 = new InterProcessMutex(getCuratorClient(), NodePath);
        //创建分布式锁2
        InterProcessMutex lock2 = new InterProcessMutex(getCuratorClient(), NodePath);
        //创建分布式锁3
        InterProcessMutex lock3 = new InterProcessMutex(getCuratorClient(), NodePath);


        //2、测试
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock1.acquire();//获取到锁
                    System.out.println("线程1获取到锁~~~");

                    lock1.acquire();//获取到锁
                    System.out.println("线程1再次获取到锁~~~");

                    Thread.sleep(5*1000);//延时5s

                    lock1.release();//释放锁
                    System.out.println("线程1释放锁~~~");

                    lock1.release();//释放锁
                    System.out.println("线程1再次释放锁~~~");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();//启动线程

        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock2.acquire();//获取到锁
                    System.out.println("线程2获取到锁~~~");

                    lock2.acquire();//获取到锁
                    System.out.println("线程2再次获取到锁~~~");

                    Thread.sleep(5*1000);//延时5s

                    lock2.release();//释放锁
                    System.out.println("线程2释放锁~~~");

                    lock2.release();//释放锁
                    System.out.println("线程2再次释放锁~~~");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();//启动线程

        //线程3
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock3.acquire();//获取到锁
                    System.out.println("线程3获取到锁~~~");

                    lock3.acquire();//获取到锁
                    System.out.println("线程3再次获取到锁~~~");

                    Thread.sleep(5*1000);//延时5s

                    lock3.release();//释放锁
                    System.out.println("线程3释放锁~~~");

                    lock3.release();//释放锁
                    System.out.println("线程3再次释放锁~~~");
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();//启动线程

    }

    //编写获取Curator客户端的方法
    private static CuratorFramework getCuratorClient() {
        ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);
        //创建客户端
        CuratorFramework client = CuratorFrameworkFactory.builder().connectString(connectAdress) //链接地址
                .connectionTimeoutMs(2000) //链接超时时间设置
                .sessionTimeoutMs(2000) //心跳时间设置
                .retryPolicy(policy) //重新试链接次数
                .build();//客户端创建


        //启动客户端
        client.start();
        System.out.println("ZooKeeper 客户端启动成功!~~");
        return client; //返回客户端
    }
}

参考:尚硅谷ZooKeeper学习

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值