Zookeeper小结

Zookeeper

zookeeper解决的问题

  • zookeeper分布锁

  • zookeeper监控集群

CAP理论:高可用容错性一致性最多取其二

BASE理论:B基本可用 、弱状态、最终一致性

zookeeper功能:Dubbo服务治理框架

  • 发布订阅:redis mq

  • 负载均衡:nginx apche httpd

  • 命名服务:

  • 分布式协调 通知

  • 集群管理 :

  • Master选举:主从结构

  • 分布式锁:redis

  • 分布式队列

高可用和一致性取舍问题

分布式事务 强一致性:2PC(二阶段提交) 3PC(3阶段提交)

不论是二阶段(强一致性)提交还是三阶段提交 本质是尽可能晚提交(在事务提交前完成其他所有工作)。提交阶段耗时较短出现错误概率低。

2PC]

二阶段缺点:

  • 同步阻塞:执行过程中所有节点参与者事务阻塞。譬如使用锁进行隔离性操作时事务没提交占用锁资源并发量很差。
  • 单点故障:协调者过于重要,协调者出现故障参与者一直阻塞。即使重新选举也没法解决问题
  • 数据不一致:协调者向参与者发送commit请求后,出现网络故障,部分参与者执行commit操作,部分为接收到无法进行事务提交。整个系统出现数据不一致问题。
  • 无法确定问题:commit后协调者和参与者同时宕机,此条事务状态不可控。即使重新选出协调者也无法确定事务是否被执行。
  • 过于保守:缺乏有效容错机制,任何一个节点的失败会导致整个事务提交失败回滚。

JTA(java里面的分布式事务编程接口规范-JTA) JBOSS(天生支持分布式)

三阶段特点:

3PC

  • cancommit:询问所有人,根据返回结果决定是否可以执行操作。
  • precommit:预执行,如果超时默认执行失败
  • docommit:即使超时也会执行

解决了单点故障问题引发问题,减少阻塞发生。根据定时器,没有接收到协调者的信息默认执行commit,可能造成数据不一致问题。(其他参与者可能执行了abort)

高可用性 集群 服务拆分:主从结构->MASTER选举 节点数据不一致

PAXOS算法:zookeeper核心原理 对多个分布式线程数据达成一致的算法

NWR策略 N:数据分片 W:Write 至少由W份数据写入成功 R:Read 至少有R份数据读取成功。 不要求数据强一致性 解决读写均衡的一种方式。Read Only Write All 本质上释放了写模式的压力转给读模式。一般情况要求W+R>N。保证不存在一个数据同时存在读写,一般读写超过N/2就返回结果成功,后续数据同步更新操作由系统后台运行。

ZOOKEEPER保证

  • 顺序一致性
  • 原子性
  • 单一视图
  • 可靠性
  • 实时性

zookeeper本身就是个分布式系统 单点故障?

一般部署奇数个zk服务器 服务器宕机数量节点总数不变若部署5个3个宕机失败 若6个3个宕机失败 so//

Zab

zk只有一个master 多个slaver

Zab(Zookeeper Atomic Broadcast):解决事务一致性问题zk使用ZAB协议。专门用来设计支持崩溃回复原子广播的协议。

Zookeeper使用主备模式保证数据一致性:

在这里插入图片描述

客户端将信息写入主进程(leader)中然后由leader将数据备份到follower中

复制过程同2PC类似,只要由一般备份返回ACK即可执行提交。

Zookeeper的两种模式

  • 消息广播
  • 崩溃回复

消息广播

ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)。

注意细节
  1. Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 兮协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理。
  2. 在 Leader 和 Follwer 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞。HOW?
  3. zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理。
  4. 实际上,这是一种简化版本的 2PC,不能解决单点问题。等会我们会讲述 ZAB 如何解决单点问题(即 Leader 崩溃问题)。

崩溃恢复

​ 当leader失去与过半的follower联系,进入崩溃回复模式。ZAB协议定义了两个原则:

  • ZAB协议确保已经在leader上提交的的事务最终会被服务器所提交。
  • ZAB协议确保丢弃那些只在leader上提出/复制。但是没被提交的事务。

解决思路:如果有这样的ZAB选举算法:根据zxid,保证选出来的服务器上有当前zxid最大的事务,那么就能保证当前服务器一定有已经成功提交的事务。

数据同步

崩溃恢复之后,在接受客户端新的请求之气那,leader服务器需要确认事务是否都被过半的follwer提交,即是否完成数据同步。当确认所有的有效follower提交数据后将这些服务器添加到可用服务器列表中。如何做:

​ 通过zxid(服务器处理或是丢弃事务)在这里插入图片描述

在 ZAB 协议的事务编号 ZXID 设计中,ZXID 是一个 64 位的数字,其中低 32 位可以看作是一个简单的递增的计数器,针对客户端的每一个事务请求,Leader 都会产生一个新的事务 Proposal 并对该计数器进行 + 1 操作。

高 32 位代表了每代 Leader 的唯一性,低 32 代表了每代 Leader 中事务的唯一性。同时,也能让 Follwer 通过高 32 位识别不同的 Leader。简化了数据恢复流程。

基于这样的策略:当 Follower 链接上 Leader 之后,Leader 服务器会根据自己服务器上最后被提交的 ZXID 和 Follower 上的 ZXID 进行比对,比对结果要么回滚,要么和 Leader 同步。

Zookeeper分布式锁

实现方式:

  • 单节点锁
  • 多节点锁

节点种类:

  1. 持久节点(Persistent),客户端断开连接不删除节点。
  2. 持久化顺序节点(Persisident_Sequential)znode天生互斥,断开连接后节点存在,zookeeper给该节点名称进行顺序编号。
  3. 临时节点(Ephemeral),客户端断开连接后该节点被删除
  4. 临时顺序节点(EPHEMERAL_SEQUENTIAL)客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

加锁方式:创建节点,解锁方式:删除节点。

如果使用永久节点,当获取节点的客户端宕机,其他客户端不能获取锁,产生死锁。采用临时节点相当于增加了一个失效时间。

单节点锁实现方式

(1)、当一个客户端成功创建一个节点,另外一个客户端是无法创建同名的节点(达到互斥的效果)

(2)、我们注册该节点的listener时,如果节点删除(数据修改),会通知其他的客户端,这个时候其他的客户端可以重新去创建该节点(可以认为时拿到锁的客户端释放锁,其他的客户端可以抢锁)

(3)、创建的节点应该时临时节点,这样保证我们在已经拿到锁的客户端挂掉了会自动释放锁

单点锁实现方式

伪代码:

if(!have childnode){
	create node(Ephemeral(临时));
	
}else {
zkClient.subscribeChildChanges("pth",new zkListener)
}

多节点锁的实现方式(在锁释放时所有线程不会和单点一样去抢占,只要按顺序访问前一个节点就好)

  1. 某个客户端尝试加锁时,先在该业务节点下,创建一个顺序节点。
  2. 创建完成后,获取出该业务节点下的所有子节点,并按照按照节点序号排序
  3. 判断第一位的节点是否为自己的节点,是的话,代表获取锁,执行业务操作
  4. 不是的话,对排在自己前一位的节点进行监听,客户端挂起
    当客户端执行业务完毕后,删除自己的节点,并通知监听自己节点的客户端进行业务操作。
    多点锁实现方式

分布式锁代码实现(单节点锁和多节点锁)

抽象类:
import org.I0Itec.zkclient.ZkClient;

/**
* @ClassName AbstractLock
* @Description TODO
* @Author YGuang
* @Date 2019/7/29 17:10
* @Version 1.0
**/


public abstract class AbstractLock {
   public static final String ZK_ADDR = "localhost:2181,localhost:2182,localhost:2183";
   public static final int SESSION_TIMEOUT = 10000;
   //创建ZK
   protected ZkClient zkClient=new ZkClient(ZK_ADDR,SESSION_TIMEOUT);
   public void getlock(){
       String threadName=Thread.currentThread().getName();
       if(tryLock()){
           System.out.println("获取当前" + threadName+ "锁成功");
       }
       else {
           System.out.println("获取锁失败" +
                   threadName+"---");
           waitLock();
           //递归重新获取锁
           getlock();
       }
   }
   public abstract boolean  tryLock();
   public abstract void releaseLock();
   public abstract void waitLock();

}

抽象类实现:
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

import java.util.concurrent.CountDownLatch;

/**
* @ClassName SimpleZklock
* @Description TODO
* @Author YGuang
* @Date 2019/7/29 17:09
* @Version 1.0
**/


public class SimpleZklock extends AbstractLock {
   private static final String NODE_NAME = "/yg";
   //计数器 初始化数是线程个数
   private CountDownLatch countDownLatch;

   @Override
   //创建临时节点如果创建成功,获取锁,创建不成功处理异常
   public boolean tryLock() {
       if (zkClient == null) {
           return false;
       }
       try {
           zkClient.createEphemeral(NODE_NAME);
           return true;
       } catch (Exception e) {
           System.out.println(e + "cant get Lock" + "wait---------------");
           return false;
       }
   }

   @Override
   //释放锁
   public void releaseLock() {
       if (zkClient != null) {
           zkClient.delete(NODE_NAME);
           zkClient.close();
           System.out.println("releaseLock success----------");
       }

   }

   @Override
   //等待锁的过程
   public void waitLock() {
       //创建监听器
       IZkDataListener iZkDataListener = new IZkDataListener() {
           @Override
           public void handleDataChange(String datapath, Object data) throws Exception {
           }

           @Override
           //节点删除被回调
           public void handleDataDeleted(String datapath) throws Exception {
               if (countDownLatch != null) {
                   //给count数减1(默认一个线程已访问?)
                   countDownLatch.countDown();
               }
           }

       };
       zkClient.subscribeDataChanges(NODE_NAME, iZkDataListener);
       if (zkClient.exists(NODE_NAME)) {
           //如果存在阻塞 设置计时器为1
           countDownLatch = new CountDownLatch(1);
           try {
               //阻塞在当前线程直到所被释放 可能会出现死锁?
               countDownLatch.await();
               System.out.println("wait lock"+"-------------");
           } catch (InterruptedException e) {
               e.printStackTrace();

           }
       }
       zkClient.unsubscribeDataChanges(NODE_NAME,iZkDataListener);

   }
}
测试:
/**
* @ClassName LockTest
* @Description TODO
* @Author YGuang
* @Date 2019/7/30 10:08
* @Version 1.0
**/


public class LockTest {
   public static void main(String[] args) {
       for (int i=1;i<10;i++){
            new Thread(()->{
               AbstractLock zkLock=new SimpleZklock();
               zkLock.getlock();
               try {
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               zkLock.releaseLock();
           }).start();
       }

   }
}


多节点代码:
import org.I0Itec.zkclient.IZkDataListener;

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

/**
* @ClassName HighPerformanceLock
* @Description TODO
* @Author YGuang
* @Date 2019/7/30 11:17
* @Version 1.0
**/


public class HighPerformanceLock extends AbstractLock {
   public static final String PATH="/highyg";
   //当前节点路径
   private String currentPath;
   //前一个节点路径
   private String beforePath;
   private CountDownLatch countDownLatch;
   public HighPerformanceLock(){
       //如果不存在父节点,创建持久节点
       if(!zkClient.exists(PATH)){
           zkClient.createPersistent(PATH);
       }
   }
   @Override
   public boolean tryLock() {
       if (null == currentPath || "".equals(currentPath)) {
           //在path下创建临时顺序节点将他赋予curretpath 临时节点没必要使用名字 因为临时节点会在一个线程结束后被释放 多个线程访问时会生成多个对应临时节点。
           currentPath = zkClient.createEphemeralSequential(PATH + "/", "is a lock");
       }
           List <String> children = zkClient.getChildren(PATH);
           Collections.sort(children);
           if (currentPath.equals(PATH + "/" + children.get(0))) {
               //如果是第一个即可
               return true;
           } else {//不是第一个值则获取他前面的节点名称,赋值给beforepath
               int pathlength = PATH.length();
               String midd=currentPath.substring(pathlength+1);
               int index = Collections.binarySearch(children, currentPath.substring(pathlength + 1));
               beforePath = PATH + "/" + children.get(index - 1);
           }

       return false;
   }
   @Override
   public void releaseLock() {
       if (null!=zkClient){
           //释放节点 删除现在持有节点
           zkClient.delete(currentPath);
           System.out.println("release current Lock------------");
           zkClient.close();
       }
   }

   @Override
   public void waitLock() {
       IZkDataListener iZkDataListener=new IZkDataListener() {
           @Override
           public void handleDataChange(String s, Object o) throws Exception {

           }

           @Override
           public void handleDataDeleted(String datapath) throws Exception {
               if(null!=countDownLatch){
                   countDownLatch.countDown();
               }
           }
       };
       zkClient.subscribeDataChanges(beforePath,iZkDataListener);
       if (zkClient.exists(beforePath)){
           countDownLatch=new CountDownLatch(1);
           try {
               countDownLatch.await();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("wai lock ---------");
           zkClient.unsubscribeDataChanges(beforePath,iZkDataListener);
       }


   }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值