Zookeeper分布式一致性与实践

1、Zookeeper概述

zookeeper是一个开源的分布式协调服务框架,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题。

1.1、Zookeeper的设计目标

1)简单的数据模型:使得分布式程序能通过一个共享的,树形结构的名字空间来相互协调。其数据模型类似于一个文件系统。
2)可以构建集群:集群的每台机器都会在内存中维护服务器状态,并且每台机器互相保持通信,只要有半数以上的机器正常工作,就能正常对外服务。
3)顺序访问:每个更新请求,都有有一个全局唯一的递增编号。
4)高性能:ZooKeeper将全量数据存储在内存中,适用于以读为主的应用场景。

1.2、Zookeeper在保证分布式一致性时,具有以下五点特性:

1)顺序一致性:同一客户端发起的事务请求,会严格地按照其发起的顺序送到ZooKeeper。
2)原子性:要么所有节点都成功执行了事务,要么全都没有执行
3)单一试图:无论客户端连接的是哪个ZooKeeper服务器,看到的数据模型都是一样的。
4)可靠性:事务引起的服务端状态变更会被一直保留下来。
5)实时性:ZooKeeper仅仅保证一定时间内,客户端能读到最新数据。

1.3、Zookeeper中的基本概念

Zookeeper概述图.png
1)会话:会话周期从第一次建立连接开始,客户端能通过心跳检测与服务器保持有效会话。
2)数据节点Znode:
3)Wacher事件监听器:
4)ACL(权限控制策略):

1.4、Zookeeper中的ZAB协议

zookeeper的数据一致性算法所采用的是ZooKeeper Atomic Broadcast(ZAB,Zookeeper原子信息广播协议)。此协议是为分布式协调服务Zookeepe专门设计的一种支持崩溃服务的原子广播协议。

image.png

1.4.1、协议中的两种基本模式(后续有机会遇到再说吧)

1)崩溃恢复模式

2)消息广播模式

2、Zookeeper常规操作

2.1、Zookeeper运行环境

1)单机模式:用于开发和测试。
2)伪分布式:一台物理机虚拟机几台虚拟机搭建的Zookeeper集群。
3)分布式:

2.2、客户端脚本的shell操作

image.png

2.3、Zookeeper原生JavaAPI操作

2.4、开源客户端zkClient操作

2.5、开源客户端Curator操作

curator客户端创建会话的方法和前两种方法有着一定的不同:
1)使用CuratorFrameworkFactory这个工厂类的两个静态方法(参数不用的newClient方法)来创建一个客户端。
2)使用CuratorFramework中的start()方法来启动方法。

(1)环境搭建(pom.xml)
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-client</artifactId>
    <version>3.2.1</version>
</dependency>
(2)必要的环境变量
      public class CommonConfig {
      public static final String NAMESPACE = "test";
      // retryPolicy 指的是连接过程中的重试策略,参数中的1000指的是响应时间1秒,3指的是重试次数。
      public static final ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
      public static final String LOCALHOST = "127.0.0.1:2181";


     // 创建client的两种方式:
      public static final CuratorFramework defaultClient = CuratorFrameworkFactory.builder()
              .connectString(LOCALHOST).retryPolicy(retryPolicy)
              .connectionTimeoutMs(1000)
              .sessionTimeoutMs(1000)
              .namespace(NAMESPACE)//每个对话创建一个隔离的命名空间
              .build();

      
      RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 1);
      CuratorFramework client = CuratorFrameworkFactory.newClient("master:2181,slave1:2181,slave2:2181",1000,1000,retryPolicy);

}
(3)基本操作(这部分都是使用Curator框架中的所提供的同步接口)
/**
 * 基本操作:创建会话,创建节点,删除节点,获取节点存储内容,更新节点存储的内容
 */
public class BaseOperation {
    public static final String PATH = "/basictest";

    public static void main(String[] args) {
        //创建会话对象
        CuratorFramework client = CommonConfig.defaultClient;
        //开始会话
        client.start();
        try {
            //创建一个节点,初始内容为空
            client.create().forPath(PATH);
            //创建一个节点,附带初始内容
            client.create().forPath(PATH, "basictest1".getBytes());
            //创建一个临时节点,附带初始内容
            client.create().withMode(CreateMode.EPHEMERAL).forPath(PATH, "basictest2".getBytes());
            //创建一个临时节点,父节点不存在,递归创建父节点,创建的父节点都是持久节点
            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(PATH, "basictest3".getBytes());

            //关闭会话
            Closeables.close(client, true);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void delete(CuratorFramework client, String path) throws Exception {
        //删除一个节点
        client.delete().forPath(path);
        //删除一个节点,递归删除子节点
        client.delete().deletingChildrenIfNeeded().forPath(path);
        //删除一个节点,强制指定版本进行删除
        client.delete().withVersion(1).forPath(path);
        //删除一个节点,强制保证删除。Curator引入了重试机制,一旦删除失败,会在后台反复重试直到删除成功
        client.delete().guaranteed().forPath(path);
        return;
    }

    public static String get(CuratorFramework client, String path) throws Exception {
        //读取一个节点的内容
        String str = new String(client.getData().forPath(path));
        //读取一个节点的内容,同时获取到该节点的stat,stat保存的是从服务器获取到的状态数据
        Stat stat = new Stat();
        client.getData().storingStatIn(stat).forPath(path);
        return str;
    }

    public static Stat set(CuratorFramework client, String path) throws Exception {
        Stat stat = new Stat();
        //更新一个节点的数据内容,返回Stat对象
        stat = client.setData().forPath(path);
        //更新一个节点的数据内容,强制指定版本进行更新,用来实现CAS
        stat = client.setData().withVersion(1).forPath(path);
        return stat;
    }
}
(4)创建节点的异步操作

在Zookeeper中所有的异步通知事件处理,都是由EventThread这个线程来处理的,EventThread采用的是串行处理机制,所以未传入线程池的事件会被EventThread默认处理,也就是主线程。

static String path = "/backgroundtest";
    // 所以这个测试的意思就是:使用两个异步创建节点的实例来进行对比实验,一个实例在异步创建的时候传入ExecutorService 线程池,一个不传入。
    //创建client
    static CuratorFramework client = CommonConfig.defaultClient;
    // Java并发工具
    static CountDownLatch semaphore = new CountDownLatch(2);
   // 创建一个可重用固定线程数的线程池
    static ExecutorService tp = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        try {
            client.start();
            System.out.println("Main thread: " + Thread.currentThread().getName());
            // 此处传入了自定义的Executor
            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
                @Override
                public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                    System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]");
                    System.out.println("Thread of processResult: " + Thread.currentThread().getName());
                    semaphore.countDown();
                }
            }, tp).forPath(path, "init1".getBytes());
            // 此处没有传入自定义的Executor
            client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).inBackground(new BackgroundCallback() {
                @Override
                public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
                    System.out.println("event[code: " + event.getResultCode() + ", type: " + event.getType() + "]");
                    System.out.println("Thread of processResult: " + Thread.currentThread().getName());
                    semaphore.countDown();
                }
            }).forPath(path, "init2".getBytes());
            semaphore.await();
            Closeables.close(client, true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            tp.shutdown();
        }
    }
(5)事件监听

Zookeeper原生通过Watcher注册来进行事件监听,但是这需要开发人员反复的注册Watcher,比较繁琐。Curator引入Cache来实现对Zookeeper服务端事件的监听,其对事件的监听可以类比成一个本地缓存视图和远程Zookeeper视图的对比过程。Cache分为两种监听类型:节点监听(NodeCache)和子节点监听(PathChildrenCache)。

节点监听

节点监听用于监听数据节点自身变化。

/**
 * @author : HaiLiang Huang
 * @date : 2021年4月14日15:18:47
 */
public class CuratorBaseOperator {
    public static final  String PATH = "/nodeCache";
    public static final CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();
        client.create()
                .creatingParentsIfNeeded()
                .withMode(CreateMode.EPHEMERAL)
                .forPath(PATH,"init".getBytes());

        final NodeCache nodeCache = new NodeCache(client, PATH, false);
        nodeCache.start(true);

        nodeCache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("Node data update, new data: " +
                        new String(nodeCache.getCurrentData().getData()));
            }
        });
        client.setData().forPath(PATH, "u".getBytes());

        Thread.sleep(1000);
        
        client.delete().deletingChildrenIfNeeded().forPath(PATH);

        Thread.sleep(Integer.MAX_VALUE);

    }
}

子节点监听

PathChildrenCache用于监听子节点的变化(新增子节点、子节点数据变更、子节点删除)
无法监听二级子节点的变化

public class PathChildrenCacheDemo {
    static String path = "/pathChiledrenCache";
    static CuratorFramework client = CommonConfig.defaultClient;
    static ExecutorService tp = Executors.newFixedThreadPool(2);

    public static void main(String[] args) throws Exception {
        client.start();
        PathChildrenCache cache = new PathChildrenCache(client, path, true, false, tp);
        cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
        cache.getListenable().addListener(new PathChildrenCacheListener() {
            public void childEvent(CuratorFramework client,
                                   PathChildrenCacheEvent event) throws Exception {
                switch (event.getType()) {
                    case CHILD_ADDED:
                        System.out.println("CHILD_ADDED," + event.getData().getPath());
                        break;
                    case CHILD_UPDATED:
                        System.out.println("CHILD_UPDATED," + event.getData().getPath());
                        break;
                    case CHILD_REMOVED:
                        System.out.println("CHILD_REMOVED," + event.getData().getPath());
                        break;
                    default:
                        break;
                }
            }
        });
        client.create().withMode(CreateMode.PERSISTENT).forPath(path);
        Thread.sleep(1000);
        client.create().withMode(CreateMode.PERSISTENT).forPath(path + "/c1");
        Thread.sleep(1000);
        client.delete().forPath(path + "/c1");
        Thread.sleep(1000);
        client.delete().forPath(path);
        Thread.sleep(Integer.MAX_VALUE);
    }
}
(6)Master选举
/**
 * Master选举。选择一个根节点,多台机器同时创建一个自己节点/lock。
 * 利用zookeeper的特性,最终只能有一个创建成功
 * 成功获取到Master权利之后,会回调监听器的takeLeaderShip()方法,
 * 一旦方式执行完毕,会立即释放掉Master权利,监听器需要开发者自定义实现
 * 当一个应用实例称为Master之后,其他竞选者会进入等待,
 * 直到当前Master挂了或退出后才开始下一轮的竞选
 */
public class LeaderSelectDemo {
    static String master_path = "/masterSelect";

    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();
        LeaderSelector selector = new LeaderSelector(client,
                master_path,
                new LeaderSelectorListenerAdapter() {
                    public void takeLeadership(CuratorFramework client) throws Exception {
                        System.out.println("成为Master角色");
                        Thread.sleep(3000);
                        System.out.println("完成Master操作,释放Master权利");
                    }
                });
        selector.autoRequeue();
        selector.start();
        Thread.sleep(Integer.MAX_VALUE);
    }
}
(7)分布式锁
/**
 * 分布式锁:
 */
public class Lock {
    static String lock_path = "/lock";
    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();
        final InterProcessMutex lock = new InterProcessMutex(client, lock_path);
        final CountDownLatch down = new CountDownLatch(1);
        for (int i = 0; i < 30; i++) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        down.await();
                        lock.acquire();
                    } catch (Exception e) {
                    }
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss|SSS");
                    String orderNo = sdf.format(new Date());
                    System.out.println("生成的订单号是 : " + orderNo);
                    try {
                        lock.release();
                    } catch (Exception e) {
                    }
                }
            }).start();
        }
        down.countDown();
    }
}
(8)分布barrier
/**
 * Created by wangzhongqiu on 2018/5/6.
 * 分布式Barrier:分为主动放开栅栏和达到指定数量自动放开栅栏
 */
public class Barrier {
    static String barrier_path = "/barrier";
    static DistributedBarrier barrier;
    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        atoBarrier();
    }

    public static void barrier() throws Exception {
        barrier = new DistributedBarrier(client, barrier_path);
        client.start();
        barrier.setBarrier();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "号barrier设置");
                        barrier.waitOnBarrier();
                        System.err.println("启动...");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        barrier.removeBarrier();
    }

    public static void atoBarrier() throws Exception {
        client.start();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client, barrier_path, 5);
                        System.out.println(Thread.currentThread().getName() + "号barrier设置");
                        barrier.enter();
                        System.err.println("启动...");
                        barrier.leave();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
    }
}
(9)分布式计数器
/**
 * Created by wangzhongqiu on 2018/5/6.
 * 分布式计数器:指定一个节点作为计数器,
 * 多个机器在分布式锁的控制下,通过更新该节点的数据来实现分布式计数器功能
 */
public class DistAtomicInt {
    static String distatomicint_path = "/distAtomicInt";
    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();
        DistributedAtomicInteger atomicInteger =
                new DistributedAtomicInteger(client, distatomicint_path,
                        new RetryNTimes(3, 1000));
        AtomicValue<Integer> rc = atomicInteger.add(8);
        System.out.println("Result: " + rc.succeeded());
    }
}

(10)工具–ZKPaths
/**
 * @date 2018/5/7.
 * 工具类:ZKPaths,提供了一些简答的API来构建Znode路径、递归创建和删除节点等。使用方式非常方便
 */
public class ZKPathsDemo {
    static String path = "/zkpath";
    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();
        ZooKeeper zookeeper = client.getZookeeperClient().getZooKeeper();

        System.out.println(ZKPaths.fixForNamespace(path, "sub"));
        System.out.println(ZKPaths.makePath(path, "sub"));
        System.out.println(ZKPaths.getNodeFromPath("/curator_zkpath_sample/sub1"));

        ZKPaths.PathAndNode pn = ZKPaths.getPathAndNode("/curator_zkpath_sample/sub1");
        System.out.println(pn.getPath());
        System.out.println(pn.getNode());

        String dir1 = path + "/child1";
        String dir2 = path + "/child2";
        ZKPaths.mkdirs(zookeeper, dir1);
        ZKPaths.mkdirs(zookeeper, dir2);
        System.out.println(ZKPaths.getSortedChildren(zookeeper, path));

        ZKPaths.deleteChildren(client.getZookeeperClient().getZooKeeper(), path, true);
    }
}
(11)工具–EnsurePath
/** 
 * @date 2018/5/7.
 * 工具类:能够确保数据节点存在的机制。内部实现了试图创建节点,
 * 如果已经存在那么就不进行任何操作,也不对外抛出异常,否则正常创建数据节点
 */
public class EnsurePathDemo {
    static String path = "/ensurePath/c1";
    static CuratorFramework client = CommonConfig.defaultClient;

    public static void main(String[] args) throws Exception {
        client.start();

        EnsurePath ensurePath = new EnsurePath(path);
        ensurePath.ensure(client.getZookeeperClient());
        ensurePath.ensure(client.getZookeeperClient());

        EnsurePath ensurePath2 = client.newNamespaceAwareEnsurePath("/c1");
        ensurePath2.ensure(client.getZookeeperClient());
    }
}
(12)TestingCluster

/**
 * 测试工具类:是一个可以模拟Zookeeper集群的环境的工具类
 */
public class TestingClusterDemo {
    public static void main(String[] args) throws Exception {
        TestingCluster cluster = new TestingCluster(3);
        cluster.start();
        Thread.sleep(2000);

        TestingZooKeeperServer leader = null;
        for(TestingZooKeeperServer zs : cluster.getServers()){
            System.out.print(zs.getInstanceSpec().getServerId()+"-");
            System.out.print(zs.getQuorumPeer().getServerState()+"-");
            System.out.println(zs.getInstanceSpec().getDataDirectory().getAbsolutePath());
            if( zs.getQuorumPeer().getServerState().equals( "leading" )){
                leader = zs;
            }
        }
        leader.kill();
        System.out.println( "--After leader kill:" );
        for(TestingZooKeeperServer zs : cluster.getServers()){
            System.out.print(zs.getInstanceSpec().getServerId()+"-");
            System.out.print(zs.getQuorumPeer().getServerState()+"-");
            System.out.println(zs.getInstanceSpec().getDataDirectory().getAbsolutePath());
        }
        cluster.stop();
    }
}
(13)TestingServer
/**
 * @date 2018/5/7.
 * 测试工具类:能够启动一个简易的标准的zookeeper服务,以此来进行一系列的单元测试
 */
public class TestingServerDemo {
    static String path = "/test";

    public static void main(String[] args) throws Exception {
        TestingServer server = new TestingServer(2181, new File("/home/admin/zk-test-data"));

        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(server.getConnectString())
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
        System.out.println(client.getChildren().forPath(path));
        server.close();
    }
}

参考文献:

[01] 从Paxos到Zookeeper分布式一致性原理与实践

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值