Zookeeper源码解析(一)之持久化

本文深入探讨Zookeeper的持久化机制,包括事务日志与数据快照的实现原理及策略。事务日志采用write-ahead log机制,确保数据安全性;数据快照则用于全量数据的备份与恢复,通过超半随机策略决定快照时机。

Zookeeper的持久化就是将内存中的数据保存到磁盘上,防止数据丢失。

持久化主要分为两种
  • 事务日志
  • 数据快照
事务日志
  • 事务日志主要是将每个事务操作先日志文件里,再进行实际的事务操作。这种先写日志后操作的方式被称为“write-ahead log”,这种机制被广泛使用的各种场景,比如Mysql的redo log,hdfs的editlog等等。有些是只先写元数据,有些是写数据本身。这种机制的好处是先写日志,可防止数据丢失。另一个附带好处是可以把多个IO合并成一个IO,大大提高吞吐量。
  • 事务日志文件的命名规则:log.2c01311231
    • 后缀就是写文件时最新的事务ID(ZXID),高32位代表选举任期(epoch),低32位代表事务序列号。2c即epoch,01311231即序列号。
  • 事务日志的磁盘空间预分配策略:每个事务日志文件固定为64M,也就是创建文件时预先分配64M的空间,未使用的部分都是0。
  • 日志写入过程
    • 确定事务日志文件是否需要扩容
    • 事务序列化
    • 生成checksum
    • 写入事务日志文件流
    • 刷到磁盘
snapshot 数据快照
  • 数据快照:将zookeeper服务器上某一时刻的全量内存数据序列化后写到磁盘文件中。可用于数据恢复。

  • 快照文件的命名:snapshot.2c01311231。快照开始时刻的最新zxid当做后缀。

    每次事务日志写入后都会判断是否需要进行快照:

// 写入事务日志成功后
if (zks.getZKDatabase().append(si)) {
    // 判断是否进行快照
    if (shouldSnapshot()) {
        // 重置快照统计
        resetSnapshotStats();
        // roll the log 将事务日志输出流置为null,便于下次写事务日志时重新创建新文件
        zks.getZKDatabase().rollLog();
        // take a snapshot 尝试获取快照锁
        if (!snapThreadMutex.tryAcquire()) {
            LOG.warn("Too busy to snap, skipping");
        } else {
            // 创建异步线程进行快照任务,任务结束后线程销毁。不影响其他线程的运行。
            new ZooKeeperThread("Snapshot Thread") {
                public void run() {
                    try {
                        // 真正的快照方法:核心是内存数据结构的序列化
                        zks.takeSnapshot();
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception", e);
                    } finally {
                        snapThreadMutex.release();
                    }
                }
            }.start();
        }
    }
} 

下面看一下shouldSnapshot()方法是如何判断是否需要快照的

    private boolean shouldSnapshot() {
        // 获取从上次快照到现在的事务总数
        int logCount = zks.getZKDatabase().getTxnCount();
        // 获取从上次快照到现在的事务数据的大小
        long logSize = zks.getZKDatabase().getTxnSize();
        // snapCount和snapSizeInBytes都是配置参数。snapCount的默认大小是10万,
        // randRoll是0到snapCount/2之间的随机数。
        // logCount > (snapCount / 2 + randRoll)的含义是一种“超半随机”的策略。
        // 事务数据大小的判断逻辑也是一样。        // 
        return (logCount > (snapCount / 2 + randRoll))
               || (snapSizeInBytes > 0 && logSize > (snapSizeInBytes / 2 + randSize));
    }
    // 重置半数随机数。在判断需要快照后(shouldSnapshot()返回true)会进行重置
    private void resetSnapshotStats() {
        // 半数随机数
        randRoll = ThreadLocalRandom.current().nextInt(snapCount / 2);
        randSize = Math.abs(ThreadLocalRandom.current().nextLong() % (snapSizeInBytes / 2));
    }

上面源码注释中提到的“过半随机”的策略目的,是为了防止集群中zookeeper机器在同一时间都进行快照,尽量地错开运行,有助于提高性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值