The ZooKeeper Data Model
ZooKeeper有一个层次的命名空间,很像一个分布式文件系统。惟一的区别是命名空间中的每个节点都可以有数据和与其关联的子节点。这就像一个文件系统允许一个文件同时也是一个目录。路径是绝对的不能是相对的。
路径规则(不可包含以下类型的unicode字符):
* The null character (u0000) cannot be part of a path name.
* The following characters can't be used because they don't display well, or render (呈现) in confusing ways ( 令人困惑的方式 ): u0001 - u001F and u007F
* u009F.
* The following characters are not allowed: ud800 - uF8FF, uFFF0 - uFFFF.
* The "." character can be used as part of another name, but "." and ".." cannot alone be used to indicate a node along a path ( 不能单独使用 ), because ZooKeeper doesn't use relative paths. The following would be invalid: "/a/b/./c" or "/a/b/../c".
* The token "zookeeper" is reserved ( 保留 ).
ZNodes
Zookeeper树上的每一个node都成为znode。Znode维护了一个stat结构,其中包括数据更改、acl更改的版本号。stat结构也有时间戳。版本号和时间戳两者结合起来允许ZooKeeper验证缓存并协调更新。每次znode的数据更改时,版本号都会增加。例如,每当客户机检索数据时,它也会接收数据的版本。当客户端执行更新或删除时,它必须提供正在更改的znode的数据版本。如果它提供的版本与数据的实际版本不匹配,则更新将失败。
node指的是一般的主机,一个服务提供方服务器,一个集群中的一员,一个客户端进程;
znodes指的是数据节点
Servers指的是提供服务的机器
quorum peers指的是集群中的机器
client指的是获得服务的任意主机和进程
Znodes特征
Watches
客户端可以在znodes上设置watches,对该znode的更改将触发该watch,然后清除该watch。当一个watch触发时,ZooKeeper会向客户端发送一个通知
Data Access
在名称空间中的每个znode中存储的数据的读写操作都是原子的。Reads get all the data bytes associated with ( 与…有关系 ) a znode and a write replaces all the data. Each node has an Access Control List (ACL) that restricts ( 约束 ) who can do what.
Ephemeral Nodes
只要创建znode的会话处于活动状态,这些临时znode就一直存在。当会话结束时,将删除这些临时znode。
Sequence Nodes -- Unique Naming
创建znode时,还可以请求ZooKeeper在路径末尾附加一个单调递增的计数器。此计数器对于父znode是唯一的。计数器的格式为%010d,即10位数字,即“0000000001”。
Time in ZooKeeper
Zookeeper通过多种方式记录时间:
zxid:对ZooKeeper状态的每次更改都会收到一个zxid (ZooKeeper事务Id)形式的戳记。这将向ZooKeeper公开所有更改的总顺序。每个更改都有一个惟一的zxid,如果zxid1小于zxid2,则zxid1发生在zxid2之前
Version numbers:对节点的每次更改都会导致该节点的版本号增加。The three version numbers are version (number of changes to the data of a znode), cversion (number of changes to the children of a znode), and aversion (number of changes to the ACL of a znode).
Ticks:当使用集群化ZooKeeper时,服务器使用ticks来定义事件的时间,如状态更新、会话超时、节点之间的连接超时等。tick时间仅通过最小会话超时(tick时间的2倍)间接暴露;如果客户端请求的会话超时小于最小会话超时,服务器将告诉客户机会话超时实际上是最小会话超时。
Real time:ZooKeeper除了在znode创建和修改时将时间戳放入stat结构外,根本不使用real time或clock time
ZooKeeper Stat Structure
ZooKeeper Sessions
ZooKeeper客户机通过使用语言绑定创建服务的句柄来与ZooKeeper服务建立会话。创建后,句柄将在连接状态下启动,客户端库将尝试连接到组成ZooKeeper服务的服务器之一,此时它将切换到连接状态。在正常操作期间,客户端句柄将处于这两种状态之一。如果发生不可恢复的错误,例如会话过期或身份验证失败,或者如果应用程序显式关闭句柄,则该句柄将移动到关闭状态。下图显示了ZooKeeper客户端的可能状态转换:
要创建客户端会话,应用程序代码必须提供一个连接字符串( "127.0.0.1:4545" or "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" ) ,ZooKeeper客户端库将选择任意服务器并尝试连接到该服务器。如果此连接失败,或者客户端由于任何原因与服务器断开连接,则客户端将自动尝试列表中的下一个服务器,直到(重新)建立连接。
连接字符串也支持相对路径,“127.0.0.1:4545/app/a”或“127.0.0.1:3000127.0.0.1:3002/app/a”
当客户端连接到Zookeeper的server时,server会创建一个sessionId和一个password并发送给客户端,当客户端尝试连接新的server,发送请求时会传这两个参数
创建ZooKeeper会话的ZooKeeper客户端库调用的参数之一是会话超时(以毫秒为单位)。客户端发送请求的超时,服务器用它可以给客户端的超时来响应。当前的实现要求超时至少是tickTime的2倍(如服务器配置中设置的),最大时间是tickTime的20倍。ZooKeeper客户端API允许访问调整超时设置。
当客户机(会话)从ZK服务集群断开连接时,它将开始搜索在会话创建期间指定的服务器列表。最终,当客户端和至少一台服务器之间的连接重新建立时,会话将再次转换到“已连接”状态(如果在会话超时值内重新连接),或者将转换到“过期”状态(如果在会话超时后重新连接)。不建议创建用于断开连接的新会话对象。ZK客户端库将为您处理重新连接。特别是我们在客户端库中内置了启发式方法来处理“羊群效应”等问题。只有在收到会话过期通知时才创建新会话(必需)。
会话过期由ZooKeeper群集本身管理,而不是由客户端管理。当ZK客户机与集群建立会话时,它会提供上面详细描述的“超时”值。群集使用此值来确定客户端会话何时过期。当群集在指定的会话超时时间(即没有心跳)内没有收到来自客户端的消息时,将发生过期。会话到期时,集群将删除该会话拥有的任何/所有临时节点,并立即将更改通知任何/所有连接的客户端(任何正在监视这些znode的用户)。此时,已过期会话的客户端仍与群集断开连接,除非能够重新建立到群集的连接,否则将不会收到会话过期的通知。客户端将保持断开连接状态,直到与群集重新建立TCP连接,此时过期会话的观察者将收到“会话过期”通知。
ZooKeeper会话建立调用的另一个参数是默认观察程序。当客户端中发生任何状态更改时,将通知观察者。例如,如果客户端与服务器失去连接,则会通知客户端,或者如果客户端的会话过期,等等。。。此观察程序应考虑断开连接的初始状态(即,在客户端库向观察程序发送任何状态更改事件之前)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。
会话通过客户端发送的请求保持活动状态。如果会话空闲一段时间会使会话超时,则客户端将发送PING请求以使会话保持活动状态。此PING请求不仅允许ZooKeeper服务器知道客户端仍然处于活动状态,而且还允许客户端验证其与ZooKeeper服务器的连接是否仍然处于活动状态。PING的时间非常保守,足以确保有合理的时间检测到死机连接并重新连接到新服务器。
有一个称为SessionMovedException的客户端通常看不到的内部异常。发生此异常是因为在连接上接收到已在其他服务器上重新建立的会话的请求。此错误的正常原因是客户端向服务器发送请求,但网络数据包被延迟,因此客户端超时并连接到新服务器。当延迟的数据包到达第一个服务器时,旧服务器检测到会话已移动,并关闭客户端连接。客户端通常不会看到此错误,因为它们不会从这些旧连接中读取数据。(旧连接通常是关闭的。)可以看到这种情况的一种情况是,两个客户端尝试使用保存的会话id和密码重新建立同一连接。其中一个客户端将重新建立连接,第二个客户端将断开连接(导致两个客户端尝试无限期地重新建立其连接/会话)。
Zookeeper允许更新连接字符串,如由三个连接增为五个,或者由五个降为三个,此时会有均衡负载算法让客户端连接重新连接到新的集群中
ZooKeeper Watches
All of the read operations in ZooKeeper - getData(), getChildren(), and exists() 等操作,额外的都会设置一个Watch
One-time trigger:One watch event will be sent to the client when the data has changed. 第一次getData('/znode', true),设置完watch后,'/znode' 数据发送变化时会给客户端发送一个watch event,之后数据再发生变化则不会再发送,除非又进行了一次另外一次读操作
Sent to the client:This implies that an event is on the way to the client,但是可能写操作已经完成但是事件还未到客户端,Watches are sent asynchronously to watchers. Zookeeper提供了一致性保证,所有客户端看到的所有东西都会有一个一致的顺序。无论网络是否延迟或其他因素
The data for which the watch was set:This refers to the different ways a node can change. 可以想象成ZooKeeper维护了两种watch,一个是data watch,一个是child watch,getData() and exists() set data watches. getChildren() sets child watches.A successful create() will trigger a data watch for the znode being created and a child watch for the parent znode. A successful delete() will trigger both a data watch and a child watch (since there can be no more children) for a znode being deleted as well as a child watch for the parent znode.
Watch维护在客户端所连接的服务器上,这使得维护、设置、调度方面轻量化。
ZooKeeper access control using ACLs
ZooKeeper使用acl来控制对其znode(ZooKeeper数据树的数据节点)的访问。ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止对节点和位应用的范围执行各种操作。与标准UNIX权限不同,ZooKeeper节点不受owner、group、others三个组权限的限制。
如果/app权限为172.16.16.1可读,/app/status权限为全部可读,则任何人对/app/status都可读,这个与UNIX不同
验证方式:IP方式,ip:172.16.16.1;digest方式,digest:bob:password
scheme:expression, perms,ip:19.22.0.0/16, READ,19.22 开头的IP地址均有读权限
ACL Permissions
使用zkCli时,ACL的格式由scheme:expression:perms三段组成
schema:可以取下列值:world, auth, digest, ip, x509
expression: 唯一标识,标识身份,值依赖于schema做解析
perms:就是访问控制列表:cdwra分别表示create, delete,write,read, admin
digest:使用用户名密码生成MD5散列哈希作为唯一标识,身份验证是通过以明文发送user:password来完成的,只有以zkCli方式授权时才需要密文
setAcl /test digest:root:qiTlqPLK7XM2ht3HMn02qRpkKIE=:cdrwa get /myznode // 提示认证未通过 addauth digest root:root get /myznode
命令行方式生成密文
echo -n root:root | openssl dgst -binary -sha1 | openssl base64 qiTlqPLK7XM2ht3HMn02qRpkKIE=
代码方式生成密文
public static void main(String[] args) { System.out.println(DigestAuthenticationProvider.main(new String[]{"root:root"})); } root:root->root:qiTlqPLK7XM2ht3HMn02qRpkKIE=
ip:使用客户端主机IP作为唯一标识,可以是IP地址,域名,IP地址段,zkCli方式 -server xxx代表创建session的IP
x509:
Pluggable ZooKeeper authentication
Zookeeper支持自定义的身份验证方案,身份验证插件必须实现的接口
public interface AuthenticationProvider { String getScheme(); KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]); boolean isValid(String id); boolean matches(String id, String aclExpr); boolean isAuthenticated(); }
有两个内置的身份验证插件:ip,digest
自定义插件可以使用系统属性添加,-Dzookeeeper.authProvider.X=com.f.MyAuth ( X一般为数字 )
自定义插件可以使用配置文件( zoo.cfg )添加
authProvider.1=com.f.MyAuth authProvider.2=com.f.MyAuth2
Consistency Guarantees
使用一致性保证,只需在ZooKeeper客户端(ZooKeeper不需要添加任何内容)就可以轻松构建更高级别的函数,如leader选举、屏障、队列和读/写可撤销锁