ZooKeeper源码分析六之FileTxnSnapLog

一、FileTxnSnapLog

前边分析可知,ZooKeeper中数据的持久化以及日志文件的写入都是通过FileTxnSnapLog对象来实现的,本文大致分析一下这个对象的整体结构。

FileTxnSnapLog的初始化在runFromConfig方法中:

FileTxnSnapLog txnLog = new FileTxnSnapLog(config.dataLogDir, config.dataDir)

其中dataDir和dataLogDir是在配置文件中进行路径配置,并且dataDir必须的且有效的地址,dataLogDir没有配置的情况下等于dataDir。

FileTxnSnapLog 的构造方法:

    public FileTxnSnapLog(File dataDir, File snapDir) throws IOException {
       //此时会在data目录和log目录下创建一个子目录,子目录version-*也就是当前的版本号
        this.dataDir = new File(dataDir, version + VERSION);
        this.snapDir = new File(snapDir, version + VERSION);
        //data目录是否是自动创建,默认是true,可以通过zookeeper.datadir.autocreate继续配置
        boolean enableAutocreate = Boolean.parseBoolean(
            System.getProperty(ZOOKEEPER_DATADIR_AUTOCREATE, ZOOKEEPER_DATADIR_AUTOCREATE_DEFAULT));
         //默认是false
        trustEmptySnapshot = Boolean.getBoolean(ZOOKEEPER_SNAPSHOT_TRUST_EMPTY);
        //如果当前data目录不存在
        if (!this.dataDir.exists()) {
        //如果不允许自动创建,抛出异常
            if (!enableAutocreate) {
                throw new DatadirException(String.format( "Missing data directory %s, automatic data directory creation is disabled (%s is false).Please create this directory manually.",this.dataDir,ZOOKEEPER_DATADIR_AUTOCREATE));
            }//如果自动创建失败,抛出异常
            if (!this.dataDir.mkdirs() && !this.dataDir.exists()) {
                throw new DatadirException("Unable to create data directory " + this.dataDir);
            }
        }
        //如果当前目录只读状态,抛出异常
        if (!this.dataDir.canWrite()) {
            throw new DatadirException("Cannot write to data directory " + this.dataDir);
        }
        //snapDir文件也是一样的处理流程(代码已经省略)
        //如果日志文件和数据文件不是相同目录
        if (!this.dataDir.getPath().equals(this.snapDir.getPath())) {
          //这个目录主要是检查data目录下是否包含日志文件,和相反检查
            checkLogDir();
            checkSnapDir();
        }
        //创建日志事务对象和快照事务对象
        txnLog = new FileTxnLog(this.dataDir);
        snapLog = new FileSnap(this.snapDir);
       //默认是true
        autoCreateDB = Boolean.parseBoolean(
            System.getProperty(ZOOKEEPER_DB_AUTOCREATE, ZOOKEEPER_DB_AUTOCREATE_DEFAULT));
    }

既然FileTxnSnapLog是管理data和log两类数据的,我们就来看看整个的管理过程。
FileTxnSnapLog 第一次调用是在服务启动时,需要加载数据到内存中,也就是ZKDatabase中的loadDataBase()调用是,此时就会调用FileTxnSnapLog 的restore方法,此方法会先调用SnapShot实例去反序列化文件数据到DataTree中。如果是首次启动,那么就会在当前目录下创建一个snapshot.0的文件,并写入文件头以及默认节点信息到这个文件中,如果是重启,此时会在该目录下找到所有的snapshot.*文件,然后根据文件后缀名降序排序,得到文件集合,然后把第一个(表示上一次关闭是的最新事务文件)文件内容反序列到内存中,如果执行成功返回。

如果当前启动是重启,就会执行fastForwardFromEdits方法,这个方法就是从日志文件中恢复上一次关闭时,没有持久化的操作。

public long fastForwardFromEdits(DataTree dt,Map<Long, Integer> sessions,PlayBackListener listener)throws IOException {
        //得到一个事务迭代器
        TxnIterator itr = txnLog.read(dt.lastProcessedZxid + 1);
        //得到上一个snapshot文件中最新的事务id
        long highestZxid = dt.lastProcessedZxid;
        TxnHeader hdr;
        int txnLoaded = 0;
        long startTime = Time.currentElapsedTime();
        try {
            while (true) {
                hdr = itr.getHeader();
                if (hdr == null) {
                    //表示当前的id是最新的
                    return dt.lastProcessedZxid;
                }
                if (hdr.getZxid() < highestZxid && highestZxid != 0) {
                    LOG.error("{}(highestZxid) > {}(next log) for type {}", highestZxid, hdr.getZxid(), hdr.getType());
                } else {
                    //设置最新id
                    highestZxid = hdr.getZxid();
                }
                try {
                    //处理上一个服务关闭前,还未完成的持久化操作,也就是说从日志中恢复数据
                    processTransaction(hdr, dt, sessions, itr.getTxn());
                    dt.compareDigest(hdr, itr.getTxn(), itr.getDigest());
                    txnLoaded++;
                } catch (KeeperException.NoNodeException e) {

                }
                listener.onTxnLoaded(hdr, itr.getTxn(), itr.getDigest());
                //继续迭代
                if (!itr.next()) {
                    break;
                }
            }
        } finally {
            if (itr != null) {
                itr.close();
            }
        }
        return highestZxid;
    }

我们先看看txnLog.read(dt.lastProcessedZxid + 1)方法,这里为什么是+1,因为,日志中的数据总是比快照数据要新,或者相等。快照文件的后缀名表示的是当前最新的事务id,所以日志文件的后缀名命名方式是当前快照文件最新事务id+1,所以这里需要+1操作,read方法如下:

    public TxnIterator read(long zxid, boolean fastForward) throws IOException {
        return new FileTxnIterator(logDir, zxid, fastForward);
    }

此时我们再看FileTxnIterator的构造方法:

        public FileTxnIterator(File logDir, long zxid, boolean fastForward) throws IOException {
            this.logDir = logDir;
            this.zxid = zxid;
            //初始
            init();
            //这里是找到当前最新的事务id
            if (fastForward && hdr != null) {
                while (hdr.getZxid() < zxid) {
                    if (!next()) {
                        break;
                    }
                }
            }
        }

init方法:

        void init() throws IOException {
            storedFiles = new ArrayList<>();
            //列出所有的日志文件
            List<File> files = Util.sortDataDir(
                FileTxnLog.getLogFiles(logDir.listFiles(), 0),
                LOG_FILE_PREFIX,
                false);
             //找到当前事务id大于等于zxid的值,以及小于它的第一个值
            for (File f : files) {
                if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) >= zxid) {
                    storedFiles.add(f);
                } else if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) < zxid) {
                    storedFiles.add(f);
                    break;
                }
            }
            //从storedFiles集合中从后往前取日志文件,并封装成文件流对象InputArchive
            goToNextLog();
            //从日志文件中取出对应的操作记录
            next();
        }

FileTxnIterator返回时,表示当前是最新的事务id,如果日志文件中存在还没有持久化的事务,调用processTransaction方法进行数据的恢复操作。

当数据加载到内存中完成,会进行数据文件的快照,也就是调用save方法,还有就是当当前的请求数达到了默认值的5W+或者当前的事务容量已经达到了默认值2GB+时,就会进行一次快照保存。

我们知道ZKDatabase会记录每一次的增删改操作,也就是调用FileTxnSnapLog的append方法,往日志文件中写入日志,每当进行数据快照备份的时候,相应的日志文件也会进行,也会重新生成对应的日志文件。

getLastLoggedZxid()这个方法总是返回日志文件中最新的事务id。

二、总结

ZooKeeper中通过FileTxnSnapLog对象来管理数据快照文件和日志文件,FileTxnSnapLog对象又是通过TxnLog和SnapShot来管理,前者是进行日志数据管理,后者是进行数据快照文件管理。ZooKeeper中通过日志文件记录所有的增删改操作,这个是实时持久化到硬盘的,所以当系统突然停止。下次重启之后也可以从日志文件中恢复没有处理的信息。

以上,有任何不对的地方,请留言指正,敬请谅解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟+1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值