一、Zookeeper持久化

当一个zookeeper服务启动时,会将磁盘上的数据还原成DataTree,那么zookeeper在磁盘上是如何来保存数据的?分为两部分,一个是快照文件,另一个是日志文件。接下来结合源码来介绍一下这两类文件

1.1 快照文件

快照文件的文件名是以"snapshot"为前缀的,如snapshot.40d4(40d4是十六进制的表示,代表该快照文件起始的zxid)。

何时生成快照?

当事务执行了5w-10w次(随机数),触发生成快照,具体逻辑在org.apache.zookeeper.server.SyncRequestProcessor类的run方法中,源码如下:

if (si != null) {
  // 将请求追加到日志文件
  if (zks.getZKDatabase().append(si)) {
      logCount++;
      // snapCount默认值是10w,randRoll初始值为5w之内的随机数
      // 如果请求数 大于 5w-10w的随机数
      if (logCount > (snapCount / 2 + randRoll)) {
          randRoll = r.nextInt(snapCount/2);
          // 滚动日志文件
          zks.getZKDatabase().rollLog();
          if (snapInProcess != null && snapInProcess.isAlive()) {
              LOG.warn("Too busy to snap, skipping");
          } else {
              // 启动快照生成线程
              snapInProcess = new Thread("Snapshot Thread") {
                      public void run() {
                          try {
                              zks.takeSnapshot();
                          } catch(Exception e) {
                              LOG.warn("Unexpected exception", e);
                          }
                      }
                  };
              snapInProcess.start();
          }
          logCount = 0;
      }
  } 
}

从上面源码可以看到,当写入到日志文件的请求数到达一个随机值(5w-10w),将会触发生成快照,zks.takeSnapshot()内部会调用FileTxnSnapLog类的save方法,将内存中的数据DataTree持久化到快照文件中,具体的序列化源码如下

public static void serializeSnapshot(DataTree dt,OutputArchive oa,
            Map<Long, Integer> sessions) throws IOException {
        HashMap<Long, Integer> sessSnap = new HashMap<Long, Integer>(sessions);
        // 序列化sessions
        oa.writeInt(sessSnap.size(), "count");
        for (Entry<Long, Integer> entry : sessSnap.entrySet()) {
            oa.writeLong(entry.getKey().longValue(), "id");
            oa.writeInt(entry.getValue().intValue(), "timeout");
        }
        // 序列化DataTree
        dt.serialize(oa, "tree");
    }

1.2 日志文件

日志文件的文件名是以"log"为前缀的,如log.40d4(40d4是十六进制的表示,代表该日志文件起始的zxid)。从上面也可以看到zookeeper处理请求时,会先将请求追加到日志文件中。

服务启动时如何使用日志文件还原到之前状态?

在zookeeper服务启动时,会先调用ZKDatabase.loadDataBase()将磁盘的数据还原到DataTree,源码如下

org.apache.zookeeper.server.ZKDatabase

public long loadDataBase() throws IOException {
    PlayBackListener listener=new PlayBackListener(){
        public void onTxnLoaded(TxnHeader hdr,Record txn){
            Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
                    null, null);
            r.txn = txn;
            r.hdr = hdr;
            r.zxid = hdr.getZxid();
            addCommittedProposal(r);
        }
    };
    // 通过磁盘上的快照文件、日志文件,将数据还原到DataTree
    long zxid = snapLog.restore(dataTree,sessionsWithTimeouts,listener);
    initialized = true;
    return zxid;
}

这里可以看到调用了FileTxnSnapLog的restore方法,看下该方法具体是如何来还原DataTree的?源码如下

public long restore(DataTree dt, Map<Long, Integer> sessions, 
            PlayBackListener listener) throws IOException {
    // 先将快照文件的数据反序列化到DataTree中
    snapLog.deserialize(dt, sessions);
    FileTxnLog txnLog = new FileTxnLog(dataDir);
    // 读取大于当前DataTree的zxid的日志数据
    TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
    long highestZxid = dt.lastProcessedZxid;
    TxnHeader hdr;
    try {
        while (true) {
            hdr = itr.getHeader();
            if (hdr == null) {
                return dt.lastProcessedZxid;
            }
            if (hdr.getZxid() < highestZxid && highestZxid != 0) {
                LOG.error("{}(higestZxid) > {}(next log) for type {}",
                        new Object[] { highestZxid, hdr.getZxid(),
                                hdr.getType() });
            } else {
                highestZxid = hdr.getZxid();
            }
            try {
                // 处理单个事务请求,将数据还原到DataTree
                processTransaction(hdr,dt,sessions, itr.getTxn());
            } catch(KeeperException.NoNodeException e) {
               throw new IOException("Failed to process transaction type: " +
                     hdr.getType() + " error: " + e.getMessage(), e);
            }
            listener.onTxnLoaded(hdr, itr.getTxn());
            if (!itr.next()) 
                break;
        }
    } finally {
        if (itr != null) {
            itr.close();
        }
    }
    return highestZxid;
}
  1. 第一步,先将快照文件数据反序列化到DataTree
  2. 读取日志文件中大于当前DataTree的zxid的日志数据
  3. 遍历日志数据,逐一将日志数据还原到DataTree中

kamier
1.5k 声望493 粉丝