内存数据
zookeeper的数据结构是一颗树,类似于linux的文件系统,但是更加简单轻量。zookeeper在内存中存储了整个树的内容,DataTree包括所有节点的路径、节点数据以及ACL信息等。
DataTree底层是一个典型的ConcurrentHashMap:
- key: 节点路径(path)
- value: 节点内容(datanode)
datanode是数据存储的最小单元,包含了节点的数据内容、ACL列表和节点状态,还记录了父节点的应用和子节点列表。
对于临时节点,zookeeper记录在另一个ConcurrentHashMap中,便于实施访问和及时清理。
事务日志
leader接收到客户端的事务请求之后,会将请求发送给follower,进行事务投票,客户端会将事务记录在本地的事务日志中,然后给leader返回ACK信号。对于observer服务器来说,虽然不用进行事务投票,但是投票成功的事务仍需要从leader同步到observer服务器,本地记录事务日志,同时将事务变更更新到内存数据库。
zookeeper默认生成的事务日志大小是64M,zk会给日志文件与分配固定的空间进行事务日志的写入,预分配的固定大小的文件使用0(\0)进行空间的填充。zk这样的实际是为了提高事务日志的写入性能,因为事务日志的写入直接影响了zk对于事务操作的相应速度。事务写入可以近似看作一个磁盘IO的过程,严格的将,文件的不断追加写入会触发底层磁盘IO为文件开辟新的磁盘块,即磁盘seek。因此为了避免seek的频率,提高磁盘IO的效率,zk对日志文件进行空间的与分配,默认大小是64M,一旦发现文件空间不足4K,会再次进行预分配,以避免文件追加过程中触发的seek对于性能的影响。
在这个过程中,如果发现非leader服务器的事务ID比leader服务器大,无论这个情况是如何发生的,都是一个非法的运行状态,此时,leader服务器会向learner服务器发送TRUNC信号,命令这台服务器对日志进行截断,删除所有大于peerLastZxid的事务文件。
数据快照
随着事务请求的发生,服务器会不断向事务日志中写入事务记录。在zookeeper中可以配置,记录多少次事务日志之后触发一次数据快照。数据快照是将内存数据库中的所有数据dump到一个快照文件中。zk每次执行快照都会生成一个新的文件,基本上文件的大小也能现在内存数据中存储的数据的多少。
初始化
zk初始化的时候,所有的服务器总体上会执行两个部分的操作:
- 读取日至快照文件中的数据快照,将快照中的数据恢复到内存数据库中。zk会默认获取最新的100个快照文件,从最新的文件开始读取,一旦读取成功之后,旧的快照文件便不需要恢复。
- 经过快照恢复之后,zk获取到了快照中最新的zxid,继而去事务日志文件中恢复大于这个zxid的事务日志,进行恢复。
由于快照的执行是通过事务执行次数来触发的,假设配置的执行间隔是snapCount,那么上一次快照触发到下一次快照触发之间,有可能有1~(snapCount-1)个事务是没有被记录,这部分事务被记录在事务日志中,因此初始化的时候需要进行上述两部分操作。
数据同步
集群初始化完成之后,进行leader选举,会优先将拥有最大zxid的服务器选举为leader。leader选举完成之后,learner会向learder进行注册,当注册完成之后,便会进行数据的同步,简单的将就是learder将follower上没有提交的事务,同步给learner服务器。