zoo.cfg配置文件分析

tickTime=2000  zookeeper中最小的时间单位长度 (ms)

initLimit=10  follower节点启动后与leader节点完成数据同步的时间

syncLimit=5 leader节点和follower节点进行心跳检测的最大延时时间

dataDir=/tmp/zookeeper  表示zookeeper服务器存储快照文件的目录

dataLogDir 表示配置 zookeeper事务日志的存储路径,默认指定在dataDir目录下

clientPort 表示客户端和服务端建立连接的端口号: 2181

zookeeper中的一些概念

数据模型

zookeeper的数据模型和文件系统类似,每一个节点称为:znode.  是zookeeper中的最小数据单元。每一个znode上都可以

保存数据和挂载子节点。 从而构成一个层次化的属性结构

节点特性

持久化节点  : 节点创建后会一直存在zookeeper服务器上,直到主动删除

持久化有序节点 :每个节点都会为它的一级子节点维护一个顺序

临时节点 : 临时节点的生命周期和客户端的会话保持一致。当客户端会话失效,该节点自动清理

临时有序节点 : 在临时节点上多勒一个顺序性特性

会话

客户端与服务器的一次连接

Watcher

zookeeper提供了分布式数据发布/订阅,zookeeper允许客户端向服务器注册一个watcher监听。当服务器端的节点触发指定事件的时候

会触发watcher。服务端会向客户端发送一个事件通知
watcher的通知是一次性,一旦触发一次通知后,该watcher就失效

ACL

zookeeper提供控制节点访问权限的功能,用于有效的保证zookeeper中数据的安全性。避免误操作而导致系统出现重大事故。

CREATE /READ/WRITE/DELETE/ADMIN

zookeeper的命令操作

1. create [-s] [-e] path data acl

-s 表示节点是否有序

-e 表示是否为临时节点

默认情况下,是持久化节点

2. get path [watch]

获得指定 path的信息

3.set path data [version]

修改节点 path对应的data

乐观锁的概念

数据库里面有一个 version 字段去控制数据行的版本号

4.delete path [version]

删除节点

stat信息

cversion = 0       子节点的版本号

aclVersion = 0     表示acl的版本号,修改节点权限

dataVersion = 1    表示的是当前节点数据的版本号

czxid    节点被创建时的事务ID

mzxid   节点最后一次被更新的事务ID

pzxid    当前节点下的子节点最后一次被修改时的事务ID

ctime = Sat Aug 05 20:48:26 CST 2017

mtime = Sat Aug 05 20:48:50 CST 2017

cZxid = 0x500000015

ctime = Sat Aug 05 20:48:26 CST 2017

mZxid = 0x500000016

mtime = Sat Aug 05 20:48:50 CST 2017

pZxid = 0x500000015

cversion = 0

dataVersion = 1

aclVersion = 0

ephemeralOwner = 0x0   创建临时节点的时候,会有一个sessionId 。 该值存储的就是这个sessionid

dataLength = 3    数据值长度

numChildren = 0  子节点数

java API的使用

1.导入java包

<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>

权限控制模式

schema:授权对象

ip     : 192.168.1.1

Digest  : username:password

world  : 开放式的权限控制模式,数据节点的访问权限对所有用户开放。 world:anyone

super  :超级用户,可以对zookeeper上的数据节点进行操作

连接状态

KeeperStat.Expired  在一定时间内客户端没有收到服务器的通知, 则认为当前的会话已经过期了。

KeeperStat.Disconnected  断开连接的状态

KeeperStat.SyncConnected  客户端和服务器端在某一个节点上建立连接,并且完成一次version、zxid同步

KeeperStat.authFailed  授权失败

事件类型

NodeCreated  当节点被创建的时候,触发

NodeChildrenChanged  表示子节点被创建、被删除、子节点数据发生变化

NodeDataChanged    节点数据发生变化

NodeDeleted        节点被删除

None   客户端和服务器端连接状态发生变化的时候,事件类型就是None

zookeeper的实际应用场景

zookeeper能够实现哪些场景

订阅发布

watcher机制

统一配置管理(disconf)

分布式锁

1.应用:redis ,zookeeper数据库

2.zookeeper分布式锁实现原理

  • 客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推;

  • 客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;

  • 执行业务代码;

  • 完成业务流程后,删除对应的子节点释放锁。

负载均衡

ID生成器

分布式队列

统一命名服务

master选举

封装的zookeeper——curator

curator

Curator本身是Netflix公司开源的zookeeper客户端;

curator提供了各种应用场景的实现封装

curator-framework  提供了fluent风格api

curator-replice     提供了实现封装

curator连接的重试策略

ExponentialBackoffRetry()  衰减重试

RetryNTimes 指定最大重试次数

RetryOneTime 仅重试一次

RetryUnitilElapsed 一直重试知道规定的时间

curator使用分布式锁

虽然zookeeper原生客户端暴露的API已经非常简洁了,但是实现一个分布式锁还是比较麻烦的。

1.引入包:

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>

2.使用分布式锁代码

public static void main(String[] args) throws Exception {
//创建zookeeper的客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181", retryPolicy);
client.start();
//创建分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client, "/curator/lock");
//获取锁
mutex.acquire();
//进行业务流程
System.out.println("Enter mutex");
//完成业务流程, 释放锁
mutex.release();
//关闭客户端
client.close(); }

锁的获取和释放使用 acquire()和release()方法即可

下面来分析下获取锁的源码实现。

acquire的方法如下:

/*
* 获取锁,当锁被占用时会阻塞等待,这个操作支持同线程的可重入(也就是重复获取锁),acquire的次数需要与release的次数相同。
* @throws Exception ZK errors, connection interruptions
*/ @Override
public void acquire() throws Exception
{
  if ( !internalLock(-1, null) )
  {
    throw new IOException("Lost connection while trying to acquire lock: " + basePath);
  }
}

这里有个地方需要注意,当与zookeeper通信存在异常时,acquire会直接抛出异常,需要使用者自身做重试策略。代码中调用了internalLock(-1, null),参数表明在锁被占用时永久阻塞等待。internalLock的代码如下:

private boolean internalLock(long time, TimeUnit unit) throws Exception
{
  //这里处理同线程的可重入性,如果已经获得锁,那么只是在对应的数据结构中增加acquire的次数统计,直接返回成功
  Thread currentThread = Thread.currentThread();
  LockData lockData = threadData.get(currentThread);
  if ( lockData != null )
  {
    // re-entering
    lockData.lockCount.incrementAndGet();
    return true;
  }
  //这里才真正去zookeeper中获取锁
  String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
  if ( lockPath != null )
  {
    //获得锁之后,记录当前的线程获得锁的信息,在重入时只需在LockData中增加次数统计即可
    LockData newLockData = new LockData(currentThread, lockPath);
    threadData.put(currentThread, newLockData);
    return true;
  }
//在阻塞返回时仍然获取不到锁,这里上下文的处理隐含的意思为zookeeper通信异常
  return false;
}

zookeeper获取锁的具体实现:

String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
{
  //参数初始化,此处省略
  //...
  //自旋获取锁
  while ( !isDone )
  {
    isDone = true;
    try
    {
      //在锁空间下创建临时且有序的子节点
      ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
      //判断是否获得锁(子节点序号最小),获得锁则直接返回,否则阻塞等待前一个子节点删除通知
      hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
    }catch ( KeeperException.NoNodeException e ){
      //对于NoNodeException,代码中确保了只有发生session过期才会在这里抛出NoNodeException,因此这里根据重试策略进行重试
      if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
      {
        isDone = false;
      }else{
        throw e;
      }
    }
}
//如果获得锁则返回该子节点的路径
if ( hasTheLock )
{
return ourPath;
}
return null;
}

上面代码中主要有两步操作:

  • driver.createsTheLock:创建临时且有序的子节点,里面实现比较简单不做展开,主要关注几种节点的模式:

  • 1)PERSISTENT(永久);

  • 2)PERSISTENT_SEQUENTIAL(永久且有序);

  • 3)EPHEMERAL(临时);

  • 4)EPHEMERAL_SEQUENTIAL(临时且有序)。

  • internalLockLoop:阻塞等待直到获得锁。

internalLockLoop是怎么判断锁以及阻塞等待的,这里删除了一些无关代码,只保留主流程:

//自旋直至获得锁
while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )
{
  //获取所有的子节点列表,并且按序号从小到大排序
  List<String> children = getSortedChildren();
  //根据序号判断当前子节点是否为最小子节点
  String sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
  PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
  if ( predicateResults.getsTheLock() )
  {
    //如果为最小子节点则认为获得锁
    haveTheLock = true;
  }else{
    //否则获取前一个子节点
    String previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
    //这里使用对象监视器做线程同步,当获取不到锁时监听前一个子节点删除消息并且进行wait(),当前一个子节点删除(也就是锁释放)时,回调会通过notifyAll唤醒此线程,此线程继续自旋判断是否获得锁
    synchronized(this){
      try{
        //这里使用getData()接口而不是checkExists()是因为,如果前一个子节点已经被删除了那么会抛出异常而且不会设置事件监听器,而checkExists虽然也可以获取到节点是否存在的信息但是同时设置了监听器,这个监听器其实永远不会触发,对于zookeeper来说属于资源泄露
        client.getData().usingWatcher(watcher).forPath(previousSequencePath);
        //如果设置了阻塞等待的时间
        if ( millisToWait != null )
        {
          millisToWait -= (System.currentTimeMillis() - startMillis);
          startMillis = System.currentTimeMillis();
          if ( millisToWait <= 0 )
          {
            doDelete = true; // 等待时间到达,删除对应的子节点
            break;
          }
          //等待相应的时间
          wait(millisToWait);
        }else{
          //永远等待
          wait();
        }
       }catch ( KeeperException.NoNodeException e ){
        //上面使用getData来设置监听器时,如果前一个子节点已经被删除那么会抛出NoNodeException,只需要自旋一次即可,无需额外处理
       }
      }
    }
  }

1.9 分布式协调服务-Zookeeper(二)的更多相关文章

  1. 分布式协调服务Zookeeper集群之ACL篇

    分布式协调服务Zookeeper集群之ACL篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.zookeeper ACL相关知识概览 1>.zookeeper官方文档(h ...

  2. 分布式协调服务Zookeeper集群监控JMX和ZkWeb应用对比

    分布式协调服务Zookeeper集群监控JMX和ZkWeb应用对比 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. JMX是用来远程监控Java应用的框架,这个也可以用来监控其他的J ...

  3. 分布式协调服务Zookeeper集群搭建

    分布式协调服务Zookeeper集群搭建 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.安装jdk环境 1>.操作环境 [root@node101.yinzhengjie ...

  4. 分布式协调服务Zookeeper扫盲篇

    分布式协调服务Zookeeper扫盲篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 身为运维工程师对kubernetes(k8s)可能比较熟,那么etcd(go语言实现)分布式协 ...

  5. 搞懂分布式技术3:初探分布式协调服务zookeeper

    搞懂分布式技术3:初探分布式协调服务zookeeper 1.Zookeepr是什么 Zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅,负载均衡, ...

  6. 分布式协调服务ZooKeeper工作原理

    分布式协调服务ZooKeeper工作原理 原创 2016-02-19 杜亦舒 性能与架构 性能与架构 性能与架构 微信号 yogoup 功能介绍 网站性能提升与架构设计 大数据处理框架Hadoop.R ...

  7. 中小型研发团队架构实践八:分布式协调服务ZooKeeper

    一.ZooKeeper 是什么 Apache ZooKeeper 由 Apache Hadoop 的子项目发展而来,于 2010 年 11 月正式成为了 Apache 的顶级项目. ZooKeeper ...

  8. 中小型研发团队架构实践:分布式协调服务ZooKeeper

    一.ZooKeeper 是什么 Apache ZooKeeper 由 Apache Hadoop 的子项目发展而来,于 2010 年 11 月正式成为了 Apache 的顶级项目. 相关厂商内容 优秀 ...

  9. 分布式协调服务-Zookeeper

    什么是 zookeeper? Zookeeper 是google的chubby一个开源实现,是hadoop的分布式协调服务 它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名 ...

随机推荐

  1. Java Web相关问题

     关于这两天主要问题的解答: (1) 驱动程序无法使用安全套接字层(SSL)加密与 SQL Server 建立安全连接.错误: java.lang.RuntimeException: Could no ...

  2. 如何使用java validation api进行参数校验----Hibernate-Validation

    在日常开发中,Hibernate Validator经常用来验证bean的字段,基于注解,方便快捷高效. 1. Bean Validation 中内置的 constraint 注解           ...

  3. June. 25th 2018, Week 26th. Monday

    Change in all things is sweet. 有改变就会有美好. From Aristole. Change is always good, but embracing change ...

  4. 从输出日志中提取接口的入参和返回做为用例导入到excel中

    1  背景 接口用例已经在项目中的yml文件中编写,但是yml文件不能做为交付文档用,本文对工作中从接口输出日志中提取用例信息,并导入到excel文件中做了总些 2  工具 idea,notepad+ ...

  5. mongoTemplate查询大数据过慢

    先上两段代码 代码一 Query query = new Query();queryAfter.addCriteria(Criteria.where("id").in(idList ...

  6. QTableWidgetItem class

    Help on class QTableWidgetItem in module PyQt5.QtWidgets: class QTableWidgetItem(sip.wrapper)  |  QT ...

  7. soamanager发布的Webservice服务,调用时出现http500报错

    最近再给薪酬那边发布ws服务时出现了报错,调用方反馈了errorCode:BEA-380002.在使用XMLspy工具去调用这个WSDL时候,则反馈http500的错误消息.如下图: 遇到这种问题我通 ...

  8. ccpc杭州站 赛后总结

    Ccpc杭州站赛后总结 2017年11月4号五号,我参加了ccpc杭州站的比赛,我的队友是聂少飞和王艳,在4号一点半,举行了比赛开幕式,听着教练代表的发言,听着参赛选手代表的发言,听着志愿者的发言,都 ...

  9. FreeMarker 入门

    目录 FreeMarker是什么 为什么要学习FreeMarker FreeMarker相关站点

  10. Ubuntu安装Navicat 12 for MySQL

    环境准备 要想运行Navicat,必须先安装Wine,这个可以使用下面的命令来安装Wine: ubuntu@ubuntu ~ $ sudo apt-get install wine-stable 安装 ...