美文网首页
zookeeper 读书笔记

zookeeper 读书笔记

作者: NazgulSun | 来源:发表于2020-08-20 10:17 被阅读0次

    提纲

    zookeper 主从机制下的分布式容错

    1, 数据如何同master 到slave
    2, master 挂了如何选举,对当前的数据有什么影响
    3, follow挂了又恢复了,对数据有什么影响,如何与master同步
    4, 什么是脑裂的问题,又是如何解决的
    zookeper 主从机制下的分布式容错
    1, 数据如何同master 到slave

    不同于kafka的follow拉取机制,zookeeper由master主导,所有数据的写入,都是通过master来负责的,
    master通过类似于二段提交的方式,同步数据到slave。
    master和slave的机制,可以用帝王颁布政令的方式来类比。
    1)皇帝写好了政令, 先和地方政府通气,看看他们有什么意见(系统里是发送预请求)
    2)地方政府,认同的,试运行。 然后发送消息,说可以了没问题(ack)
    3)皇帝,看全国大半都说试运行可以,就下令,正式执行(commit)
    4)全国各自政府,颁布命令,正式执行,并发送结果给皇帝。

    2), follow挂了又恢复了,对数据有什么影响,如何与master同步
    地方政府挂了,就是有可能错过了,皇帝的政令,但是地方政府重新运行的时候,
    他可以看看自己的档案馆里(commit log,snapshot里面)看看,我上次接收的行政指令是什么,
    这个新政指令是 康熙10年(epoch+ txdId), 然后现在已经能够是 康熙11年的指令,那么地方政府就
    派人去取康熙11年的指令,进而完成同步。

    3), master 挂了如何选举,对当前的数据有什么影响
    康熙挂了,那么就要搞九子夺嫡,选一个新的皇帝出来。
    这里有一个规则,优先选txdId 大的节点做皇帝,txdId大,代表执行了最多的康熙爷的行政指令,
    如果一样,就找 serverId 大的,代表长子为大。 选出来做了新皇帝,就是雍正爷,继续为大家提供服务。
    其他小弟,继续同步之前为完成的指令,并开始接收雍正爷的新指令。
    这个时候,康熙炸尸(复活)了怎么办? 也没办法,也只能是做小弟了

    4), 什么是脑裂的问题,又是如何解决的
    脑裂问题,是一种特殊场景下的问题,不同机房之间,网络不通了。
    机房A 有3太机器,机房b 有3 台机器,那么就会选出 2个leader。 等恢复之后就错乱了。
    为了解决这种问题,zookeeper,规定在选举leader的时候,只有超过总数1半的票才能当选,
    也就是4台(超过2台),这个时候,机房断开互联,就没有leader。
    没有leader,可以提供读服务,不能提供写服务。

    zookeeper 的分布式锁是如何实现的

    1, 实现lock 接口的方法, 用永久节点实现?
    2, 用临时顺序节点实现
    3, 用临时顺序理解实现,各自有什么问题
    4, zookeeper 如何保障新建节点的原子性,新建之后master failed之后会有什么问题,如何做的容错
    1, 实现lock 接口的方法, 用永久节点实现?
    永久节点,如果一个用户获得了锁,然后宕机了,session断了,但节点还在不会删除,别人就死锁了
    2, 用临时节点实现
    解决了上述宕机的问题,但是所有的节点都监听一个事件,每一次变化都会有大量的事件数据发给监听者,
    造成惊群效应,浪费网络流量。而起存在大量的线程的唤醒,马上因为竞争失败而又陷入竞争中,造成性能低下
    3, 用临时顺序节点实现:
    非常巧妙的思路,让所有的竞争者排队,队列一个好了,通知后面一个人,没有精确效应,一次精准唤醒一个人。性能也高很多。

    临时节点的思路:

    1. trylock, 如果节点不存在则创建,成功就获得锁。不成功,就进行阻塞等待。
    2. 阻塞等待用到cdl,countdownlatch。同事监听锁节点的变化,如果有人释放锁。就用cdl唤醒线程
    3. 唤醒之后,再一次尝试获得锁。
    @Override
        public void releaseLock() {
            if (null != zkClient) {
                //删除节点
                zkClient.delete(NODE_NAME);
                zkClient.close();
                System.out.println(Thread.currentThread().getName()+"-释放锁成功");
            }
            
        }
     
        //直接创建临时节点,如果创建成功,则表示获取了锁,创建不成功则处理异常
        @Override
        public boolean tryLock() {
            if (null == zkClient) return false;
            try {
                zkClient.createEphemeral(NODE_NAME);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
     
        @Override
        public void waitLock() {
            //监听器
            IZkDataListener iZkDataListener = new IZkDataListener() {
                //节点被删除回调
                @Override
                public void handleDataDeleted(String dataPath) throws Exception {
                    if (countDownLatch != null) {
                        countDownLatch.countDown();
                    }
                }
                //节点改变被回调
                @Override
                public void handleDataChange(String dataPath, Object data) throws Exception {
                    // TODO Auto-generated method stub
                    
                }
            };
            zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
            //如果存在则阻塞
            if (zkClient.exists(NODE_NAME)) {
                countDownLatch = new CountDownLatch(1);
                try {
                    countDownLatch.await();
                    System.out.println(Thread.currentThread().getName()+" 等待获取锁...");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            //删除监听
            zkClient.unsubscribeDataChanges(NODE_NAME, iZkDataListener);
        }
    

    临时顺序节点的思路:

    1. 每个线程启动调用zk api创建顺序节点,由zk内部机制保证顺序性。
    2. 获取整个顺序节点list,如果第一个节点是自己,则代表获得锁。
    3. 如果第一个不是自己,则监听前一个节点, 使用cdl机制,如果前一个删除(release)了,则下一个就是我,cdl马上唤醒自己。做业务处理。
    public HighPerformanceZkLock() {
            //如果不存在这个节点,则创建持久节点
            if (!zkClient.exists(PATH)) {       
                zkClient.createPersistent(PATH);
            }
        }
        
        @Override
        public void releaseLock() {
            if (null != zkClient) {
                zkClient.delete(currentPath);
                zkClient.close();
            }
     
        }
     
        @Override
        public boolean tryLock() {
            //如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
            if (null == currentPath || "".equals(currentPath)) {
                //在path下创建一个临时的顺序节点
                currentPath = zkClient.createEphemeralSequential(PATH+"/", "lock");
            }
            //获取所有的临时节点,并排序
            List<String> childrens = zkClient.getChildren(PATH);
            Collections.sort(childrens);
            if (currentPath.equals(PATH+"/"+childrens.get(0))) {
                return true;
            }else {//如果当前节点不是排名第一,则获取它前面的节点名称,并赋值给beforePath
                int pathLength = PATH.length();
                int wz = Collections.binarySearch(childrens, currentPath.substring(pathLength+1));
                beforePath = PATH+"/"+childrens.get(wz-1);
            }
            return false;
        }
     
        @Override
        public void waitLock() {
            IZkDataListener lIZkDataListener = new IZkDataListener() {
                
                @Override
                public void handleDataDeleted(String dataPath) throws Exception {
                    if (null != countDownLatch){
                        countDownLatch.countDown();
                    }
                }
                
                @Override
                public void handleDataChange(String dataPath, Object data) throws Exception {
                    
                }
            };
            //监听前一个节点的变化
            zkClient.subscribeDataChanges(beforePath, lIZkDataListener);
            if (zkClient.exists(beforePath)) {
                countDownLatch = new CountDownLatch(1);
                try {
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            zkClient.unsubscribeDataChanges(beforePath, lIZkDataListener);
        }
     
    
    
    zookeeper 的底层存储接口

    1,内存
    2,snapshot
    3,commit log
    zookeeper的存储机制类似与 redis,采用了 内存做高性能访问,使用snapshot 存储在 磁盘。
    因为性能不可能实时做snapshot,引入commit log。 也就是一个操作要是被视为成功,必须要写入到commit log。
    有了snap +log,可以从事故中恢复当时的状态。

    这里牵涉到一些异常处理的问题:
    如果 write 到了内存+log中。 这个时候还没有 向follower同步,就挂了。
    这个节点恢复之后,最后一次的commit 会被丢掉。
    如果,这个commit 提交到了一部分 follower。那么这个 commit 会被认可。
    因为其他的选举的leader 是能够同步到最后这个commit的。

    zookeeper 与kafa的分布式容错,master选举机制等有何不同

    1,各自的特点
    2,设计哲学不同的考虑

    kafka,高性能,大数据存储。N个节点,容错N-1个节点。
    幂等性+事务性的实现难度大。
    ISR机制,写入更快

    相关文章

      网友评论

          本文标题:zookeeper 读书笔记

          本文链接:https://www.haomeiwen.com/subject/stksjktx.html