這一節咱們主要來看一下zookeeper文件系統的實現。java
樹結構apache
爲了提升對指定節點的操做,zookeeper使用一個HashMap來存儲樹結構數據,key爲數據路徑,value爲節點數據。session
樹節點(DataNode)app
1 public class DataNode implements Record { 2 //父節點 3 DataNode parent; 4 //節點數據 5 byte data[]; 6 //節點權限 7 Long acl; 8 //節點狀態信息 9 public StatPersisted stat; 10 //子節點名 11 private Set<String> children = null; 12 13 }
數節點狀態(StatPersisted)ide
1 public class StatPersisted implements Record { 2 //該節點建立是的事務xid 3 private long czxid; 4 //該節點最後一次修改的事務id 5 private long mzxid; 6 //建立時間 7 private long ctime; 8 //最後一次修改時間 9 private long mtime; 10 //節點版本號 11 private int version; 12 //子節點版本號 13 private int cversion; 14 //acl版本號 15 private int aversion; 16 //是否爲零時節點 17 private long ephemeralOwner; 18 //子列表被修改的zxid 19 private long pzxid; 20 }
配額管理ui
zookeeper在建立、修改節點時能夠設置特定路徑上的配額。在實現上,配額也存儲在文件系統中,而且還存儲了節點當前的信息。配額的控制在特定的路徑下:/zookeeper/quota/{節點路徑}/zookeeper_limits 節點的限制數據節點;/zookeeper/quota/{節點路徑}/zookeeper_stats 節點的當前量節點。一個節點路徑中只能有一個配額限制。this
當在對某個節點進行操做時,咱們須要知道該路徑下的哪一個節點設置了配額,由於樹結構使用hashmap來存儲,因此不便於經過路徑查找,因此使用了一個樹結構來表示那個節點上配置了限額。spa
配額路徑(PathTrie)3d
1 public class PathTrie { 2 //根節點 3 private final TrieNode rootNode ; 4 //節點 5 static class TrieNode { 6 //是否設置配額,false沒有,true有 7 boolean property = false; 8 //子節點 9 final HashMap<String, TrieNode> children; 10 //父節點 11 TrieNode parent = null; 12 13 14 //刪除子節點配額 15 void deleteChild(String childName) { 16 synchronized(children) { 17 if (!children.containsKey(childName)) { 18 return; 19 } 20 TrieNode childNode = children.get(childName); 21 //若是子節點沒有本身點直接刪除,不然設置property爲false 22 if (childNode.getChildren().length == 1) { 23 childNode.setParent(null); 24 children.remove(childName); 25 } 26 else { 27 childNode.setProperty(false); 28 } 29 } 30 } 31 } 32 //新增配額節點 33 public void addPath(String path) { 34 if (path == null) { 35 return; 36 } 37 String[] pathComponents = path.split("/"); 38 TrieNode parent = rootNode; 39 String part = null; 40 if (pathComponents.length <= 1) { 41 throw new IllegalArgumentException("Invalid path " + path); 42 } 43 for (int i=1; i<pathComponents.length; i++) { 44 part = pathComponents[i]; 45 if (parent.getChild(part) == null) { 46 parent.addChild(part, new TrieNode(parent)); 47 } 48 parent = parent.getChild(part); 49 } 50 parent.setProperty(true); 51 } 52 53 //刪除配額節點 54 public void deletePath(String path) { 55 if (path == null) { 56 return; 57 } 58 String[] pathComponents = path.split("/"); 59 TrieNode parent = rootNode; 60 String part = null; 61 if (pathComponents.length <= 1) { 62 throw new IllegalArgumentException("Invalid path " + path); 63 } 64 for (int i=1; i<pathComponents.length; i++) { 65 part = pathComponents[i]; 66 if (parent.getChild(part) == null) { 67 return; 68 } 69 parent = parent.getChild(part); 70 } 71 TrieNode realParent = parent.getParent(); 72 realParent.deleteChild(part); 73 } 74 75 //獲取指定路徑上配額節點最大路徑 76 public String findMaxPrefix(String path) { 77 if (path == null) { 78 return null; 79 } 80 if ("/".equals(path)) { 81 return path; 82 } 83 String[] pathComponents = path.split("/"); 84 TrieNode parent = rootNode; 85 List<String> components = new ArrayList<String>(); 86 if (pathComponents.length <= 1) { 87 throw new IllegalArgumentException("Invalid path " + path); 88 } 89 int i = 1; 90 String part = null; 91 StringBuilder sb = new StringBuilder(); 92 //最大路徑的index 93 int lastindex = -1; 94 while((i < pathComponents.length)) { 95 if (parent.getChild(pathComponents[i]) != null) { 96 part = pathComponents[i]; 97 parent = parent.getChild(part); 98 components.add(part); 99 if (parent.getProperty()) { 100 lastindex = i-1; 101 } 102 } 103 else { 104 break; 105 } 106 i++; 107 } 108 for (int j=0; j< (lastindex+1); j++) { 109 sb.append("/" + components.get(j)); 110 } 111 return sb.toString(); 112 } 113 }
監聽器管理code
zookeeper能夠對指定路徑進行監聽,當指定路徑發生變化時,監聽器會執行響應的動做。主要是經過將path和watcher創建關聯關係,在對指定路徑進行操做是調用相應監聽器方法。
監聽管理器(WatchManager)
1 public class WatchManager { 2 //key爲path value爲該path對應的watcher集合 3 private final HashMap<String, HashSet<Watcher>> watchTable = 4 new HashMap<String, HashSet<Watcher>>(); 5 //key爲watcher value爲該watcher對應的path集合,使用兩個hashmap來維護路徑和監聽器是由於watcher和路徑是多對多關係,這樣不管經過watcher仍是路徑均可以很快找到對應的路徑和watcher。 6 private final HashMap<Watcher, HashSet<String>> watch2Paths = 7 new HashMap<Watcher, HashSet<String>>(); 8 9 public synchronized void addWatch(String path, Watcher watcher) { 10 //新增watcher到watchTable 11 HashSet<Watcher> list = watchTable.get(path); 12 if (list == null) { 13 list = new HashSet<Watcher>(4); 14 watchTable.put(path, list); 15 } 16 list.add(watcher); 17 //新增watcher到watch2Paths 18 HashSet<String> paths = watch2Paths.get(watcher); 19 if (paths == null) { 20 paths = new HashSet<String>(); 21 watch2Paths.put(watcher, paths); 22 } 23 paths.add(path); 24 } 25 26 public synchronized void removeWatcher(Watcher watcher) { 27 //從watch2Paths和watchTable刪除watcher 28 HashSet<String> paths = watch2Paths.remove(watcher); 29 if (paths == null) { 30 return; 31 } 32 for (String p : paths) { 33 HashSet<Watcher> list = watchTable.get(p); 34 if (list != null) { 35 list.remove(watcher); 36 if (list.size() == 0) { 37 watchTable.remove(p); 38 } 39 } 40 } 41 } 42 //觸發watcher 43 public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) { 44 WatchedEvent e = new WatchedEvent(type, 45 KeeperState.SyncConnected, path); 46 HashSet<Watcher> watchers; 47 synchronized (this) { 48 //zookeeper的通知是一次性的,也就是說若是一個路徑觸發通知後,相應的watcher會從這兩個hashmap中刪除。 49 watchers = watchTable.remove(path); 50 for (Watcher w : watchers) { 51 HashSet<String> paths = watch2Paths.get(w); 52 if (paths != null) { 53 paths.remove(path); 54 } 55 } 56 } 57 for (Watcher w : watchers) { 58 if (supress != null && supress.contains(w)) { 59 continue; 60 } 61 w.process(e); 62 } 63 return watchers; 64 } 65 }
臨時節點
zookeeper中有一類節點在建立的session結束後會被清除掉,zookeeper在建立這些節點時會記錄節點和session 的對應關係,到session結束是,刪除這些節點。
結束session(DataTree.killSession)
1 //session與零時節點對應關係 2 private final Map<Long, HashSet<String>> ephemerals = 3 new ConcurrentHashMap<Long, HashSet<String>>(); 4 //關閉session 5 void killSession(long session, long zxid) { 6 //session結束後,刪除零時節點 7 HashSet<String> list = ephemerals.remove(session); 8 if (list != null) { 9 for (String path : list) { 10 try { 11 deleteNode(path, zxid); 12 } catch (NoNodeException e) { 13 LOG.warn("Ignoring NoNodeException for path " + path 14 + " while removing ephemeral for dead session 0x" 15 + Long.toHexString(session)); 16 } 17 } 18 } 19 }
權限管理
zookeeper的每一個節點都會存儲該節點能夠訪問的用戶已經能夠執行的操做。
權限(ACL)
1 public class ACL implements Record { 2 //perms即權限,有五種權限:READ(可讀);WRITE(可寫);CREATE(可建立子節點);DELETE(可刪除子節點);ADMIN(管理權限);perms的每一位表明一種權限。 3 private int perms; 4 //id是受權的對象。 5 private org.apache.zookeeper.data.Id id; 6 } 7 public class Id implements Record { 8 //scheme是權限模式,有五種模式:digest(經過用戶名密碼,id爲user:password);auth();ip(經過ip,id爲ip地址);world(固定用戶爲anyone,爲全部Client端開放權限 );super(對應的id擁有超級權限)。 9 private String scheme; 10 private String id; 11 }
每一個節點能夠設置多個權限,實際節點權限只存儲一個整數,對應的acl信息保存在兩個hashmap中。(DataTree.java)
1 public final Map<Long, List<ACL>> longKeyMap = new HashMap<Long, List<ACL>>(); 2 public final Map<List<ACL>, Long> aclKeyMap = new HashMap<List<ACL>, Long>();
節點操做