Zookeeper(四))持久化日志文件

Zookeeper(四))持久化日志文件

持久化用途

存储两种文件

  • snapshot:内存快照
  • log:事务日志,类似MySQL的binlog,存储数据节点的操作日志

 

问题

  • 序列化的本质其实就是将原数据重新写入
  • roll中的BufferedOutputStream.flush和commit中的FileChannel.force()都是强制刷新:有什么区别

 

基本术语

  • FileTxnSnapLog,封装了TxnLog和SnapShot。 是操作数据文件和快照文件的对外API
  • TxnLog,接口类型,读取事务性日志的接口。
  • FileTxnLog,实现TxnLog接口,添加了访问该事务性日志的API。
  • Snapshot,接口类型,持久层快照接口。
  • FileSnap,实现Snapshot接口,负责存储、序列化、反序列化、访问快照。
  • Util,工具类,提供持久化所需的API。

实现

TxnLog
public interface TxnLog {
    /**
     * Setter for ServerStats to monitor fsync threshold exceed
     * @param serverStats used to update fsyncThresholdExceedCount
     */
    void setServerStats(ServerStats serverStats);
    
    // 滚动日志,从当前日志滚到下一个日志,不是回滚
    void rollLog() throws IOException;
    // 添加一个请求至事务性日志
    boolean append(TxnHeader hdr, Record r) throws IOException;

    // 读取事务性日志
    TxnIterator read(long zxid) throws IOException;
    
    // 事务性操作的最新zxid
    long getLastLoggedZxid() throws IOException;
    
    // 清空zxid以后的日志
    boolean truncate(long zxid) throws IOException;
    
    // 获取数据库的id
    long getDbId() throws IOException;
    
    // 提交事务并进行确认
    void commit() throws IOException;

    long getTxnLogSyncElapsedTime();
   
    // 关闭事务性日志
    void close() throws IOException;
}
FileTxnLog

实现TxnLog接口,提供操作事务日志的api

public class FileTxnLog implements TxnLog {
    //最新的日志zxid
    long lastZxidSeen;
    //日志文件
    volatile BufferedOutputStream logStream = null;
    volatile OutputArchive oa;
    //日志存储文件
    File logFileWrite = null;
    private FilePadding filePadding = new FilePadding();
    private LinkedList<FileOutputStream> streamsToFlush =
        new LinkedList<FileOutputStream>();
    //重置日志文件
    public synchronized void rollLog() throws IOException {
        if (logStream != null) {
            this.logStream.flush();
            this.logStream = null;
            oa = null;
        }
    }
    //添加事务日志 hdr:事务标题 txn:事务本身
    public synchronized boolean append(TxnHeader hdr, Record txn)
        throws IOException {
        if (hdr == null) {
            return false;
        }
        //判断并更新最新的zxid
        if (hdr.getZxid() <= lastZxidSeen) {
            LOG.warn("Current zxid " + hdr.getZxid()
                    + " is <= " + lastZxidSeen + " for " + hdr.getType());
        } else {
            lastZxidSeen = hdr.getZxid();
        }
        //构建事务日志文件
        if (logStream==null) {
           if(LOG.isInfoEnabled()){
                LOG.info("Creating new log file: " + Util.makeLogName(hdr.getZxid()));
           }
		   //1. 生成新的log文件
           logFileWrite = new File(logDir, Util.makeLogName(hdr.getZxid()));
           //2. 生成log文件输出流
           fos = new FileOutputStream(logFileWrite);
           //为写入给定的输出流而创建缓冲输出流
           logStream=new BufferedOutputStream(fos);
           //获取二进制序列化类 TODO
            //BinaryOutputArchive内部维护一个DataOutput 根据值传递特性 
            //oa序列化写入时其实就是写入log文件
           oa = BinaryOutputArchive.getArchive(logStream);
           //3. 用TXNLOG_MAGIC VERSION dbId来生成事务日志文件头
           FileHeader fhdr = new FileHeader(TXNLOG_MAGIC,VERSION, dbId);
           //4. 将事务日志文件头序列化到文件上
           fhdr.serialize(oa, "fileheader");
           //确保文件扩展之前 魔数已被写入
           logStream.flush();
           filePadding.setCurrentSize(fos.getChannel().position());
           streamsToFlush.add(fos);
        }
        //5. 剩余空间不足时 填充文件
        filePadding.padFile(fos.getChannel());
        //6. 将事务头部和本身序列化为字节数组
        byte[] buf = Util.marshallTxnEntry(hdr, txn);
        if (buf == null || buf.length == 0) {
            throw new IOException("Faulty serialization for header " + "and txn");
        }
        //生成验证算法 校验数据流
        Checksum crc = makeChecksumAlgorithm();
        crc.update(buf, 0, buf.length);
        oa.writeLong(crc.getValue(), "txnEntryCRC");
        //6. 将当前序列化的事务记录写入到oa
        Util.writeTxnBytes(oa, buf);
        return true;
    }
    //从给定的zxid开始读取日志文件
    public TxnIterator read(long zxid, boolean fastForward) throws IOException {
        return new FileTxnIterator(logDir, zxid, fastForward);
    }
    //提交日志 保存到磁盘
    public synchronized void commit() throws IOException {
        //刷到磁盘
        if (logStream != null) {
            logStream.flush();
        }
        //强刷到磁盘
        for (FileOutputStream log : streamsToFlush) {
            log.flush();
            if (forceSync) {
                long startSyncNS = System.nanoTime();
                FileChannel channel = log.getChannel();
                //会强制将所有未写入磁盘的数据都强制写入磁盘 比如还在缓冲区中的数据
                channel.force(false);
                syncElapsedMS = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startSyncNS);
                if (syncElapsedMS > fsyncWarningThresholdMS) {
                    if(serverStats != null) {
                        serverStats.incrementFsyncThresholdExceedCount();
                    }
                    LOG.warn("fsync-ing the write ahead log in "
                            + Thread.currentThread().getName()
                            + " took " + syncElapsedMS
                            + "ms which will adversely effect operation latency. "
                            + "File size is " + channel.size() + " bytes. "
                            + "See the ZooKeeper troubleshooting guide");
                }
                ServerMetrics.FSYNC_TIME.add(syncElapsedMS);
            }
        }
        //移除流并关闭
        while (streamsToFlush.size() > 1) {
            streamsToFlush.removeFirst().close();
        }
       //当日志文件大小超过限制 强刷到磁盘并重置
        if(txnLogSizeLimit > 0) {
            long logSize = getCurrentLogSize();
            if (logSize > txnLogSizeLimit) {
                LOG.debug("Log size limit reached: {}", logSize);
                rollLog();
            }
        }
    }
}

 

FileTxnIterator:用于读取事务日志

public static class FileTxnIterator implements TxnLog.TxnIterator {
    public FileTxnIterator(File logDir, long zxid, boolean fastForward)
                throws IOException {
        this.logDir = logDir;
        this.zxid = zxid;
        init();
        if (fastForward && hdr != null) {
            while (hdr.getZxid() < zxid) {
                if (!next())
                    break;
            }
        }
    }
    void init() throws IOException {
        storedFiles = new ArrayList<File>();
        //找出大于等于snapshot中最大的zxid的logfile及后续logfile并升序
        List<File> files = Util.sortDataDir(FileTxnLog.getLogFiles(logDir.listFiles(), 0), LOG_FILE_PREFIX, false);
        for (File f: files) {
            if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) >= zxid) {
                storedFiles.add(f);
            }
            // add the last logfile that is less than the zxid
            else if (Util.getZxidFromName(f.getName(), LOG_FILE_PREFIX) < zxid) {
                storedFiles.add(f);
                break;
            }
        }
        goToNextLog();
        next();
    }
}

 

posted on 2019-04-28 10:16  hangzhi  阅读(764)  评论(0编辑  收藏  举报

导航