数据分类
数据整体可以分为3类:
内存数据
磁盘数据:1)快照
2)事务日志
内存数据
Zookeeper将数据存储在内存中三个数据结构:DataNode、DataTree、ZKDataBase。
DataNode是ZooKeeper内存数据存储的最小单位,是持久化数据节点描述的最小单位,包括:parent(父节点的引用)、data(该节点存储数据)、acl(acl控制权限)、stat(持久化节点状态)、children(子节点列表)。
DataTree中nodes是Map,表示所有的ZooKeeper节点,ZNode的唯一标识path作为key。ephemerals是Map,用于存储临时节点,临时节点是跟Session绑定的,sessionId作为key。

Zookeeper的内存数据库,管理Zookeeper的所有会话、DataTree存储和事务日志。ZKDatabase会定时向磁盘dump快照数据,同时在Zookeeper启动时,会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库。
快照数据
ZooKeeper的数据在内存中是以树形结构进行存储的,而快照就是每隔一段时间就会把整个DataTree的数据序列化后存储在磁盘中,这就是ZooKeeper的快照文件。快照数据生成的基本过程:
FileSnap负责维护快照数据对外的接口,包括快照数据的写入和读取等,将内存数据库写入快照数据文件其实是一个序列化过程。针对客户端的每一次事务操作,Zookeeper都会将他们记录到事务日志中,同时也会将数据变更应用到内存数据库中,Zookeeper在进行若干次事务日志记录后,将内存数据库的全量数据Dump到本地文件中,这就是数据快照。其步骤如下:
- 确定是否需要进行数据快照:每进行一次事务日志记录之后,Zookeeper都会检测当前是否需要进行数据快照,考虑到数据快照对于Zookeeper机器的影响,需要尽量避免Zookeeper集群中的所有机器在同一时刻进行数据快照。采用过半随机策略进行数据快照操作。
- 切换事务日志文件:表示当前的事务日志已经写满,需要重新创建一个新的事务日志。
- 创建数据快照异步线程:创建单独的异步线程来进行数据快照以避免影响Zookeeper主流程。
- 获取全量数据和会话信息:从ZKDatabase中获取到DataTree和会话信息。
- 生成快照数据文件名:Zookeeper根据当前已经提交的最大ZXID来生成数据快照文件名。
- 数据序列化:首先序列化文件头信息,然后再对会话信息和DataTree分别进行序列化,同时生成一个Checksum,一并写入快照数据文件中去。
事务日志
在配置Zookeeper集群时需要配置dataDir目录,其用来存储事务日志文件。

日志写入
FileTxnLog负责维护事务日志对外的接口,包括事务日志的写入和读取等。Zookeeper的事务日志写入过程大体可以分为如下6个步骤。
- 确定是否有事务日志可写:当Zookeeper服务器启动完成需要进行第一次事务日志的写入,或是上一次事务日志写满时,都会处于与事务日志文件断开的状态,即Zookeeper服务器没有和任意一个日志文件相关联。因此在进行事务日志写入前,Zookeeper首先会判断FileTxnLog组件是否已经关联上一个可写的事务日志文件。若没有,则会使用该事务操作关联的ZXID作为后缀创建一个事务日志文件,同时构建事务日志的文件头信息,并立即写入这个事务日志文件中去,同时将该文件的文件流放入streamToFlush集合,该集合用来记录当前需要强制进行数据落盘的文件流。
- 确定事务日志文件是否需要扩容(预分配):Zookeeper会采用磁盘空间预分配策略。当检测到当前事务日志文件剩余空间不足4096字节时,就会开始进行文件空间扩容,即在现有文件大小上,将文件增加65536KB(64MB),然后使用"0"填充被扩容的文件空间。
- 事务序列化:对事务头和事务体的序列化,其中事务体又可分为会话创建事务、节点创建事务、节点删除事务、节点数据更新事务等。
- 生成Checksum:为保证日志文件的完整性和数据的准确性,Zookeeper在将事务日志写入文件前,会计算生成Checksum。
- 写入事务日志文件流:将序列化后的事务头、事务体和Checksum写入文件流中,此时并为写入到磁盘上。
- 事务日志刷入磁盘:由于步骤5中的缓存原因,无法实时地写入磁盘文件中,因此需要将缓存数据强制刷入磁盘。
日志截断
在ZooKeeper运行过程中,可能出现非Leader记录的事务ID比Leader上大,这是非法运行状态。此时,需要保证所有机器必须与该Leader的数据保持同步,即Leader会发送TRUNC命令给该机器,要求进行日志截断,Learner收到该命令后,就会删除所有包含或大于该事务ID的事务日志文件。
数据相关过程
初始化
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服务器。
总结
ZooKeeper的数据与存储中,内存数据与磁盘数据间的关系:
1、内存数据是真正提供服务的数据
2、磁盘数据作用:
- 恢复内存数据,恢复现场;
- 数据同步:集群内,不同节点间的数据同步
磁盘数据,为什么同时包含:快照、事务日志?
出于数据粒度的考虑。如果只包含快照,那恢复现场的时候,会有数据丢失,因为生成快照的时间间隔太大,即,快照的粒度太粗了。事务日志,针对每条提交的事务都会 flush 到磁盘,因此粒度很细,恢复现场时,能够恢复到事务粒度上
快照生成的时机:基于阈值,引入随机因素
1、解决的关键问题:避免所有节点同时 dump snapshot,因为 dump snapshot 耗费大量的 磁盘 IO、CPU,所有节点同时 dump 会严重影响集群的对外服务能力
2、countLog > snapCount/2 + randRoll,其中:
countLog 为累计执行事务个数
snapCount 为配置的阈值
randRoll 为随机因素(取值:0~snapCount/2)
ZooKeeper的快照文件是 Fuzzy 快照,不是精确到某一时刻的快照,而是某一时间段内的快照
1、ZooKeeper使用「异步线程」生成快照:
线程之间共享内存空间,导致 Fuzzy 快照
这就要求ZooKeeper的所有事务操作是幂等的,否则产生数据不一致的问题
实际上ZooKeeper的所有操作都是幂等的
ZooKeeper的数据存储分为DataNode、DataTree和ZKDataBase,内存数据以树形结构存储,定期生成快照并记录事务日志。快照在数据恢复和集群同步中起到关键作用。数据同步包括差异同步、回滚后再差异同步和全量同步。快照基于阈值和随机因素生成,确保服务稳定。
2827

被折叠的 条评论
为什么被折叠?



