zookeeper日志有三类:快照(虽然不是日志但是它是数据)、事务日志(记录每次操作)、zookeeper自己系统日志。第三个不属于数据类所以这里不做说明。


快照数据

Zookeeper在运行时会在内存中维护一个完整的数据,就像内存数据库一样。ZKDatabase就是Zookeeper的内存数据库,负载管理Zookeeper的会话、存储和事务日志。它会定期dump一份数据快照到硬盘上,在Zookeeper启动时根据这个快照数据和事务日志来加载一份完整的数据到内存。这一点跟Redis很像,其实很多时候思路都是一样。

通过在配置文件中设置dataDir来指定快照保存位置。将内存数据库写入快照文件其实是一个序列化的过程。快照文件保存只是每个节点的元数据而非数据本身

那么每次快照间隔多久呢?

其实可以通过snapCount来进行配置,这个值得含义是每次快照之间的事务数量。也就是说执行多少次事务操作后进行一次快照。


  1. 每完成一次事务操作Zookeeper都会检查是否达到snapCount设置也就是来判断是否需要进行快照操作,因为快照本身对机器性能有影响,要避免集群中所有节点都进行快照

  2. 如果要进行快照操作,首先就需要对事务日志进行截断然后切换,所以事务日志写满不是以64M为标准而是以事务条数为标准的。

  3. 创建异步线程来执行快照操作(这一点又和Redis的bgsave一样)

  4. 从ZKDatabase中获取全量数据和会话,因为要保存内存所有数据节点信息和会话信息

  5. 生成快照名称,会根据已提交的最大ZXID来生成快照名称

  6. 数据序列化,首先会序列化文件头信息(魔数、版本、dbid信息),然后对会话信息和DataTree(Zookeeper内存数据的核心一个树形数据结构,代表内存完整数据)分别序列化,同时生成一个校验和,然后一起写入数据文件中。


事务日志

在配置文件中通过dataLogDir来配置事务日志路径,如果不配置默认保存在dataDir指定的路径下面。

Snip20180630_34.png

这些文件都是65M,其实是64M。而且都是以log.开头后面是一个十六进制数字作为后缀。这个后缀是一个ZXID它是写入该日志的第一个事务的事务ID,这样可以达到快速定位某一事务的效果。


日志写入过程:

  1. 当需要写入事务日志的时候Zookeeper会判断它是否和一个可写入的事务日志相关联,如果关联就写入,如果没有则用该事务的事务ID来创建一个事务日志,同时将这个事务日志对应的文件流放入到一个集合中(streamsToFlush),这个集合中记录的是当前需要强刷数据到磁盘的文件流(因为操作系统通常有延迟写入机制,对于Linux系统强刷等于调用fsync)。

  2. 事务日志文件采用预先分配空间策略,这样为了保证单一事务日志文件所占用的磁盘块是连续的,这也是为了提高性能。当Zookeeper发现当前正在写入的事务日志文件空间不足4KB时,就会启动预先分配空间策略进行扩容。第一次使用事务日志或者事务日志达到切割条数(snapCount参数触发快照)会启动预先分配策略;其他时候只要发现当前使用的事务日志空余不足4KB就进行扩容,扩容时使用0进行填充。

  3. 确保日志文件空间够之后就需要对进行事务序列化操作,最终产生一个字节数组。主要对事务头(TxnHeader)和事务体(Record)进行序列化。

  4. 根据序列化后的字节数据计算一个校验和

  5. 将字节数组和校验写入到文件流中。

  6. 由于该事务日志的文件流在集合中,这时候就会从集合里面取出文件流强刷落盘。

初始化

Zookeeper启动期间要完成数据初始化也就是将完整的数据加载到内存。它会根据快照和事务日志来完成加载过程。快照保存的是数据的元数据(不包括节点数据)而事务日志记录的操作和数据,那么使用快照+事务日志的方式可以还原完整的数据。它会先解析快照文件用于恢复出一个DataTree,此时就可以解析出个最新的ZXID,然后再根据ZXID来通过事务日志进行数据填充。另外还需要获取大于ZXID之后的事务日志进行应用。当完成数据恢复以后就可以获得一个最新的ZXID,这ZXID就是上次最后一个提交的事务ID。


完成初始化之后并且如果在集群环境中,Leader和其他服务器还会有一个数据同步过程。

Leader服务器会提取三个值:peerLastZxid(其他角色服务器最后处理的ZXID)、minCommittedLog(Leader服务器当前最小的ZXID)、maxCommittedLog(Leader服务器当前最大的ZXID)。

  • 差异同步:如果其他角色服务器的peerLastZxid介于minCommittedLog、maxCommittedLog那么就使用差异化同步,这时候Leader就知道他和对端服务器差多少,然后把这个差异进行发送再提交就是Zookeeper的两阶段提交。

  • 先回滚在差异同步:另外一种情况是在上一种情况下发生了意外原来的Leader要发送但是此时该Leader挂了,那么其余的会进行Leader选举,新的Leader产生后原来的Leader恢复了,那么显然他俩的ZXID有可能不同,新的Leader的ZXID小,但是要保证Leader的权威所以其他ZXID比它大的都要回滚到和现有Leader一样的状态或者小于Leader的ZXID的状态,然后在进行差异同步。

  • 仅回滚:也就是Leader让其他服务器都回滚到和自己一样的最大ZXID上

  • 全量同步:peerLastZxid小于Leader上的minCommittedLog,其实就是Leader将全部内存数据给Learner。这种情况通常用于在现有集群中又增加了一台Zookeeper服务器。