Apache ZooKeeper学习之ZooKeeper编程指南

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

ZooKeeper 数据模型

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

  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节点数据发生变化的时候,就会通过这些监控器触发向客户端发送通知的任务。

数据访问

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

临时节点

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进行同步读(可能没有实际意义,但也不违法,它仅是一个简单的例子)。

转载于:https://my.oschina.net/MyHeaven1987/blog/735846

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值