一、使用 ACL

每次创建znode节点时,必须设置访问权限,而且子节点并不会继承父节点的访问权限。访问权限的检查也是基于每一个 znode 节点的,如果一个客户端可以访问一个 znode 节点,即使这个客户端无权访问该节点的父节点,仍然可以访问这个 znode 节点。

ZooKeeper 通过访问控制表(ACL)来控制访问权限。一个ACL包括以下形式的记录:scheme:auth-info

  • scheme:对应了一组内置的鉴权模式
  • auth-info:为对于特定模式所对应的方式进行编码的鉴权信息
ZooKeeper通过检查客户端进程访问每个节点时提交上来的授权信息来保证安全性。如果一个进程没有提供鉴权信息,或者鉴权信息与要请求的znode节点的信息不匹配,进程就会收到一个权限错误。

为了给一个ZooKeeper增加鉴权信息,需要调用 addAuthInfo 方法,形式如下:

void addAuthInfo(
    String scheme,
    byte auth[]
)
  • scheme:表示所采用的鉴权模式。
  • auth:表示发送给服务器的鉴权信息。该参数的类型为byte[]类型,不过大部分的鉴权模式需要一个String类型的信息,所以你可以通过String.getBytes()来将String转换为byte[]。
一个进程可以在任何时候调用addAuthInfo来添加鉴权信息。一般情况下,在ZooKeeper句柄创建后就会调用该方法来添加鉴权信息。进程中可以多次调用该方法,为一个ZooKeeper句柄添加多个权限的身份。

1、内置的鉴权模式

ZooKeeper提供了4种内置模式进行ACL的处理:

  • OPEN_ACL_UNSAFE常量

    • 使用world作为鉴权模式
    • 使用anyone作为auth-info
  • 管理员所使用的super模式

    • 该模式不会列入到 ACL 中
    • 但可以用于ZooKeeper的鉴权
    • 一个客户端通过super鉴权模式连接到ZooKeeper后,不会被任何节点的ACL所限制
  • digest为内置鉴权模式

    • auth-info格式为userid:passwd_digest(当调用addAuthInfo时需要设置ACL和userid:password信息。)
当ZooKeeper以一个空树开始,只有一个znode节点:/,这个节点对所有人开放,我们假设管理员Amy负责配置ZooKeeper服务,Amy创建/apps节点,用于所有使用服务的应用需要创建节点的父节点,她现在需要锁定服务,所以她设置为/和/apps节点设置的ACL为:digest:amy:Iq0onHjzb4KyxPAp8YWOIC8zzwY=, READ | WRITE | CREATE | DELETE | ADMIN
其中passwd_digest为用户密码的加密摘要。在这个ACL例子中,Iq0onHjzb4KyxPAp8YWOIC8zzwY=为passwd_digest,因此当Amy调用addAuthInfo方法,auth参数传入的为amy:secret字符串的字节数组,Amy使用下面的DigestAuthenticationProvider来为她的账户amy生成摘要信息。
java -cp $ZK_CLASSPATH \org.apache.zookeeper.server.auth.DigestAuthenticationProvider amy:secret.... amy:secret->amy:Iq0onHjzb4KyxPAp8YWOIC8zzwY= 
amy:后面生成的字符串为密码摘要信息,也就是我们在ACL记录总使用的信息。当Amy需要向ZooKeeper提供鉴权信息时,她就要使用digest amy:secret。例如,当Amy使用zkCli.sh连接到ZooKeeper,她可以通过以下方式提供鉴权信息:
[zk: localhost:2181(CONNECTED) 1] addauth digest amy:secret
为了避免在后面的例子中写出所有的摘要信息,将使用XXXXX作为占位符来简单的表示摘要信息。Amy想要设置一个子树,用于一个名为SuperApp的应用,该应用由开发人员Dom所开发,因此她创建了/apps/SuperApp节点,设置ACL如下:
digest:dom:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
digest:amy:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
该ACL由两条记录组成,一个由Dom使用,一个由Amy使用。这些记录对所有以dom或amy密码信息认证的客户端提供了全部权限。注意,根据ACL中的Dom的记录,他对/apps/SuperApp节点具有ADMIN权限,他有权限修改ACL,这就意味着Dom可以删除Amy访问/apps/SuperApp节点的权限。当然Amy具有super的访问权限,所以她可以随时访问任何znode节点,即使Dom删除了她的访问权限。

Dom使用ZooKeeper来保存其应用的配置信息,因此他创建了/apps/SuperApp/config节点来保存配置信息。之后他使用我们在之前例子中介绍的模式OPEN_ACL_UNSAFE来创建znode节点,因为Dom认为/apps和/apps/SuperApp的访问是受限制的,所以也能保护/apps/SuperApp/config节点的访问。我们后面就会看到,这样做称为UNSAFE。

我们假设一个名为Gabe的人具有ZooKeeper服务的网络访问权限。因为ACL的策略设置,Gabe无法访问/app或/apps/SuperApp节点,Gabe也无法获取/apps/SuperApp节点的子节点列表。但是,也许Gabe猜测Dom使用ZooKeeper保存配置信息,config这个名字对于配置文件信息也非常显而易见,因此他连接到ZooKeeper服务,调用getData方法获取/apps/SuperApp/config节点的信息。因为该znode节点采用了开放的ACL策略,Gabe可以获取该节点信息。还不止这些,Gabe可以修改、删除该节点,甚至限制/apps/SuperApp/config节点的访问权限。

假设Dom意识到这个问题,修改/apps/SuperApp/config节点的ACL策略为:

digest:dom:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
随着事情的发展,Dom得到一个新的开发人员Nico的帮助,来一同完善SuperApp。Nico需要访问SuperApp的子树,因此Dom修改了子树的ACL策略,将Nico添加进来。新的ACL策略为:
digest:dom:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
digest:nico:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN

注意:用户名和密码的摘要信息从何而来?

你也许注意到我们用于摘要的用户名和密码似乎凭空而来。实际上确实如此。这些用户名或密码不用对应任何真实系统的标识,甚至用户名也可以重复。也许有另一个开发人员叫Amy,并且开始和Dom和Nico一同工作,Dom可以使用amy:XXXXX来添加她的ACL策略,只是在这两个Amy的密码一样时会发生冲突,因为这样就导致她们俩可以互相访问对方的信息。

现在Dom和Nico具有了他们需要完成的SuperApp的所需的访问权限。应用部署到生产环境,然而Dom和Nico并不想提供进程访问ZooKeeper数据时所使用的密码信息,因此他们决定通过SuperApp所运行的服务器的网络地址来限制数据的访问权限。例如所有10.11.12.0/24网络中服务器,因此他们修改了SuperApp子树的znode节点的ACL为:

digest:dom:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
digest:nico:XXXXX, READ | WRITE | CREATE | DELETE | ADMIN
ip:10.11.12.0/24, READ

  • ip鉴权模式
ip鉴权模式需要提供网络的地址和掩码,因为需要通过客户端的地址来进行ACL策略的检查,客户端在使用ip模式的ACL策略访问znode节
点时,不需要调用addAuthInfo方法。
现在,任何在10.11.12.0/24网段中运行的ZooKeeper客户端都具有SuperApp子树的znode节点的读取权限。该鉴权模式假设IP地址无法被伪造,这个假设也许并不能适合于所有环境中。

2、SASL和Kerberos

SASL表示简单认证与安全层(Simple Authentication and SecurityLayer)。SASL将底层系统的鉴权模型抽象为一个框架,因此应用程序可以使用SASL框架,并使用SASL支持多各种协议。在ZooKeeper中,SASL常常使用Kerberos协议,该鉴权协议提供之前我们提到的那些缺失的功能。在使用SASL模式时,使用sasl作为模式名,id则使用客户端的 Kerberos的ID。
SASL是ZooKeeper的扩展鉴权模式,因此,需要通过配置参数或Java系统中参数激活该模式。如果你采用ZooKeeper的配置文件方式,需要使用authProvider.XXX配置参数,如果你想要通过系统参数方式,需要使用zookeeper.authProvider.XXX作为参数名。这两种情况,XXX
可以为任意值,只要没有任何重名的authProvider,一般XXX采用以0开始的一个数字。配置项的参数值为org.apache.zookeeper.server.auth.SASLAuthenticationProvider,这样就可以激活SASL模式。

3、增加新鉴权模式

ZooKeeper中还可以使用其他的任何鉴权模式。对于激活新的鉴权模式来说只是简单的编码问题。在org.apache.zookeeper.server.auth包中提供了一个名为AuthenticationProvider的接口类,如果你实现你自己的鉴权模式,你可以将你的类发布到服务器的classpath下,创建zookeeper.authProvider名称前缀的Java系统参数,并将参数值设置为你实现AuthenticationProvider接口的实际的类名。

二、恢复会话

  • 首先,应用程序的ZooKeeper状态还处于客户端崩溃时的状态,其他客户端进程还在继续运行,也许已经修改了ZooKeeper的状态,因此,建议客户端不要使用任何之前从ZooKeeper获取的缓存状态,而是使用ZooKeeper作为协作状态的可信来源。
  • 第二个重要问题是客户端崩溃时,已经提交给ZooKeeper的待处理操作也许已经完成了,由于客户端崩溃导致无法收到确认消息,ZooKeeper无法保证这些操作肯定会成功执行,因此,客户端在恢复时也许需要进行一些ZooKeeper状态的清理操作,以便完成某些未完成的任务。

三、当znode节点重新创建时,重置版本号

znode节点被删除并重建后,其版本号将会被重置。如果应用程序在一个znode节点重建后,进行版本号检查会导致错误的发生。

四、sync方法

因为与ZooKeeper的带外通信可能会导致某些问题,这种通信常常称为隐蔽通道(hidden channel)
sync方法可以用于处理这种情况。sync为异步调用的方法,客户端在读操作前调用该方法,假如客户端从某些直接通道收到了某个节点变化的通知,并要读取这个znode节点,客户端就可以通过sync方法,然后再调用getData方法:
...
zk.sync(path, voidCb, ctx); ①
zk.getData(path, watcher, dataCb, ctx); ②
...
/** ①sync方法接受一个path参数,一个void返回类型的回调方法的示例,一个上下文对象实例。
    ②getData方法与之前介绍的调用方式一样。 */
sync方法的path参数指示需要进行操作的路径。在系统内部,sync方法实际上并不会影响ZooKeeper,当服务端处理sync调用时,服务端会刷新群首与调用sync操作的客户端c所连接的服务端之间的通道,刷新的意思就是说在调用getData的返回数据的时候,服务端确保返回所有客户端c调用sync方法时所有可能的变化情况。在上面的隐蔽通道的情况中,变化情况的通信会先于sync操作的调用而发生,因此当c收到getData调用的响应,响应中必然会包含c'所通知的变化情况。注意,在此时该节点也可能发生了其他变化,因此在调用getData时,ZooKeeper只保证所有变化情况能够返回。
使用sync还有一个注意事项,这个需要深入ZooKeeper内部的技术问题(你可以选择跳过此部分)。因为ZooKeeper的设计初衷是用于快速读取以及以读为主要负载的扩展性考虑,所以简化了sync的实现,同时与其他常规的更新操作(如create、setData或delete)不同,sync操作并不会进入执行管道之中。sync操作只是简单地传递到群首,之后群首会将响应包队列化,传递给群组成员,之后发送响应包。不过还有另外一种可能,仲裁机制确定的群首l',现在已经不被仲裁组成员所认可,仲裁组成员现在选举了另个群首l',在这种情况下,群首l可能无法处理所有的更新操作的同步,而sync调用也就可能无法履行其保障。
ZooKeeper的实现中,通过以下方式处理上面的问题,ZooKeeper中的仲裁组成员在放弃一个群首时会通知该群首,通过群首与群组成员之间的tickTime来控制超时时间,当它们之间的TCP连接丢失,群组成员在收到socket的异常后就会确定群首是否已经消失。群首与群组成员之间的超时会快于TCP连接的中止,虽然的确存在这种极端情况导致错误的可能,但是在我们现有经验中还未曾遇到过。

五、顺序性保障

虽然ZooKeeper声明对一个会话中所有客户端操作提供顺序性的保障,但还是会存在ZooKeeper控制之外某些情况,可能会改变客户端操作的顺序。

1、连接丢失时的顺序性

对于连接丢失事件,ZooKeeper会取消等待中的请求:

  • 对于同步方法的调用客户端库会抛出异常
  • 对于异步请求调用,客户端调用的回调函数会返回结果码来标识连接丢失

2、同步 API 和多线程的顺序性

如果在多线程环境中使用同步API,需要特别注意顺序性问题,一个同步ZooKeeper调用会阻塞运行,直到收到响应信息,如果两个或更多线程向ZooKeeper同时提交了同步操作,这些线程中将会被阻塞,直到收到响应信息,ZooKeeper会顺序返回响应信息,但操作结果可能因线程调度等原因导致后提交的操作而先被执行。

3、同步和异步混合使用的顺序性

六、数据字段和子节点的限制

ZooKeeper默认情况下对数据字段的传输限制为1MB,该限制为任何节点数据字段的最大可存储字节数,同时也限制了任何父节点可以拥有的子节点数。

七、嵌入式 Zookeeper 服务器

缺点:

  • Zookeeper 不透明
  • 应用可用性和 ZooKeeper 可用性耦合
  • ZooKeeper常常被用来提供高可用服务,但对于应用中嵌入ZooKeeper的方式却降低了其最强的优势。

总有刁民想害朕
111 声望9 粉丝

引用和评论

0 条评论