Apache ZooKeeper学习之ZooKeeper编程指南

本篇文章帮助开发者了解如何利用zookeeper的协调服务开发分布式应用。文章讲解了开发中涉及到的一些概念和实践的内容。文章的前四部分讨论了zookeeper的概念,了解这些概念使用者理解zookeeper如何工做以及如何利用它,后面四个部分讲解的是编程实践的内容。java

ZooKeeper 数据模型

ZooKeeper有一个相似于分布式文件系统的分层命名空间。空间中的节点既能够包含数据也能够关联子节点,这个节点既像文件又像文件目录。命名空间中的节点路径不是相对路径,是一个标准化的,绝对的,使用反斜扛分隔的路径。路径中能够包含任何的unicode字符除了如下的限制:node

  1. 控制符\u0000不能做为路径的一部分,使用它会致使C语言绑定的时候出现错误。
  2. \u0001-\u001F 和 \u007F - \u009F两个范围的Unicode字符不容许出如今路径中,使用它们会致使显示异常,或者显示称为让人疑惑的形式。
  3. \ud800 - uF8FF, \uFFF0 - uFFFF两个范围的字符不容许出现
  4. 因为路径不支持相对路径,所以字符"."容许出如今路径中,可是不容许”.",".."独立成为路径的一部分。例如:"/a/b/./c" or "/a/b/../c"都是无效路径。
  5. zookeeper做为保留字

ZNodes

ZooKeeper命名空间中的节点被称为ZNode,Znode节点维持了一个包含了数据变动版本号和acl(访问控制列表)变动版本号的状态结构,状态结构中还有一个时间戳字段。版本号和时间戳被用来进行缓存验证和协调更新。ZNode节点数据发生变动的时候,版本号就会增长。数据库

监控器

zookeeper客户端能够在ZNode上设置监控器,ZNode节点数据发生变化的时候,就会经过这些监控器触发向客户端发送通知的任务。express

数据访问

每个ZNode的数据都是原子读写,也就是说读和写操做都是对整个ZNode节点的操做。为了控制对ZNode的操做,ZNode维持了一个ACL(访问控制列表)来控制什么人具备操做权限。 zookeeper并无设计为普通的数据库存储或者大对象存储,相反他用来保存一些协调性数据例如配置信息,状态信息或者位置信息等。这些协调性的数据通常都比较小,在1千字节之内。zookeeper客户端和服务器的实现有一个健康检查来检查数据大小小于1M,可是实际的平均水平要远小于这个值。节点数据较大会致使某些节点操做耗时较多致使处理延迟变大,若是必须存储较大数据,考虑保存数据保存位置。apache

临时节点

zookeeper有一个临时节点的概念,临时节点的生命周期与会话一致,随会话建立而建立,随会话关闭而被删除。临时节点不容许包含子节点。编程

序列化节点 -- 惟一命名

ZNode节点建立的时候能够在后面加一个路径后面加一个计数器。对于父节点计数是惟一的。缓存

容器节点(3.6.0版本添加)

zookeeper在3.6版本后添加了一个容器节点的概念,容器节点被设计用于特殊的用途例如领袖选举,锁处理等。当容器节点中的最后一个节点被删除后,容器节点将会被标志位待删除节点,在将来的某一个时间点会被服务器删除。因为上面提到的内容,在容器节点添加子节点的时候要检查容器节点是否有子节点,若是没有子节点须要重建容器节点再添加子节点。安全

ZooKeeper中的时间

zookeeper经过下面四种方式来追踪时间:服务器

  1. Zxid(zookeeper事务id)

zookeeper每个状态的改变都会受收一个称为Zxid的标志,Zxid是一个全局性的属性值,若是受到两个请求,Zxid较小的请求要先于Zxid较大的请求。网络

  1. 版本号

zookeeper的每次修改都会致使版本号的增长,znode有三种版本号:version (znode数据发生变动的版本号),cversion (znode子节点的变动版本号),以及aversion(ACL的变动版本号)

  1. Ticks

Ticks属性被用来做为时间基本单位,时间单位是毫秒。在多zookeeper服务器的时候,它被用来为状态上传,会话超时,链接超时等时间的基本单位。

  1. 真实时间

zookeeper除了时间戳,其余时间都不是使用真实时间或者时钟时间。

ZooKeeper状态结构

  1. czxid:znode建立时被修改
  2. mzxid:最后一次修改的事务号
  3. pzxid:zookeeper最后一次子节点修改的事务号
  4. ctime:节点建立的毫秒数
  5. mtime:节点修改的毫秒数
  6. version:数据修改的版本号
  7. cversion:子节点变动的版本号
  8. aversion:ACL变动版本号
  9. ephemeralOwner:临时节点拥有者
  10. dataLength:znode的数据字段长度
  11. numChildren:znode子节点数

ZooKeeper会话

zookeeper客户端经过java或者C语言绑定的方式创建一个句柄来链接zookeeper服务器。句柄的开始状态是链接中状态,若是和提供zookeeper服务的某一个服务器成功链接,句柄的状态会变为已链接。若是客户端和服务器处于链接状态的时候,客户端主动关闭会话或者出现不可恢复异常的时候,句柄的状态会变为关闭状态。

为了建立客户端和服务器会话,应用须要传递一个逗号分隔的主机列表,开始建立链接的时候,客户端会从主机列表中随机选择一台进行链接,若是链接失败将会自动选择下一台进行链接,直到链接成功。

3.2.0版本新增特性:

支持在链接串后面添加一个根路径后缀,添加后缀后执行的命令都是相对于此根路径的操做。例如使用以下的链接串"127.0.0.1:4545/app/a" 或者 "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" ,运行命令对/foo/bar进行设置,修改等处理的时候,实际处理的是/app/a/foo/bar路径。在多终端环境,每一个用户都有本身的一个单独的根路径的时候,这个特性很是有用。

当客户端和服务器成功建立链接的时候,服务器会给客户端指定一个64位的字符串来标志这个会话。当客户端链接其余的服务器的时候,他会在建立链接的时候将这个64位的会话id传递给服务器。基于安全考虑,防止有人恶意仿冒已创建会话的客户端,建立链接的时候伴随会话id下发的还有一个秘钥,这个秘钥会被用于检查重连会话是不是正确的。当客户端重连服务器的时候,客户端会携带会话id和秘钥进行会话重连。

客户端建立会话有一个超时机制控制,超时时间要大于两倍的tickTime并小于二十倍的tickTime。而且支持经过协商设置这个超时时间。

客户端从zookeeper集群中断开的时候,它将会检索主机列表尝试进行重连。若是重连成功句柄的状态会从新变回已链接状态,若是重连超时,那么句柄会变成超时状态。zookeeper已经实现自动重连机制因此不推荐为重连定义单独的对象,若是接收到超时的提示的时候,直接重连会话就能够了。

会话超时是由服务器集群控制的,若是集群在超时时间内没有接收到客户端的心跳就会认为会话超时,就会将当前会话的全部临时节点删除,并通知设置了监听器的客户端这个变化。客户端会一直保持断开状态直到客户端从新链接服务器,监控器会通知客户端会话超时。

ZooKeeper监视器

ZooKeeper的getData(), getChildren()和exists() 读操做能够设置监控器来监控数据的变化。ZooKeeper对监控器的定义是监控的数据发生变化的一次性的发送给客户端的事件。对于zookeeper中的监控器须要注意以下三点:

  1. 一次触发

数据发生变化的时候,监控事件将会触发,可是若是已经触发过而且没有再次设置监控器,数据后续发生变化是不会触发监控事件的。

  1. 发送给客户端

监控事件是异步的,数据发送变化的时候向客户端发送通知的事件。因为可能存在网络延迟或者其余不可测的缘由致使不一样客户端在不一样的时间接收到监控事件并响应事件。为了保证数据的一致性,客户端只会对第一次接收到的监听事件作处理,后续若是是同一个事件的话会被忽略。

  1. 设置监控的数据

监控事件主要分为两类,一种是监控数据变化的称为数据监控器,一种是监控子节点变化的称为子节点监控器。getData()和exists()方法会设置数据监控器,getChildren()会设置子节点监控器。

监控事件被保存在和客户端链接的zookeeper服务器中,这能够保证事件是轻量级的,方便维护和分发。若是客户端从新链接一个新的zookeeper服务器,若是须要的话这个监控事件将会经过任何一个会话事件触发注册。可是须要注意的是若是客户端断连的时候,数据发生变化,这个监控事件将会遗失。

监控器的保障

  1. 监控器的顺序是和其余事件一块儿排序的。zookeeper客户端保证全部的分发都是按顺序的。
  2. zookeeper客户端会先看到监控事件,而后才是znode的新数据。(如何实现的?
  3. 来自于zookeeper服务器的监控事件的顺序和数据更新的顺序保持一致。

监控器注意点

  1. 监控器是一次性的,若是须要继续监控后续的变化,须要设置其余的监控。
  2. 因为监控器是一次性触发的,获取事件和发送新的请求之间有一个延迟,因此并不能保证每个变化你都能监控到,你须要考虑到zookeeper的屡次变化你只能监控到其中的某几回。
  3. 一个监控对象只会触发一次向客户端发送通知,例如一个相同的监控对象被exists和getData两个方法设置,若是数据被删除了,监控对象只会被调用一次通知删除信息。
  4. 若是客户端和服务器断开链接,在从新得到链接以前,客户端将没法获取任何监视器。因为这个缘由,会话事件会发送给全部的未完成的监控器。

使用ACL对zookeeper进行访问控制

zookeeper使用ACL进行访问控制,ACL的实现方式相似于Unix系统的权限位来控制对znode的各类操做以及范围。ACL中保存的是id集合以及对应的权限。ACL的控制是针对特定节点的,不可以递归赋予。

zookeeper支持可插入的认证方案。id列表经过scheme:id的形式指定,scheme表明的是一种认证方案,id表示的容许访问的id。例如ip:172.16.16.1指的就是支持ip地址为172.16.16.1的主机访问znode。

客户端链接上zookeeper服务器的时候,zookeeper服务器会将客户端与其具备的id关联。当客户端访问znode的时候,znode会经过ACL来校验客户端是否能够访问以及访问权限。ACL列表是由(scheme:expression, perms)形式的结构组成,经过scheme指定具体的认证方案。例如(ip:19.22.0.0/16, READ)容许19.22.0.0/16网段的主机对znode有读权限。

ACL权限

zookeeper支持的ACL权限以下:

  1. CREATE:建立子节点权限
  2. READ:当前节点以及其子节点读取权限
  3. WRITE:当前节点写权限
  4. DELETE:子节点删除权限
  5. ADMIN:当前节点设置权限的权限

内置ACL方案

zookeeper支持的内存ACL方案以下:

world:有一个惟一的id,anyone,表示任何人均可以访问。

auth:不须要id,表示说有认证过的人均可以访问。

digest:使用username:password的字符串进行md5的哈希来做为ACL的id。认证是经过发送username:password明文来进行的。若是在ACL表达式中使用须要使用username:base64的格式(base64是密码经过SHA1加密)

ip:使用客户端的ip来进行认证,格式为addr/bits。

可插拔的认证方式

zookeeper支持在不一样的环境中使用不一样的认证方案,所以它采用的是一种可插拔的认证框架。内置的认证方案也是使用这种可插拔的框架。

为了理解认证框架是如何工做的,你须要了解两种主要的认证操做。框架第一步须要验证客户端,这个是在客户端链接服务器的时候验证的。第二个是框架经过查找ACL响应客户端。ACL记录为<idspec, permissions> 的结构对。idspec中的信息会和会话中的信息比对来校验用户。这种比对依赖于具体的认证明现。下面是认证插件必须实现的接口:

public interface AuthenticationProvider {
String getScheme();
KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);
boolean isValid(String id);
boolean matches(String id, String aclExpr);
boolean isAuthenticated();
}

getScheme方法返回认证插件标识,因为咱们支持多种认证方式,服务器经过不一样的scheme获取到对应的认证方法。

handleAuthentication方法在客户端向服务器发起认证的时候触发,经过getScheme获取对应的认证方法,将传递过来的认证信息进行认证,若是认证失败会向客户端返回错误信息。

认证插件会在设置和使用ACL的时候被调用,当给znode设置ACL的时候zookeeper服务器将会将ACL的id值传递给isValid(String id)方法。经过插件来校验id是不是一个正确的形式。

zookeeper在校验ACL的时候会调用matches(String id, String aclExpr)方法,这个方法须要匹配客户端的认证信息和ACL中的记录。zookeeper会检索全部的ACL记录的scheme,若是有和客户端携带的scheme一致的记录就会调用matches(String id, String aclExpr)方法,使用插件本身的实现对认证信息进行校验。

zookeeper提供了两种内存的认证插件:ip和digest。额外的插件能够经过系统属性添加,zookeeper启动的时候会查询zookeeper.authProvider属性来获取认证插件,并将插件名解析为对应的插件实现类名。这些属性能够经过-Dzookeeeper.authProvider.X=com.f.MyAuth或者服务器配置文件设置

authProvider.1=com.f.MyAuth
authProvider.2=com.f.MyAuth2

须要注意的是后缀必须是惟一的,若是属性名有重复只有一个是有效的。

一致性保障

zookeeper是一个高性能,高拓展性的服务。它的读写操做都被设计的很是快而且读操做更快。读操做能够如此快是由于zookeeper提供旧数据服务并提供一致性保障:

  1. 顺序一致性:来自客户端的更新顺序和客户端发送顺序保持一致
  2. 原子性:数据修改只有成功或者失败
  3. 单系统镜像:不管链接那天服务器,看到的都是同样的。
  4. 可靠性:一旦数据被变动,直到下一次变动,数据会一致保持当前的状态。

时效性

客户端对系统的视图被保证在某个时间段内(数十秒)是最新的,每个系统的改变都在这个事件段内更新到客户端。

java绑定

zookeeper的java绑定有两个包构成org.apache.zookeeper和org.apache.zookeeper.data。其余的包为内部使用或者服务器实现使用。org.apache.zookeeper.data包由生成的类组成仅做为容器。

ZooKeeper的java客户端主要使用的类是ZooKeeper类,它的两个构造方法区别在于可选的会话id和密码。zookeeper支持进程内的会话恢复,java程序能够保存会话id和密码到一个稳态存储,在服务重启的时候能够恢复会话。

当zookeeper对象建立后,有两个线程会同时建立(一个IO线程,一个事件线程)。全部的IO都发生在IO线程(使用java NIO),全部的事件毁掉都发生在事件线程。会话维护例如重连,心跳在IO线程中已经处理。同步方法的响应也在IO线程处理。全部的异步事件处理,监控事件都在线程线程中处理。因为这种设计咱们须要注意以下内容:

  1. 这里是列表文本全部异步方法的调用和监视器的回调都是顺序的,一次一个。调用者能够作任何它想的事情,但这期间,不会处理其余回调方法。
  2. 这里是列表文本回调函数不会阻塞IO线程的处理和同步调用。
  3. 这里是列表文本同步调用可能不会按顺序返回。例如,一个客户端要作如下事情:对节点/a发出一个异步读,同时设置监视器,而后在读回调完成时,它对节点/a进行同步读(可能没有实际意义,但也不违法,它仅是一个简单的例子)。
相关文章
相关标签/搜索