WHCSRL 技术网

学习动物园管理员

1.Zookeeper概念

Zookeeper是 Apache Hadoop项目下的一个子项目,是一个树形目录服务。
Zookeeper翻译过来就是 动物园管理员,他是用来管Hadoop (大象)、Hive(蜜蜂)、Pig(小猪)的管理员。简称水
Zookeeper是一个分布式的、开源的分布式应用程序的协调服务。
Zookeeper提供的主要功能包括:
        配置管理
        分布式锁
        集群管理


2.Zookeeper 命令操作

Zookeeper 数据模型

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

ZK服务端命令:
        ./zkServer.sh start                开启ZK
        ./zkServer.sh status             查看ZK
        ./zkServer.sh stop                关闭ZK
        ./zkServer.sh  restart            重启ZK

ZK客户端命令:
        连接ZK服务端                    ./zkCli.sh -server ip:port
        创建节点                             creat /节点path value
        获取节点值                          get /节点path
        设置节点值                          set /节点path value
        删除单个子节点                   delete /节点path
        删除带有子节点的节点        deleteall /节点path
        查看命令帮助                       help
        断开连接                              quit
        
        创建临时节点                        create -e /节点path value
        创建顺序节点                        create -s /节点path value
        查询节点详细信息                 ls -s /节点path

                czxid:节点被创建的事务ID
                ctime:创建时间
                mzxid:最后一次被更新的事务ID
                mtime:修改时间
                pzxid:子节点列表最后一次被更新的事务ID
                cversion:子节点的版本号
                dataversion:数据版本号
                aclversion:权限版本号
                ephemeralOwner:用于临时节点,代表临时节点的事务ID,如果为持久节点则为0
                dataLength:节点存储的数据的长度
                numChildren:当前节点的子节点个数

3.Zookeeper Courator介绍

Curator 介绍
        Curator是 Apache ZooKeeper 的Java客户端库。

Curator项目的目标是简化ZooKeeper客户端的使用。
Curator最初是Netfix研发的,后来捐献了Apache基金会,目前是Apache的顶级项目。

4.Zookeeper 建立连接

  1. //建立连接
  2. public void testCourator() {
  3. //建立连接
  4. @Before
  5. public void linkCourator() {
  6. /*
  7. * @param connectString 连接字符串。zk server 地址和端口 "127.0.0.1:2181"
  8. * @param sessionTimeoutMs 会话超时时间 单位ms
  9. * @param connectionTimeoutMs 连接超时时间 单位ms
  10. * @param retryPolicy 重试策略
  11. */
  12. RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
  13. // 第一种
  14. // CuratorFrameworkFactory.newClient("127.0.0.1:2181", 60 * 1000, 15 * 1000, retryPolicy);
  15. // 第二种
  16. // CuratorFramework client = CuratorFrameworkFactory.builder()
  17. client = CuratorFrameworkFactory.builder()
  18. .connectString("127.0.0.1:2181")
  19. // .connectString("192.168.89.181:2181")
  20. .sessionTimeoutMs(60 * 1000)
  21. .connectionTimeoutMs(15 * 1000)
  22. .retryPolicy(retryPolicy).namespace("jie").build();
  23. // 开启连接
  24. client.start();
  25. }
  26. @After
  27. public void close() {
  28. if (client != null){
  29. client.close();
  30. }
  31. }
  32. }

5.Zookeeper 创建节点

 创建节点:create 持久 临时 顺序 数据
        1. 基本创建 :create().forPath("")
        2. 创建节点 带有数据:create().forPath("",data)
        3. 设置节点的类型:create().withMode().forPath("",data)
        4. 创建多级节点 /app1/p1 :create().creatingParentsIfNeeded().forPath("",data)

  1. //创建节点:持久 临时 顺序 数据
  2. @Test
  3. public void testCreate() throws Exception {
  4. //基本创建
  5. //如果没有指定数据,则默认讲当前客户端的IP作为数据存储
  6. String path = client.create().forPath("/app1");
  7. System.out.println(path);
  8. }
  9. //创建节点
  10. @Test
  11. public void testCreate2() throws Exception {
  12. //带数据创建
  13. String path = client.create().forPath("/app2");
  14. System.out.println(path);
  15. }
  16. @Test
  17. public void testCreate3() throws Exception {
  18. //设置节点的类型
  19. String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3", "data".getBytes());
  20. System.out.println(path);
  21. }
  22. @Test
  23. public void testCreate4() throws Exception {
  24. //创建多级节点
  25. String path = client.create().creatingParentsIfNeeded().forPath("/app4/data");
  26. System.out.println(path);
  27. }

6.Zookeeper 查询节点

查询节点:
        1. 查询数据:get: getData().forPath()
        2. 查询子节点: ls: getChildren().forPath()
        3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath()

  1. //查询节点
  2. @Test
  3. public void testGet() throws Exception {
  4. //查询数据
  5. byte[] data = client.getData().forPath("/app1");
  6. System.out.println(new String(data));
  7. }
  8. @Test
  9. public void testGet2() throws Exception {
  10. //查询子节点
  11. List<String> list = client.getChildren().forPath("/");
  12. System.out.println(list);
  13. }
  14. @Test
  15. public void testGet3() throws Exception {
  16. Stat stat = new Stat();
  17. //查询状态信息
  18. client.getData().storingStatIn(stat).forPath("/app1");
  19. System.out.println(stat);
  20. }

7.Zookeeper 修改节点

 修改数据
        1. 基本修改数据:setData().forPath()
        2. 根据版本修改: setData().withVersion().forPath()
        version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。

  1. //修改节点
  2. @Test
  3. public void testSet() throws Exception {
  4. client.setData().forPath("/app1","new".getBytes());
  5. }
  6. @Test
  7. public void testSet2() throws Exception {
  8. Stat stat = new Stat();
  9. //查询状态信息
  10. client.getData().storingStatIn(stat).forPath("/app1");
  11. int version = stat.getVersion(); // 查询出来
  12. client.setData().withVersion(version).forPath("/app1","new_2".getBytes());
  13. // 错误版本无法修改
  14. // client.setData().withVersion(100).forPath("/app1","new_3".getBytes());
  15. }

8.Zookeeper 删除节点

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

  1. //删除节点
  2. @Test
  3. public void testDelete() throws Exception {
  4. //删除叶子节点
  5. client.delete().forPath("/app1");
  6. }
  7. @Test
  8. public void testDelete2() throws Exception {
  9. //删除非叶子节点
  10. client.delete().deletingChildrenIfNeeded().forPath("/app2/data");
  11. }
  12. @Test
  13. public void testDelete3() throws Exception {
  14. //必须成功删除 本质就是重试
  15. client.delete().guaranteed().forPath("/app2");
  16. }
  17. @Test
  18. public void testDelete4() throws Exception {
  19. //回调
  20. client.delete().guaranteed().inBackground(new BackgroundCallback() {
  21. @Override
  22. public void processResult(CuratorFramework curatorFramework, CuratorEvent curatorEvent) throws Exception {
  23. System.out.println("die");
  24. System.out.println(curatorEvent);
  25. }
  26. }).forPath("/app1");
  27. }

9.Zookeeper Watch监听概述

Curator API常用操作–Watch事件监听
        ZooKeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是ZooKeeper实现分布式协调服务的重要特性。
        ZooKeeper中引入了Watcher机制来实现了发布/订阅功能能,能够让多个订阅者同时监听某一个对象,当一个对象自身状态变化时,会通知所有订阅者。
        ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便需要开发人员自己反复注册Watcher,比较繁琐。
        Curator引入了Cache来实现对ZooKeeper服务端事件的监听。
        ZooKeeper提供了三种Watcher
                NodeCache:只是监听某一个特定的节点
                PathChildrenCache:监控一个ZNode的子节点.
                TreeCache:可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合

10.Zookeeper NodeCache

NodeCache:只是监听某一个特定的节点

  1. //演示 NodeCache:给指定节点注册监听器
  2. @Test
  3. public void testNodeCache() throws Exception {
  4. //创建NodeCache
  5. final NodeCache nodeCache = new NodeCache(client, "/mydubbo");
  6. //注册监听
  7. nodeCache.getListenable().addListener(new NodeCacheListener() {
  8. @Override
  9. public void nodeChanged() throws Exception {
  10. System.out.println("节点变化了!");
  11. //获取修改节点后的数据
  12. byte[] data = nodeCache.getCurrentData().getData();
  13. System.out.println(new String(data));
  14. }
  15. });
  16. //开启监听,如果设置为true,则开启监听时,加载缓冲数据
  17. nodeCache.start(true);
  18. //不让程序直接结束
  19. while (true) {
  20. }
  21. }

11.Zookeeper PathChildrenCache

PathChildrenCache:监控一个ZNode的子节点.

  1. //演示 PathChildrenCache:监听某个节点的所有子节点
  2. @Test
  3. public void testPathChildrenCache() throws Exception {
  4. //创建监听对象
  5. PathChildrenCache pathChildrenCache = new PathChildrenCache(client, "/mydubbo", true);
  6. //绑定监听器
  7. pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {
  8. @Override
  9. public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
  10. System.out.println("节点变化了!");
  11. System.out.println(pathChildrenCacheEvent);
  12. //获取修改节点后的数据
  13. //获取类型
  14. PathChildrenCacheEvent.Type type = pathChildrenCacheEvent.getType();
  15. if (type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
  16. byte[] data = pathChildrenCacheEvent.getData().getData();
  17. System.out.println(new String(data));
  18. }
  19. }
  20. });
  21. //开启监听,如果设置为true,则开启监听时,加载缓冲数据
  22. pathChildrenCache.start();
  23. //不让程序直接结束
  24. while (true) {
  25. }
  26. }

12.Zookeeper TreeCache

TreeCache:可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合 

  1. //演示 TreeCache:监听某个节点自己和所有子节点
  2. @Test
  3. public void testTreeCache() throws Exception {
  4. //创建监听对象
  5. TreeCache treeCache = new TreeCache(client,"/mydubbo");
  6. //绑定监听器
  7. treeCache.getListenable().addListener(new TreeCacheListener() {
  8. @Override
  9. public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
  10. System.out.println("节点变化了!");
  11. System.out.println(treeCacheEvent);
  12. //获取修改节点后的数据
  13. //获取类型
  14. TreeCacheEvent.Type type = treeCacheEvent.getType();
  15. if (type.equals(TreeCacheEvent.Type.NODE_UPDATED)){
  16. byte[] data = treeCacheEvent.getData().getData();
  17. System.out.println(new String(data));
  18. }
  19. }
  20. });
  21. //开启监听,如果设置为true,则开启监听时,加载缓冲数据
  22. treeCache.start();
  23. //不让程序直接结束
  24. while (true) {
  25. }
  26. }

13.分布式锁 概念

分布式锁
        在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者Lock的方式来解决多线程间的代码同续问题,这时多线程的运行都是在同一个JVM之下,没有任何问题。
        但当我们的应用是分布式集群工作的情况下,属于多 JVM下的工作环境,跨JVM之间已经无法通过多线程的锁解决同步问题。
        那么就需要一种更加高级的锁机制,来处理这种跨机器的进程之间的数据同步问题这就是分布式锁。

14.ZooKeeper 分布式锁原理

核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点

1、客户端获取锁时,在lock节点下创建临时顺序节点

2、然后获取lock下面的所有子节点,客户端获取到所有子节点后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。

3、如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还有没获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。

4、如果发现比自己小的那个节点被删除,则客户端的watcher会收到相应的通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的值,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。

15.Curator 实现分布式锁

在Curator中有五种锁方案:
        InterProcessSemaphoreMutex:分布式排它锁(非可重入锁)
        InterProcessMutex:分布式可重入排它锁
        InterProcessReadWriteLock:分布式读写锁
        InterProcessMultiLock:将多个锁作为单个实体管理的容
        InterProcessSemaphoreV2:共享信号量

  1. public class Ticket12306 implements Runnable{
  2. private int tickets = 100;//数据库的票数
  3. private InterProcessMutex lock ;
  4. public Ticket12306(){
  5. //重试策略
  6. RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);
  7. //2.第二种方式
  8. //CuratorFrameworkFactory.builder();
  9. CuratorFramework client = CuratorFrameworkFactory.builder()
  10. .connectString("127.0.0.1:2181")
  11. .sessionTimeoutMs(60 * 1000)
  12. .connectionTimeoutMs(15 * 1000)
  13. .retryPolicy(retryPolicy)
  14. .build();
  15. //开启连接
  16. client.start();
  17. lock = new InterProcessMutex(client,"/lock");
  18. }
  19. @Override
  20. public void run() {
  21. while(true){
  22. //获取锁
  23. try {
  24. lock.acquire(3, TimeUnit.SECONDS);
  25. if(tickets > 0){
  26. System.out.println(Thread.currentThread()+":"+tickets);
  27. tickets--;
  28. }
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }finally {
  32. //释放锁
  33. try {
  34. lock.release();
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }
  40. }
  41. }

16.Zookeeper集群

Leader选举:
        Serverid:服务器ID比如有三台服务器,编号分别是1,2,3。编号越大在选择算法中的权重越大。
        Zxid:数据ID服务器中存放的最大数据ID.值越大说明数据 越新,在选举算法中数据越新权重越大。
        在Leader选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了

Zookeeper 集群角色

在ZooKeeper集群服中务中有三个角色:
Leader 领导者 :
        处理事务请求
        集群内部各服务器的调度者

Follower 跟随者 :
        处理客户端非事务请求,转发事务请求给Leader服务器
        参与Leader选举投票  

Observer 观察者:
        处理客户端非事务请求,转发事务请求给Leader服务器

推荐阅读