起因
最近在做多租户改造,租户使用的配置项都要放到zk上(如数据库配置、redis配置、阿里oss配置、每个租户的域名配置等),每个子系统去zk读取配置。当配置信息改变时,通过zk的watch机制,给子系统发消息,子系统接收到消息之后,再去做相应的处理(如:租户1的数据库配置变了,就重新创建数据库连接池)。
图1:系统关系逻辑及问题描述
问题描述:
主要是只读的那些zkClient,不用账号密码,就能读(r)到zk上的数据。而不是给子系统分配账号密码。
子系统访问zk的时候,是不能带账号密码的。
如果用digest 给子系统分配统一的一个账号密码,在代码的层面是可以实现的。
但是老大要求,子系统访问zk的时候,不能带着密码。而且,只能有读(r)权限。
思路
注:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限
使用命令看一下world的权限是cdrwa(也就是anyone拥有所有权限)
所以,突然想到,能不能把world身份认证方式的默认权限设置成r,而admin的账号分配给crdwa权限。
于是使用代码:
public void set(String path, String data) { createPathIfNotExists(path); zkClient.writeData(path, data); } /** * 如果path不存在,则创建。 * * @param path */ private void createPathIfNotExists(String path) { String id = null; try { id = generateDigest("admin:admin"); // admin用户的账号:密码 } catch (NoSuchAlgorithmException e) { throw new RRException("zk生成idPassword失败。zkPassword=" + zkPassword + ",zkPassword=" + zkPassword); } // 创建znode时,同时设置Acl权限: Listacl = new ArrayList<>(); acl.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", id))); acl.add(new ACL(ZooDefs.Perms.READ, new Id("world", "anyone"))); createPersistentIfNotExists(path, acl); } private void createPersistentIfNotExists(String path, List acl) { if (!zkClient.exists(path)) { zkClient.createPersistent(path, true, acl); } }
这样,world默认的权限就改为了readOnly的了:
参考
然而,ACL毕竟仅仅是访问控制,并非完善的权限管理,通过这种方式做多集群隔离,还有很多局限性:
(1)ACL并无递归机制,任何一个znode创建后,都需要单独设置ACL,无法继承父节点的ACL设置。
(2)除了ip这种scheme,digest和auth的使用对用户都不是透明的,这也给使用带来了很大的成本,很多依赖zookeeper的开源框架也没有加入对ACL的支持,例如hbase,storm
2. session的超时问题:
ZKClient框架里会经常看见一些while语句,是由这些while语句完成的,比如ZkClient.retryUntilConnected方法
(感谢紫川的反馈,此条可能存在描述性问题。经校对:ZkClient貌似还是有对Session Expired 处理的,在ZkClient.processStateChanged方法中。虽然能重新连接,但是连接上是一个新的 session,原有创建的ephemeral znode(即临时节点)和watch会被删除,程序上你可能需要处理这个问题。欢迎大家提出意见,万分感谢)
对上面引用的补充:zkClient重新连接之后,还会把通过org.I0Itec.zkclient.ZkClient#addAuthInfo方法设置的权限信息给删掉。也需要自己去处理这个问题。详情: