最近在作多租戶改造,租戶使用的配置項都要放到zk上(如數據庫配置、redis配置、阿里oss配置、每一個租戶的域名配置等),每一個子系統去zk讀取配置。當配置信息改變時,經過zk的watch機制,給子系統發消息,子系統接收到消息以後,再去作相應的處理(如:租戶1的數據庫配置變了,就從新建立數據庫鏈接池)。html
圖1:系統關係邏輯及問題描述java
問題描述:node
主要是隻讀的那些zkClient,不用帳號密碼,就能讀(r)到zk上的數據。而不是給子系統分配帳號密碼。redis
子系統訪問zk的時候,是不能帶帳號密碼的。數據庫
若是用digest 給子系統分配統一的一個帳號密碼,在代碼的層面是能夠實現的。apache
可是老大要求,子系統訪問zk的時候,不能帶着密碼。並且,只能有讀(r)權限。session
注:這5種權限中,delete是指對子節點的刪除權限,其它4種權限指對自身節點的操做權限框架
使用命令看一下world的權限是cdrwa(也就是anyone擁有全部權限)spa
因此,忽然想到,能不能把world身份認證方式的默認權限設置成r,而admin的帳號分配給crdwa權限。.net
因而使用代碼:
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權限: List<ACL> acl = 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> acl) { if (!zkClient.exists(path)) { zkClient.createPersistent(path, true, acl); } }
這樣,world默認的權限就改成了readOnly的了:
ZooKeeper 筆記(5) ACL(Access Control List)訪問控制列表
然而,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方法設置的權限信息給刪掉。也須要本身去處理這個問題。詳情:
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /nb-conf