こんにちはfukuです。
前回は実際にZooKeeperをスタンドアローンで起動して、zkCliからデータツリーの操作に関して説明を行いました。
今回は実際のソースコードからデータツリーがどのように実装されているのかを解説していこうと思います。ソースコードはバージョン3.5.3
をもとにしています。
データツリーのデータ構造
データツリーはこれまで説明を行ってきたように、znodeで構成されます。znodeはソースコードではDataNodeクラスで管理されます。まずはDataNodeについてみていきましょう。
DataNode
DetaNodeクラスのインスタンス変数には以下のようなものがあります。
public class DataNode implements Record { /** the data for this datanode */ byte data[]; /** * the acl map long for this datanode. the datatree has the map */ Long acl; /** * the stat for this node that is persisted to disk. */ public StatPersisted stat; /** * the list of children for this node. note that the list of children string * does not contain the parent path -- just the last part of the path. This * should be synchronized on except deserializing (for speed up issues). */ private Set<String> children = null;
data
はcreateやsetコマンドで指定するznodeのデータを保持します。またacl
はznodeに設定されている権限情報を管理しますが、今回は権限には触れずに説明を行います。
children
は対象のznodeの子ノードを保持します。データはSet<String>
と なっているので、子ノードの親ノードを除いたパス名が格納されます。例として、/parent
、/parent/child1
、/parent/child2
とznodeがある場合には/parrent
ノードのchildren
変数には"child1" と"child2"が格納されることになります。
最後にstat
はStatPersistedクラスのインスタンスが格納されます。StatPersistedクラスはディスク永続化するためのノードの情報が格納されます。StatPersistedクラスの定義は以下となります。
// information explicitly stored by the server persistently class StatPersisted { long czxid; // created zxid long mzxid; // last modified zxid long ctime; // created long mtime; // last modified int version; // version int cversion; // child version int aversion; // acl version long ephemeralOwner; // owner id if ephemeral, 0 otw long pzxid; // last modified children }
StatPersistedでは主にノードに関してのバージョン情報とZooKeeperトランザクションID(ZXID)に関する情報を保持します。バージョンやトランザクションに関しては次回以降に説明をできればと思います。
StatPersistedの状態は前回紹介した、zkCli.shからget
コマンドの-s
オプションによって参照が行えます。
[zk: localhost:2181(CONNECTED) 0] get -s /parent hogehoge cZxid = 0x9 ctime = Sun Jan 07 17:37:14 JST 2018 mZxid = 0xe mtime = Sun Jan 07 17:48:47 JST 2018 pZxid = 0x9 cversion = 0 dataVersion = 4 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 8 numChildren = 0
ただしCLIから表示される内容はStatPersistedインスタンスそのものではなく、以下に示すStatクラスのインスタンスへ変換した内容を表示します(StatPersistedのcopyStatメソッドにより変換が行われます)。ただ内容に関してはdataの長さ(dataLength)や子ノードの数(numChildren)が追加される程度で、ほとんど内容としては同じとなります。
// information shared with the client class Stat { long czxid; // created zxid long mzxid; // last modified zxid long ctime; // created long mtime; // last modified int version; // version int cversion; // child version int aversion; // acl version long ephemeralOwner; // owner id if ephemeral, 0 otw int dataLength; //length of the data in the node int numChildren; //number of children of this node long pzxid; // last modified children }
DataTree
つづいてデータツリーをメモリ上で管理するDataTreeクラスについてみていきます。DataTreeクラスから、znode管理に関する変数を以下に示します。
public class DataTree { /** * This hashtable provides a fast lookup to the datanodes. The tree is the * source of truth and is where all the locking occurs */ private final ConcurrentHashMap<String, DataNode> nodes = new ConcurrentHashMap<String, DataNode>(); /** * This hashtable lists the paths of the ephemeral nodes of a session. */ private final Map<Long, HashSet<String>> ephemerals = new ConcurrentHashMap<Long, HashSet<String>>(); /** * This set contains the paths of all container nodes */ private final Set<String> containers = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); /** * This set contains the paths of all ttl nodes */ private final Set<String> ttls = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()); }
ここで上記で紹介したかくインスタンス変数に関して説明をしていきます。
nodes
nodes
はデータツリーが管理するすべてznodeの情報を格納します。ZooKeeperではツリー構造のデータを扱いますが、データ構造上は一般的な親ノードが子ノードのデータポインタを管理するようなツリー構造のデータ構造をしておらず(DataNodeではあくまで子ノードのパス名した管理していませんでした)、DataTreeインスタンスがすべてのznodeのデータポインタを管理するような構造となっています。
ZooKeeperでは内部のメタデータを管理するためにデフォルトのデータツリーの状態をDataTreeのコンストラクタで生成します。デフォルトのデータツリーの状態は以下のようになっています。
そのためデフォルトのnodes
の状態は"/"、"/zookeeper"、"zookeeper/config"、"/zookeeper/quota" それぞれのパス名とDataNodeへ参照となります。
ephemerals
変数ephemerals
ではEPHEMERALモードのznodeを管理します。Map<Long, HashSet<String>>
構造になっていますが、KeyでセッションIDを管理してValueで対象のセッションに紐づくEPHEMERALノードパスを管理するようになっており、特定のセッションが切断された場合に、削除するべきEPHEMERALノードを簡単に参照できるような構造となっています。
参考としてセッションを切断した時に呼び出すkillSessionメソッドを記載します。
void killSession(long session, long zxid) { HashSet<String> list = ephemerals.remove(session); if (list != null) { for (String path : list) { try { deleteNode(path, zxid); if (LOG.isDebugEnabled()) { LOG .debug("Deleting ephemeral node " + path + " for session 0x" + Long.toHexString(session)); } } catch (NoNodeException e) { LOG.warn("Ignoring NoNodeException for path " + path + " while removing ephemeral for dead session 0x" + Long.toHexString(session)); } } } }
2行目のHashSet<String> list = ephemerals.remove(session);
で切断対象のセッションに紐づくEPHEMERALノードのパス一覧を取得していることがわかります。
containers
containers
変数ではCONTAINERモードのznodeのパスを管理しています。これはZooKeeper内部で自動的にCONTAINERモードのznodeを削除する際に、すべてのCONTAINERノードを簡単に参照できるようにするためです。
ttls
ttls
変数はTTLモードのznodeのパスを管理します。ttls
もcontainers
と同じように内部的にttlsを効率よく参照して、生存期間が切れたのznodeを削除するために利用されます。
このようにNodeTreeインスタンスでは、すべてのznodeをnodes
変数で管理して、EPHEMERALモードなどの特定のモードに関してのznodeに対しては、追加でそれぞれのモードを管理する変数にそのパス情報を格納しながらデータツリー全体を管理しています。
おわりに
今回は実際にソースコードからデータツリーがメモリ上でどのように管理しているのかをみていきました。 次回はさらにznodeを深掘りして理解を深めていけたらと思います。
ではでは...