精华内容
下载资源
问答
  • 2021-12-12 21:40:04

    1 概述

    Namenode会定期将文件系统的命名空间(文件目录树、文件/ 目录元信息) 保存到fsimage文件中, 以防止Namenode掉电或者进程崩溃。 但如果Namenode实时地将内存中的元数据同步到fsimage文件中, 将会非常消耗资源且造成Namenode运行缓慢。 所以Namenode会先将命名空间的修改操作保存在editlog文件中, 然后定期合并fsimage和editlog文件。

    FSImage类主要实现了以下功能:

    • 保存命名空间——将当前时刻Namenode内存中的命名空间保存到fsimage文件中。
    • 加载fsimage文件——将磁盘上fsimage文件中保存的命名空间加载到Namenode内存中, 这个操作是保存命名空间操作的逆操作。
    • 加载editlog文件——Namenode加载了fsimage文件后, 内存中只包含了命名空间在保存fsimage文件时的信息, Namenode还需要加载后续对命名空间的修改操作,即editlog文件中记录的内容。 所以FSImage类还提供了加载editlog文件到Namenode内存中的功能.

    2 源码解释

    2.1 初始化

    FsImage是在NN启动时加载完成,通过FSNamesystem中的loadFromDisk方法进行初始化的

    // FSNamesystem初始化是在NameNode#initialize()方法中的loadNamesystem(conf)完成:
    // 根据配置中指定位置的edit和fsImage初始化FsNameSystem
    loadNamesystem(conf);
    ...
    // 这里其实是调用FSNamesystem中的loadFromDisk方法,在加载磁盘中edit日志的过程中完成了FSNamesystem的初始化
    protected void loadNamesystem(Configuration conf) throws IOException {
        this.namesystem = FSNamesystem.loadFromDisk(conf);
    }
    static FSNamesystem loadFromDisk(Configuration conf) throws IOException {
    
        // 检查和fsImage以及edit相关的配置
        checkConfiguration(conf);
        // 根据指定目录初始化FSImage
        FSImage fsImage = new FSImage(conf,
            FSNamesystem.getNamespaceDirs(conf),
            FSNamesystem.getNamespaceEditsDirs(conf));
    }

    现在看看他的构造函数:

    /**
       * Construct the FSImage. Set the default checkpoint directories.
       *
       * Setup storage and initialize the edit log.
       *
       * @param conf Configuration
       * @param imageDirs Directories the image can be stored in.
       * @param editsDirs Directories the editlog can be stored in.
       * @throws IOException if directories are invalid.
       */
    protected FSImage(Configuration conf,
                      Collection<URI> imageDirs,
                      List<URI> editsDirs)
        throws IOException {
        this.conf = conf;
    
        // 负责管理 NameNode 使用的 StorageDirectories
        storage = new NNStorage(conf, imageDirs, editsDirs);
        
        // dfs.namenode.name.dir.restore:默认false,设置为 true 使 NameNode 能够尝试恢复
        // 以前失败的 dfs.namenode.name.dir。启用后,将在检查点期间尝试恢复任何失败的目录
        if(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
                           DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT)) {
            storage.setRestoreFailedStorage(true);
        }
    
        // 初始化FsEditLog
        this.editLog = FSEditLog.newInstance(conf, storage, editsDirs);
        // 负责检查 NN 的存储目录并在检查点和编辑日志上执行保留策略。
        archivalManager = new NNStorageRetentionManager(conf, storage, editLog);
    }

    2.2 保存Namespace

    2.2.1 saveFSImageInAllDirs过程

    FsImage的功能之一就是保存namespace到fsimage。这里使用的方法是saveFSImageInAllDirs,而此方法会在这些地方调用:

    • SecondaryNameNode的doMerge方法中,完成fsimage和editlog的merge;
    • Checkpointer中的doCheckpoint方法中
    • FsImage在执行format和upgrade时
    /**
       * @see #saveFSImageInAllDirs(FSNamesystem, NameNodeFile, long, Canceler)
       */
    protected synchronized void saveFSImageInAllDirs(FSNamesystem source, long txid)
        throws IOException {
        if (!addToCheckpointing(txid)) {
            throw new IOException(("FS image is being downloaded from another NN"));
        }
        try {
            saveFSImageInAllDirs(source, NameNodeFile.IMAGE, txid, null);
        } finally {
            removeFromCheckpointing(txid);
        }
    }

    而另一种调用方式是在FsImage#saveNamespace方法中调用,这里保存namespace数据时,主要使用此种方式。

    /**
       * Save the contents of the FS image to a new image file in each of the
       * current storage directories.
       */
    public synchronized void saveNamespace(FSNamesystem source, NameNodeFile nnf,
                                           Canceler canceler) throws IOException {
        assert editLog != null : "editLog must be initialized";
        LOG.info("Save namespace ...");
        storage.attemptRestoreRemovedStorage();
    
        boolean editLogWasOpen = editLog.isSegmentOpen();
    
        if (editLogWasOpen) {
            editLog.endCurrentLogSegment(true);
        }
        long imageTxId = getCorrectLastAppliedOrWrittenTxId();
        if (!addToCheckpointing(imageTxId)) {
            throw new IOException(
                "FS image is being downloaded from another NN at txid " + imageTxId);
        }
        try {
            try {
                saveFSImageInAllDirs(source, nnf, imageTxId, canceler);
                ...

    下面saveFSImageInAllDirs是一个私有方法,最终完成fsimage的存储。

    // namenode可以配置多个存储目录
    private synchronized void saveFSImageInAllDirs(FSNamesystem source,
                                                   NameNodeFile nnf, long txid, Canceler canceler) throws IOException {
        StartupProgress prog = NameNode.getStartupProgress();
        prog.beginPhase(Phase.SAVING_CHECKPOINT);
        if (storage.getNumStorageDirs(NameNodeDirType.IMAGE) == 0) {
            throw new IOException("No image directories available!");
        }
        if (canceler == null) {
            canceler = new Canceler();
        }
        SaveNamespaceContext ctx = new SaveNamespaceContext(
            source, txid, canceler);
    
        try {
            // 对于每个存储路径,开启一个线程进行处理
            List<Thread> saveThreads = new ArrayList<Thread>();
            // save images into current
            // 遍历
            for (Iterator<StorageDirectory> it
                 = storage.dirIterator(NameNodeDirType.IMAGE); it.hasNext();) {
                StorageDirectory sd = it.next();
                // 命名空间具体的保存操作是由FSImageSaver这个类来承担的,
            // FSImageSaver是FSImage中的内部类, 也是一个线程类,
            // 它的run()方法调用了saveFSImage()方法来保存fsimage文件。
                FSImageSaver saver = new FSImageSaver(ctx, sd, nnf);
                Thread saveThread = new Thread(saver, saver.toString());
                saveThreads.add(saveThread);
                saveThread.start();
            }
            //等待所有线程执行完毕
            waitForThreads(saveThreads);
            saveThreads.clear();
            storage.reportErrorsOnDirectories(ctx.getErrorSDs());
    
            if (storage.getNumStorageDirs(NameNodeDirType.IMAGE) == 0) {
                throw new IOException(
                    "Failed to save in any storage directories while saving namespace.");
            }
            if (canceler.isCancelled()) {
                deleteCancelledCheckpoint(txid);
                ctx.checkCancelled(); // throws
                assert false : "should have thrown above!";
            }
    
            // 将fsimage.ckpt 改名为 fsimage
            renameCheckpoint(txid, NameNodeFile.IMAGE_NEW, nnf, false);
    
            // Since we now have a new checkpoint, we can clean up some
            // old edit logs and checkpoints.
            // Do not purge anything if we just wrote a corrupted FsImage.
            // 如果完成了一次checckpoint,则清楚老的editlog以及老的checkpoint
            if (!exitAfterSave.get()) {
                purgeOldStorage(nnf);
                archivalManager.purgeCheckpoints(NameNodeFile.IMAGE_NEW);
            }
        } finally {
            // Notify any threads waiting on the checkpoint to be canceled
            // that it is complete.
            ctx.markComplete();
            ctx = null;
        }
        prog.endPhase(Phase.SAVING_CHECKPOINT);
    }

    总结一下就是:nn由多个目录存储fsimage,那么在保存namespace元数据时,会每一个目录对应一个线程去操作,之后等待所有线程执行完成,在此过程中,为了防止保存过程中出现错误,会在过程将namespace信息保存在一个fsimage.ckpt文件中,等到所有信息都被保存完整后才将文件名改为fsimage,最后会在确定完成了此次checkpoint之后清除老的editlog和checkpoint。

    2.2.2 FSImageSaver

    在上述过程中,主要的线程是FSImageSaver,这里通过start启动后调用线程中的run方法,从而完成namespace数据的保存:

    /**
       * FSImageSaver is being run in a separate thread when saving
       * FSImage. There is one thread per each copy of the image.
       *
       * FSImageSaver assumes that it was launched from a thread that holds
       * FSNamesystem lock and waits for the execution of FSImageSaver thread
       * to finish.
       * This way we are guaranteed that the namespace is not being updated
       * while multiple instances of FSImageSaver are traversing it
       * and writing it out.
       */
    private class FSImageSaver implements Runnable {
        private final SaveNamespaceContext context;
        private final StorageDirectory sd;
        private final NameNodeFile nnf;
    
        public FSImageSaver(SaveNamespaceContext context, StorageDirectory sd,
                            NameNodeFile nnf) {
            this.context = context;
            this.sd = sd;
            this.nnf = nnf;
        }
    
        @Override
        public void run() {
            // Deletes checkpoint file in every storage directory when shutdown.
            Runnable cancelCheckpointFinalizer = () -> {
                try {
                    deleteCancelledCheckpoint(context.getTxId());
                    LOG.info("FSImageSaver clean checkpoint: txid={} when meet " +
                             "shutdown.", context.getTxId());
                } catch (IOException e) {
                    LOG.error("FSImageSaver cancel checkpoint threw an exception:", e);
                }
            };
            ShutdownHookManager.get().addShutdownHook(cancelCheckpointFinalizer,
                                                      SHUTDOWN_HOOK_PRIORITY);
            try {
                saveFSImage(context, sd, nnf);
            } catch (SaveNamespaceCancelledException snce) {
                LOG.info("Cancelled image saving for " + sd.getRoot() +
                         ": " + snce.getMessage());
                // don't report an error on the storage dir!
            } catch (Throwable t) {
                LOG.error("Unable to save image for " + sd.getRoot(), t);
                context.reportErrorOnStorageDirectory(sd);
                try {
                    deleteCancelledCheckpoint(context.getTxId());
                    LOG.info("FSImageSaver clean checkpoint: txid={} when meet " +
                             "Throwable.", context.getTxId());
                } catch (IOException e) {
                    LOG.error("FSImageSaver cancel checkpoint threw an exception:", e);
                }
            }
        }
    
        @Override
        public String toString() {
            return "FSImageSaver for " + sd.getRoot() +
                " of type " + sd.getStorageDirType();
        }
    }

    在上面run方法中会调用saveFSImage方法完成namespace数据的存储

    2.2.3 saveFSImage

    /**
       * Save the contents of the FS image to the file.
       */
    void saveFSImage(SaveNamespaceContext context, StorageDirectory sd,
                     NameNodeFile dstType) throws IOException {
        // 获取当前命名空间中记录的最新事务的txid
        long txid = context.getTxId();
        // fsimage.ckpt文件
        File newFile = NNStorage.getStorageFile(sd, NameNodeFile.IMAGE_NEW, txid);
        // IMAGE:fsimage文件,即为最后的目标文件
        File dstFile = NNStorage.getStorageFile(sd, dstType, txid);
        // 使用protobuf方式做存储
        FSImageFormatProtobuf.Saver saver = new FSImageFormatProtobuf.Saver(context,
                                                                            conf);
        FSImageCompression compression = FSImageCompression.createCompression(conf);
        // 通过saver存储nn的namespace数据
        long numErrors = saver.save(newFile, compression);
        if (numErrors > 0) {
            // The image is likely corrupted.
            LOG.error("Detected " + numErrors + " errors while saving FsImage " +
                      dstFile);
            exitAfterSave.set(true);
        }
    
        // 将md5值写入md5文件
        MD5FileUtils.saveMD5File(dstFile, saver.getSavedDigest());
        storage.setMostRecentCheckpointInfo(txid, Time.now());
    }

    2.2.4 FSImageFormatProtobuf.Saver

    在上面步骤中使用FSImageFormatProtobuf.Saver完成nn的namespace数据的存储,调用save方法继续操作,而在此方法中可以调用saveInternal完成最后的操作。

    不过在此之前,先了解下FSImageFormatProtobuf,使用protobuf定义的fsimage文件的格式, 它包括了4个部分的信息:

    • MAGIC: fsimage的文件头, 是“HDFSIMG1”这个字符串的二进制形式, MAGIC头标识了当前fsimage文件是使用protobuf格式序列化的。 FSImage类在读取fsimage文件时, 会先判断fsimage文件是否包含了MAGIC头, 如果包含了则使用protobuf格式反序列化fsimage文件。
    • SECTIONS: fsimage文件会将同一类型的Namenode元信息保存在一个section中,例如将文件系统元信息保存在NameSystemSection中, 将文件系统目录树中的所有INode信息保存在INodeSection中, 将快照信息保存在SnapshotSection中等。fsimage文件的第二个部分就是Namenode各类元信息对应的所有section, 每类section中都包含了对应Namenode元信息的属性。
    • FileSummary: FileSummary记录了fsimage文件的元信息, 以及fsimage文件保存的所有section的信息。 FileSummary中的ondiskVersion字段记录了fsimage文件的版本号(hadoop3.3.0还是1) , layoutVersion字段记录了当前HDFS的文件系统布局版本号, codec字段记录了fsimage文件的压缩编码, sections字段则记录了fsimage文件中各个section字段的元信息, 每个fsimage文件中记录的section在FileSummary中都有一个与之对应的section字段。 FileSummary的section字段记录了对应的fsimage中section的名称、 在fsimage文件中的长度以及这个section在fsimage中的起始位置。 FSImage类在读取fsimage文件时, 会先从fsimage中读取出FileSummary部分, 然后利用FileSummary记录的元信息指导fsimage文件的反序列化操作。
    • FileSummaryLength: FileSummaryLength记录了FileSummary在fsimage文件中所占的长度, FSImage类在读取fsimage文件时, 会首先读取FileSummaryLength获取FileSummary部分的长度, 然后根据这个长度从fsimage中反序列化出FileSummary

    下面就先看看FSImageFormatProtobuf.Saver#save()方法

    /**
         * @return number of non-fatal errors detected while writing the image.
         * @throws IOException on fatal error.
         */
    long save(File file, FSImageCompression compression) throws IOException {
        enableSubSectionsIfRequired();
        // 创建outputstream
        FileOutputStream fout = new FileOutputStream(file);
        fileChannel = fout.getChannel();
        try {
            LOG.info("Saving image file {} using {}", file, compression);
            long startTime = monotonicNow();
            // 调用方法完成最后的保存
            long numErrors = saveInternal(
                fout, compression, file.getAbsolutePath());
            LOG.info("Image file {} of size {} bytes saved in {} seconds {}.", file,
                     file.length(), (monotonicNow() - startTime) / 1000,
                     (numErrors > 0 ? (" with" + numErrors + " errors") : ""));
            return numErrors;
        } finally {
            fout.close();
        }
    }
    
    /**
     * saveInternal()方法首先构造底层fsimage文件的输出流, 构造fsimage文件的描述类FileSummary, 
     * 然后在FileSummary中记录ondiskVersion、 layoutVersion、 codec等信息。
     * @return number of non-fatal errors detected while writing the FsImage.
     * @throws IOException on fatal error.
     */
    private long saveInternal(FileOutputStream fout,
                              FSImageCompression compression, String filePath) throws IOException {
        StartupProgress prog = NameNode.getStartupProgress();
        MessageDigest digester = MD5Hash.getDigester();
        // 获取layoutVersion
        int layoutVersion =
            context.getSourceNamesystem().getEffectiveLayoutVersion();
    
        // 构建输出流
        underlyingOutputStream = new DigestOutputStream(new BufferedOutputStream(
            fout), digester);
        // 写入magic_header:内容为"HDFSIMG1"
        underlyingOutputStream.write(FSImageUtil.MAGIC_HEADER);
    
        fileChannel = fout.getChannel();
    
        // 构建FileSummary,是fsimage文件的描述部分, 也是protobuf定义的
        // 包括onDiskVersion、layoutVersion
        FileSummary.Builder b = FileSummary.newBuilder()
            .setOndiskVersion(FSImageUtil.FILE_VERSION)
            .setLayoutVersion(
            context.getSourceNamesystem().getEffectiveLayoutVersion());
    
        // 为FileSummary设置codec,记录的是压缩格式
        codec = compression.getImageCodec();
        if (codec != null) {
            b.setCodec(codec.getClass().getCanonicalName());
            sectionOutputStream = codec.createOutputStream(underlyingOutputStream);
        } else {
            sectionOutputStream = underlyingOutputStream;
        }
        
        /**
         * 接下来saveInternal()方法依次向fsimage文件中写入
         *     1.命名空间信息、
         *     2.inode信息、
         *     3.快照信息、
         *     4.安全信息、
         *     5.缓存信息、
         *     6.StringTable
         * 信息等。
         *
         * 注意上述信息都是以section为单位写入的, 每个section的格式定义请参考fsimage.proto文件。
         * saveInternal()方法以section为单位写入元数据信息时,
         * 还会在FileSummary中记录这个section的长度,以及section在fsimage文件中的起始位置等信息。
         */
    
        // 写入名称空间信息
        saveNameSystemSection(b);
        // Check for cancellation right after serializing the name system section.
        // Some unit tests, such as TestSaveNamespace#testCancelSaveNameSpace
        // depends on this behavior.
        // 检查是否取消保存操作
        context.checkCancelled();
    
        Step step;
    
        // Erasure coding policies should be saved before inodes
        // 根据是否支持erasure_coding从而保存erasure_coding section
        if (NameNodeLayoutVersion.supports(
            NameNodeLayoutVersion.Feature.ERASURE_CODING, layoutVersion)) {
            step = new Step(StepType.ERASURE_CODING_POLICIES, filePath);
            prog.beginStep(Phase.SAVING_CHECKPOINT, step);
            saveErasureCodingSection(b);
            prog.endStep(Phase.SAVING_CHECKPOINT, step);
        }
    
        // 保存命名空间中的inode信息
        step = new Step(StepType.INODES, filePath);
        prog.beginStep(Phase.SAVING_CHECKPOINT, step);
        // Count number of non-fatal errors when saving inodes and snapshots.
        long numErrors = saveInodes(b);
        // 保存快照信息
        numErrors += saveSnapshots(b);
        prog.endStep(Phase.SAVING_CHECKPOINT, step);
    
        step = new Step(StepType.DELEGATION_TOKENS, filePath);
        prog.beginStep(Phase.SAVING_CHECKPOINT, step);
        
        // 保存安全信息
        saveSecretManagerSection(b);
        prog.endStep(Phase.SAVING_CHECKPOINT, step);
    
        // 保存缓存信息
        step = new Step(StepType.CACHE_POOLS, filePath);
        prog.beginStep(Phase.SAVING_CHECKPOINT, step);
        saveCacheManagerSection(b);
        prog.endStep(Phase.SAVING_CHECKPOINT, step);
    
        // 保存StringTable
        saveStringTableSection(b);
    
        // We use the underlyingOutputStream to write the header. Therefore flush
        // the buffered stream (which is potentially compressed) first.
        // flush 输出流
        flushSectionOutputStream();
    
        FileSummary summary = b.build();
        // 保存FileSummary
        saveFileSummary(underlyingOutputStream, summary);
        underlyingOutputStream.close();
        savedDigest = new MD5Hash(digester.digest());
        return numErrors;
    }

    因此整个saveInternal方法完成fsimage的写入。

    代码中涉及的各个section在此不作介绍,最后看看saveFileSummary:

    private static void saveFileSummary(OutputStream out, FileSummary summary)
        throws IOException {
        summary.writeDelimitedTo(out);
        int length = getOndiskTrunkSize(summary);
        byte[] lengthBytes = new byte[4];
        ByteBuffer.wrap(lengthBytes).asIntBuffer().put(length);
        out.write(lengthBytes);
    }

    2.3 加载fsImage

    当Namenode启动时, 首先会将fsimage文件中记录的命名空间加载到Namenode内存中, 然后再一条一条地将editlog文件中记录的更新操作加载并合并到命名空间中

    long loadStart = monotonicNow();
    try {
        // 加载fsImage,过程会做一系列的判断,如是否formatted,是否需要保存新的fsImage等
        namesystem.loadFSImage(startOpt);
    } catch (IOException ioe) {
        LOG.warn("Encountered exception loading fsimage", ioe);
        fsImage.close();
        throw ioe;
    }

    2.3.1 FSNamesystem#loadFSImage

    // 加载fsimage
    private void loadFSImage(StartupOption startOpt) throws IOException {
        final FSImage fsImage = getFSImage();
    
        // format before starting up if requested
        // 如果是format,完成fsimage的format
        if (startOpt == StartupOption.FORMAT) {
            // reuse current id
            fsImage.format(this, fsImage.getStorage().determineClusterId(), false);
    
            startOpt = StartupOption.REGULAR;
        }
        boolean success = false;
        writeLock();
        try {
            // We shouldn't be calling saveNamespace if we've come up in standby state.
            MetaRecoveryContext recovery = startOpt.createRecoveryContext();
            // 在加载fsImage的过程中完成判断,如果存在fsImage的文件目录未被格式化,则为true
            final boolean staleImage
                = fsImage.recoverTransitionRead(startOpt, this, recovery);
            if (RollingUpgradeStartupOption.ROLLBACK.matches(startOpt)) {
                rollingUpgradeInfo = null;
            }
            final boolean needToSave = staleImage && !haEnabled && !isRollingUpgrade(); 
            LOG.info("Need to save fs image? " + needToSave
                     + " (staleImage=" + staleImage + ", haEnabled=" + haEnabled
                     + ", isRollingUpgrade=" + isRollingUpgrade() + ")");
            if (needToSave) {
                // 保存fsImage到磁盘中
                fsImage.saveNamespace(this);
            } else {
                // No need to save, so mark the phase done.
                StartupProgress prog = NameNode.getStartupProgress();
                prog.beginPhase(Phase.SAVING_CHECKPOINT);
                prog.endPhase(Phase.SAVING_CHECKPOINT);
            }
            // This will start a new log segment and write to the seen_txid file, so
            // we shouldn't do it when coming up in standby state
            if (!haEnabled || (haEnabled && startOpt == StartupOption.UPGRADE)
                || (haEnabled && startOpt == StartupOption.UPGRADEONLY)) {
                fsImage.openEditLogForWrite(getEffectiveLayoutVersion());
            }
            success = true;
        } finally {
            if (!success) {
                fsImage.close();
            }
            writeUnlock("loadFSImage", true);
        }
        imageLoadComplete();
    }

    2.3.2 recoverTransitionRead

    /**
       * Analyze storage directories.
       * Recover from previous transitions if required. 
       * Perform fs state transition if necessary depending on the namespace info.
       * Read storage info. 
       * 
       * @throws IOException
       * @return true if the image needs to be saved or false otherwise
       */
    boolean recoverTransitionRead(StartupOption startOpt, FSNamesystem target,
                                  MetaRecoveryContext recovery)
        throws IOException {
        assert startOpt != StartupOption.FORMAT : 
        "NameNode formatting should be performed before reading the image";
    
        // 获取imageDir和editDir
        Collection<URI> imageDirs = storage.getImageDirectories();
        Collection<URI> editsDirs = editLog.getEditURIs();
    
        // none of the data dirs exist
        if((imageDirs.size() == 0 || editsDirs.size() == 0) 
           && startOpt != StartupOption.IMPORT)  
            throw new IOException(
            "All specified directories are not accessible or do not exist.");
    
        // 1. For each data directory calculate its state and 
        // check whether all is consistent before transitioning.
        // 检查每一个fsimage数据目录的状态
        Map<StorageDirectory, StorageState> dataDirStates = 
            new HashMap<StorageDirectory, StorageState>();
        // 对于每个存储目录,执行未完成转换(例如升级、回滚、检查点)的
        // 恢复并将目录的存储状态插入到 dataDirStates 映射中。
        boolean isFormatted = recoverStorageDirs(startOpt, storage, dataDirStates);
    
        if (LOG.isTraceEnabled()) {
            LOG.trace("Data dir states:\n  " +
                      Joiner.on("\n  ").withKeyValueSeparator(": ")
                      .join(dataDirStates));
        }
    
        if (!isFormatted && startOpt != StartupOption.ROLLBACK 
            && startOpt != StartupOption.IMPORT) {
            throw new IOException("NameNode is not formatted.");      
        }
    
    
        int layoutVersion = storage.getLayoutVersion();
        if (startOpt == StartupOption.METADATAVERSION) {
            System.out.println("HDFS Image Version: " + layoutVersion);
            System.out.println("Software format version: " +
                               HdfsServerConstants.NAMENODE_LAYOUT_VERSION);
            return false;
        }
    
        if (layoutVersion < Storage.LAST_PRE_UPGRADE_LAYOUT_VERSION) {
            NNStorage.checkVersionUpgradable(storage.getLayoutVersion());
        }
        if (startOpt != StartupOption.UPGRADE
            && startOpt != StartupOption.UPGRADEONLY
            && !RollingUpgradeStartupOption.STARTED.matches(startOpt)
            && layoutVersion < Storage.LAST_PRE_UPGRADE_LAYOUT_VERSION
            && layoutVersion != HdfsServerConstants.NAMENODE_LAYOUT_VERSION) {
            throw new IOException(
                "\nFile system image contains an old layout version " 
                + storage.getLayoutVersion() + ".\nAn upgrade to version "
                + HdfsServerConstants.NAMENODE_LAYOUT_VERSION + " is required.\n"
                + "Please restart NameNode with the \""
                + RollingUpgradeStartupOption.STARTED.getOptionString()
                + "\" option if a rolling upgrade is already started;"
                + " or restart NameNode with the \""
                + StartupOption.UPGRADE.getName() + "\" option to start"
                + " a new upgrade.");
        }
    
        // 处理upgrade和upgradeOnly,设置clusterId和blockPoolId
        storage.processStartupOptionsForUpgrade(startOpt, layoutVersion);
    
        // 2. Format unformatted dirs.
        //  format fsimage dirs
        for (Iterator<StorageDirectory> it = storage.dirIterator(); it.hasNext();) {
            StorageDirectory sd = it.next();
            StorageState curState = dataDirStates.get(sd);
            switch(curState) {
                case NON_EXISTENT:
                    throw new IOException(StorageState.NON_EXISTENT + 
                                          " state cannot be here");
                case NOT_FORMATTED:
                    // Create a dir structure, but not the VERSION file. The presence of
                    // VERSION is checked in the inspector's needToSave() method and
                    // saveNamespace is triggered if it is absent. This will bring
                    // the storage state uptodate along with a new VERSION file.
                    // If HA is enabled, NNs start up as standby so saveNamespace is not
                    // triggered.
                    LOG.info("Storage directory " + sd.getRoot() + " is not formatted.");
                    LOG.info("Formatting ...");
                    sd.clearDirectory(); // create empty current dir
                    // For non-HA, no further action is needed here, as saveNamespace will
                    // take care of the rest.
                    if (!target.isHaEnabled()) {
                        continue;
                    }
                    // If HA is enabled, save the dirs to create a version file later when
                    // a checkpoint image is saved.
                    if (newDirs == null) {
                        newDirs = new HashSet<StorageDirectory>();
                    }
                    newDirs.add(sd);
                    break;
                default:
                    break;
            }
        }
    
        // 3. Do transitions
        // 执行不同操作
        switch(startOpt) {
            case UPGRADE:
            case UPGRADEONLY:
                doUpgrade(target);
                return false; // upgrade saved image already
            case IMPORT:
                doImportCheckpoint(target);
                return false; // import checkpoint saved image already
            case ROLLBACK:
                throw new AssertionError("Rollback is now a standalone command, " +
                                         "NameNode should not be starting with this option.");
            case REGULAR:
            default:
                // just load the image
        }
    
        // 执行加载loadFSImage
        return loadFSImage(target, startOpt, recovery);
    }

    2.3.3 FSImage#loadFSImage

    /**
       * 选择一个最新的fsimage目录加载并与editlog合并
       * Choose latest image from one of the directories,
       * load it and merge with the edits.
       * 
       * Saving and loading fsimage should never trigger symlink resolution. 
       * The paths that are persisted do not have *intermediate* symlinks 
       * because intermediate symlinks are resolved at the time files, 
       * directories, and symlinks are created. All paths accessed while 
       * loading or saving fsimage should therefore only see symlinks as 
       * the final path component, and the functions called below do not
       * resolve symlinks that are the final path component.
       *
       * @return whether the image should be saved
       * @throws IOException
       */
    private boolean loadFSImage(FSNamesystem target, StartupOption startOpt,
                                MetaRecoveryContext recovery)
        throws IOException {
        final boolean rollingRollback
            = RollingUpgradeStartupOption.ROLLBACK.matches(startOpt);
        final EnumSet<NameNodeFile> nnfs;
        if (rollingRollback) {
            // if it is rollback of rolling upgrade, only load from the rollback image
            nnfs = EnumSet.of(NameNodeFile.IMAGE_ROLLBACK);
        } else {
            // otherwise we can load from both IMAGE and IMAGE_ROLLBACK
            nnfs = EnumSet.of(NameNodeFile.IMAGE, NameNodeFile.IMAGE_ROLLBACK);
        }
        final FSImageStorageInspector inspector = storage
            .readAndInspectDirs(nnfs, startOpt);
    
        isUpgradeFinalized = inspector.isUpgradeFinalized();
        List<FSImageFile> imageFiles = inspector.getLatestImages();
    
        StartupProgress prog = NameNode.getStartupProgress();
        prog.beginPhase(Phase.LOADING_FSIMAGE);
        File phaseFile = imageFiles.get(0).getFile();
        prog.setFile(Phase.LOADING_FSIMAGE, phaseFile.getAbsolutePath());
        prog.setSize(Phase.LOADING_FSIMAGE, phaseFile.length());
        // 各个文件目录是否被格式化了
        boolean needToSave = inspector.needToSave();
    
        Iterable<EditLogInputStream> editStreams = null;
    
        initEditLog(startOpt);
    
        if (NameNodeLayoutVersion.supports(
            LayoutVersion.Feature.TXID_BASED_LAYOUT, getLayoutVersion())) {
            // If we're open for write, we're either non-HA or we're the active NN, so
            // we better be able to load all the edits. If we're the standby NN, it's
            // OK to not be able to read all of edits right now.
            // In the meanwhile, for HA upgrade, we will still write editlog thus need
            // this toAtLeastTxId to be set to the max-seen txid
            // For rollback in rolling upgrade, we need to set the toAtLeastTxId to
            // the txid right before the upgrade marker.  
            long toAtLeastTxId = editLog.isOpenForWrite() ? inspector
                .getMaxSeenTxId() : 0;
            if (rollingRollback) {
                // note that the first image in imageFiles is the special checkpoint
                // for the rolling upgrade
                toAtLeastTxId = imageFiles.get(0).getCheckpointTxId() + 2;
            }
            editStreams = editLog.selectInputStreams(
                imageFiles.get(0).getCheckpointTxId() + 1,
                toAtLeastTxId, recovery, false);
        } else {
            editStreams = FSImagePreTransactionalStorageInspector
                .getEditLogStreams(storage);
        }
        int maxOpSize = conf.getInt(DFSConfigKeys.DFS_NAMENODE_MAX_OP_SIZE_KEY,
                                    DFSConfigKeys.DFS_NAMENODE_MAX_OP_SIZE_DEFAULT);
        for (EditLogInputStream elis : editStreams) {
            elis.setMaxOpSize(maxOpSize);
        }
    
        for (EditLogInputStream l : editStreams) {
            LOG.debug("Planning to load edit log stream: " + l);
        }
        if (!editStreams.iterator().hasNext()) {
            LOG.info("No edit log streams selected.");
        }
    
        FSImageFile imageFile = null;
        // 通过imageFile加载fsimage
        for (int i = 0; i < imageFiles.size(); i++) {
            try {
                imageFile = imageFiles.get(i);
                loadFSImageFile(target, recovery, imageFile, startOpt);
                break;
            } catch (IllegalReservedPathException ie) {
                throw new IOException("Failed to load image from " + imageFile,
                                      ie);
            } catch (Exception e) {
                LOG.error("Failed to load image from " + imageFile, e);
                target.clear();
                imageFile = null;
            }
        }
        // Failed to load any images, error out
        if (imageFile == null) {
            FSEditLog.closeAllStreams(editStreams);
            throw new IOException("Failed to load FSImage file, see error(s) " +
                                  "above for more info.");
        }
        prog.endPhase(Phase.LOADING_FSIMAGE);
    
        if (!rollingRollback) {
            prog.beginPhase(Phase.LOADING_EDITS);
            long txnsAdvanced = loadEdits(editStreams, target, Long.MAX_VALUE,
                                          startOpt, recovery);
            prog.endPhase(Phase.LOADING_EDITS);
            needToSave |= needsResaveBasedOnStaleCheckpoint(imageFile.getFile(),
                                                            txnsAdvanced);
        } else {
            // Trigger the rollback for rolling upgrade. Here lastAppliedTxId equals
            // to the last txid in rollback fsimage.
            rollingRollback(lastAppliedTxId + 1, imageFiles.get(0).getCheckpointTxId());
            needToSave = false;
        }
        editLog.setNextTxId(lastAppliedTxId + 1);
        return needToSave;
    }

    在上面代码中,遍历所有fsimageFile,而后根据指定的fsImageFile加载fsimage,过程中各种嵌套调用loadFSImage,因此我们在这看最后一个:

    /**
       * Load in the filesystem image from file. It's a big list of
       * filenames and blocks.
       */
    private void loadFSImage(File curFile, MD5Hash expectedMd5,
                             FSNamesystem target, MetaRecoveryContext recovery,
                             boolean requireSameLayoutVersion) throws IOException {
        // BlockPoolId is required when the FsImageLoader loads the rolling upgrade
        // information. Make sure the ID is properly set.
        target.setBlockPoolId(this.getBlockPoolID());
    
        // 构建LoaderDelegator对象(加载器代理对象),便于后续使用此加载器完成fsimage的加载
        FSImageFormat.LoaderDelegator loader = FSImageFormat.newLoader(conf, target);
        loader.load(curFile, requireSameLayoutVersion);
    
        // Check that the image digest we loaded matches up with what
        // we expected
        MD5Hash readImageMd5 = loader.getLoadedImageMd5();
        if (expectedMd5 != null &&
            !expectedMd5.equals(readImageMd5)) {
            throw new IOException("Image file " + curFile +
                                  " is corrupt with MD5 checksum of " + readImageMd5 +
                                  " but expecting " + expectedMd5);
        }
    
        long txId = loader.getLoadedImageTxId();
        LOG.info("Loaded image for txid " + txId + " from " + curFile);
        lastAppliedTxId = txId;
        storage.setMostRecentCheckpointInfo(txId, curFile.lastModified());
    }
    
    // 在此方法中在此调用
    public void load(File file, boolean requireSameLayoutVersion)
        throws IOException {
        Preconditions.checkState(impl == null, "Image already loaded!");
    
        InputStream is = null;
        try {
            is = Files.newInputStream(file.toPath());
            byte[] magic = new byte[FSImageUtil.MAGIC_HEADER.length];
            IOUtils.readFully(is, magic, 0, magic.length);
            if (Arrays.equals(magic, FSImageUtil.MAGIC_HEADER)) {
                FSImageFormatProtobuf.Loader loader = new FSImageFormatProtobuf.Loader(
                    conf, fsn, requireSameLayoutVersion);
                impl = loader;
                // 使用它的load方法加载fsimage文件. 在load方法中最终调用了loadInternal(raFile, fin);方法. 
                // 这个方法是加载fsimage文件的相对最底层的方法了.
                loader.load(file);
            } else {
                Loader loader = new Loader(conf, fsn);
                impl = loader;
                loader.load(file);
            }
        } finally {
            IOUtils.cleanupWithLogger(LOG, is);
        }
    }

    FSImageFormatProtobuf.Loader.loadInternal()方法执行了加载fsimage文件的操作, loadInternal()方法会打开fsimage文件通道, 然后读取fsimage文件中的FileSummary对象, FileSummary对象中记录了fsimage中保存的所有section的信息。loadInternal()会对FileSummary对象中保存的section排序, 然后遍历每个section并调用对应的方法从fsimage文件中加载这个section。

    private void loadInternal(RandomAccessFile raFile, FileInputStream fin)
        throws IOException {
        if (!FSImageUtil.checkFileFormat(raFile)) {
            throw new IOException("Unrecognized file format");
        }
        // 加载fileSummary
        FileSummary summary = FSImageUtil.loadSummary(raFile);
        if (requireSameLayoutVersion && summary.getLayoutVersion() !=
            HdfsServerConstants.NAMENODE_LAYOUT_VERSION) {
            throw new IOException("Image version " + summary.getLayoutVersion() +
                                  " is not equal to the software version " +
                                  HdfsServerConstants.NAMENODE_LAYOUT_VERSION);
        }
    
        FileChannel channel = fin.getChannel();
    
        // innnode和snapshot加载器
        FSImageFormatPBINode.Loader inodeLoader = new FSImageFormatPBINode.Loader(
            fsn, this);
        FSImageFormatPBSnapshot.Loader snapshotLoader = new FSImageFormatPBSnapshot.Loader(
            fsn, this);
    
        // 给section排序
        ArrayList<FileSummary.Section> sections = Lists.newArrayList(summary
                                                                     .getSectionsList());
        Collections.sort(sections, new Comparator<FileSummary.Section>() {
            @Override
            public int compare(FileSummary.Section s1, FileSummary.Section s2) {
                SectionName n1 = SectionName.fromString(s1.getName());
                SectionName n2 = SectionName.fromString(s2.getName());
                if (n1 == null) {
                    return n2 == null ? 0 : -1;
                } else if (n2 == null) {
                    return -1;
                } else {
                    return n1.ordinal() - n2.ordinal();
                }
            }
        });
    
        StartupProgress prog = NameNode.getStartupProgress();
        /**
           * beginStep() and the endStep() calls do not match the boundary of the
           * sections. This is because that the current implementation only allows
           * a particular step to be started for once.
           */
        Step currentStep = null;
        boolean loadInParallel = enableParallelSaveAndLoad(conf);
    
        ExecutorService executorService = null;
        ArrayList<FileSummary.Section> subSections =
            getAndRemoveSubSections(sections);
        if (loadInParallel) {
            executorService = getParallelExecutorService();
        }
    
        // 遍历每个section, 并调用对应的方法加载这个section
        for (FileSummary.Section s : sections) {
            channel.position(s.getOffset());
            InputStream in = new BufferedInputStream(new LimitInputStream(fin,
                                                                          s.getLength()));
    
            in = FSImageUtil.wrapInputStreamForCompression(conf,
                                                           summary.getCodec(), in);
    
            String n = s.getName();
            SectionName sectionName = SectionName.fromString(n);
            if (sectionName == null) {
                throw new IOException("Unrecognized section " + n);
            }
    
            ArrayList<FileSummary.Section> stageSubSections;
            // 加载fsimage文件中的不同类型的section
            switch (sectionName) {
                case NS_INFO:
                    loadNameSystemSection(in);
                    break;
                case STRING_TABLE:
                    loadStringTableSection(in);
                    break;
                case INODE: {
                    currentStep = new Step(StepType.INODES);
                    prog.beginStep(Phase.LOADING_FSIMAGE, currentStep);
                    stageSubSections = getSubSectionsOfName(
                        subSections, SectionName.INODE_SUB);
                    if (loadInParallel && (stageSubSections.size() > 0)) {
                        inodeLoader.loadINodeSectionInParallel(executorService,
                                                               stageSubSections, summary.getCodec(), prog, currentStep);
                    } else {
                        inodeLoader.loadINodeSection(in, prog, currentStep);
                    }
                }
                    break;
                case INODE_REFERENCE:
                    snapshotLoader.loadINodeReferenceSection(in);
                    break;
                case INODE_DIR:
                    stageSubSections = getSubSectionsOfName(
                        subSections, SectionName.INODE_DIR_SUB);
                    if (loadInParallel && stageSubSections.size() > 0) {
                        inodeLoader.loadINodeDirectorySectionInParallel(executorService,
                                                                        stageSubSections, summary.getCodec());
                    } else {
                        inodeLoader.loadINodeDirectorySection(in);
                    }
                    break;
                case FILES_UNDERCONSTRUCTION:
                    inodeLoader.loadFilesUnderConstructionSection(in);
                    break;
                case SNAPSHOT:
                    snapshotLoader.loadSnapshotSection(in);
                    break;
                case SNAPSHOT_DIFF:
                    snapshotLoader.loadSnapshotDiffSection(in);
                    break;
                case SECRET_MANAGER: {
                    prog.endStep(Phase.LOADING_FSIMAGE, currentStep);
                    Step step = new Step(StepType.DELEGATION_TOKENS);
                    prog.beginStep(Phase.LOADING_FSIMAGE, step);
                    loadSecretManagerSection(in, prog, step);
                    prog.endStep(Phase.LOADING_FSIMAGE, step);
                }
                    break;
                case CACHE_MANAGER: {
                    Step step = new Step(StepType.CACHE_POOLS);
                    prog.beginStep(Phase.LOADING_FSIMAGE, step);
                    loadCacheManagerSection(in, prog, step);
                    prog.endStep(Phase.LOADING_FSIMAGE, step);
                }
                    break;
                case ERASURE_CODING:
                    Step step = new Step(StepType.ERASURE_CODING_POLICIES);
                    prog.beginStep(Phase.LOADING_FSIMAGE, step);
                    loadErasureCodingSection(in);
                    prog.endStep(Phase.LOADING_FSIMAGE, step);
                    break;
                default:
                    LOG.warn("Unrecognized section {}", n);
                    break;
            }
        }
        if (executorService != null) {
            executorService.shutdown();
        }
    }

    2.4 加载Editlog

    在FSImage#loadFSImage中,在完成fsimage的加载后,如果不需要进行回滚,那么将执行加载edits的操作:

    if (!rollingRollback) {
        prog.beginPhase(Phase.LOADING_EDITS);
        long txnsAdvanced = loadEdits(editStreams, target, Long.MAX_VALUE,
                                      startOpt, recovery);
        prog.endPhase(Phase.LOADING_EDITS);
        needToSave |= needsResaveBasedOnStaleCheckpoint(imageFile.getFile(),
                                                        txnsAdvanced);
    } else {
        // Trigger the rollback for rolling upgrade. Here lastAppliedTxId equals
        // to the last txid in rollback fsimage.
        rollingRollback(lastAppliedTxId + 1, imageFiles.get(0).getCheckpointTxId());
        needToSave = false;
    }

    最后看看loadEdits

    FSImage.loadEdits()方法会构造一个FSEditLogLoader对象. 然后遍历Namenode所有存储路径上保存的editlog文件的输入流 并调用FSEditLogLoader.loadFSEdits()方法加载指定路径上的editlog文件。

    public long loadEdits(Iterable<EditLogInputStream> editStreams,
                          FSNamesystem target, long maxTxnsToRead,
                          StartupOption startOpt, MetaRecoveryContext recovery)
        throws IOException {
        LOG.debug("About to load edits:\n  " + Joiner.on("\n  ").join(editStreams));
    
        //记录命名空间中加载的最新的事务id
        long prevLastAppliedTxId = lastAppliedTxId;
        long remainingReadTxns = maxTxnsToRead;
        try {    
            // 构造FSEditLogLoader对象用于加栽editlog文件
            FSEditLogLoader loader = new FSEditLogLoader(target, lastAppliedTxId);
    
            // Load latest edits
            // 遍历edit流,加载所有edit
            for (EditLogInputStream editIn : editStreams) {
                LogAction logAction = loadEditLogHelper.record();
                if (logAction.shouldLog()) {
                    String logSuppressed = "";
                    if (logAction.getCount() > 1) {
                        logSuppressed = "; suppressed logging for " +
                            (logAction.getCount() - 1) + " edit reads";
                    }
                    LOG.info("Reading " + editIn + " expecting start txid #" +
                             (lastAppliedTxId + 1) + logSuppressed);
                }
                try {
                    remainingReadTxns -= loader.loadFSEdits(editIn, lastAppliedTxId + 1,
                                                            remainingReadTxns, startOpt, recovery);
                } finally {
                    // Update lastAppliedTxId even in case of error, since some ops may
                    // have been successfully applied before the error.
                    lastAppliedTxId = loader.getLastAppliedTxId();
                }
                // If we are in recovery mode, we may have skipped over some txids.
                if (editIn.getLastTxId() != HdfsServerConstants.INVALID_TXID
                    && recovery != null) {
                    lastAppliedTxId = editIn.getLastTxId();
                }
                if (remainingReadTxns <= 0) {
                    break;
                }
            }
        } finally {
            FSEditLog.closeAllStreams(editStreams);
        }
        return lastAppliedTxId - prevLastAppliedTxId;
    }

    更多相关内容
  • Hadoop FSImage分析器(HFSA) 介绍 Hadoop FSImage分析器(HFSA)通过提供补充 支持对HDFS数据文件以及用户和组目录的概述进行概述(回答“谁拥有多少个/大/小文件...”) 一个用于从派生的用于快速和部分多线程的...
  • Prometheus Hadoop HDFS FSImage导出器 | 将Hadoop HDFS统计信息导出到包括 总数/每个用户/每个组/每个配置的目录路径/每个路径集 目录数 文件数 文件大小和大小分布(可选) 块数 文件复制(总体/每个用户摘要)...
  • 介绍hbase hadoop fsimage损坏处理问题.
  • 所以元数据存储在内存中,但是有一个备份文件fsimage在磁盘。这就防止了断电元数据丢失的问题。 现在内存中的元数据增加之后,需要不断同步fsimage吗?这样又很没有效率。但你又必然要更新元数据,所以,可以将追加...

    namenode的fsimage与edits文件

    引入

    hadoop集群中,namenode管理了元数据。那么,元数据存储在哪里呢?

    如果是磁盘中,必然效率过低,如果是内存中,又会不安全。

    所以元数据存储在内存中,但是有一个备份文件fsimage在磁盘。这就防止了断电元数据丢失的问题。

    现在内存中的元数据增加之后,需要不断同步fsimage吗?这样又很没有效率。但你又必然要更新元数据,所以,可以将追加的操作写入一个文件edits。

    fsimage与edits一起加载到内存中,就会得到所有的元数据。

    合并

    元数据一旦变得庞大,加载到内存就会很慢。所以,还需要定期去合并fsimage与edits。这个合并的工作最好由别人来做。

    一般的集群中,可以由secondaryNameNode完成。

    合并的时机可以是到一定的时间,或者是操作数到达一定阈值。

    由于我使用的是hadoop3.1.3,所以我找了该版本的hdfs-default.xml:

    <property>
      <name>dfs.namenode.checkpoint.period</name>
      <value>3600s</value>
      <description>The number of seconds between two periodic checkpoints. 
    Support multiple time unit suffix(case insensitive), as described in dfs.heartbeat.interval.</description>
    </property>
    
    <property>
      <name>dfs.namenode.checkpoint.txns</name>
      <value>1000000</value>
    <description>The Secondary NameNode or CheckpointNode will create a checkpoint of the namespace every 'dfs.namenode.checkpoint.txns' transactions, 
    regardless of whether 'dfs.namenode.checkpoint.period' has expired.</description>
    </property>
    
    <property>
      <name>dfs.namenode.checkpoint.check.period</name>
      <value>60s</value>
    <description> The SecondaryNameNode and CheckpointNode will poll the NameNode every 'dfs.namenode.checkpoint.check.period' seconds to query the number of uncheckpointed transactions. 
    Support multiple time unit suffix(case insensitive), as described in dfs.heartbeat.interval.</description>
    </property>
    

    默认的检查合并时间是一个小时,或者操作数到达了1000000。查询操作数的时间间隔为一分钟。

    另一方面:

    在这里插入图片描述
    从我的hadoop元数据来看,有些edits的确是1个小时更新的,有些是因为启动关闭的时间不同造成混乱,但是没有一个edits超过1M,这是不是也是一个大小的限制呢?不得而知。如果默认是1M的话也太小了。

    至于HA,那么我们就不需要secondaryNameNode来定期检查和合并了,做这份工作的是standBy namenode:

    Note that, in an HA cluster, the Standby NameNodes also performs checkpoints of the namespace state, and thus it is not necessary to run a Secondary NameNode, CheckpointNode, or BackupNode in an HA cluster. In fact, to do so would be an error. This also allows one who is reconfiguring a non-HA-enabled HDFS cluster to be HA-enabled to reuse the hardware which they had previously dedicated to the Secondary NameNode.

    查看

    我将除了tmp文件之外的所有文件都删了:

    在这里插入图片描述
    最后我们观察几个文件:

    在这里插入图片描述
    首先,当前的最大操作数(事务数)为29325。

    使用

    hdfs oev -p XML -i edits_0000000000000029321-0000000000000029322 -o /opt/module/hadoop-3.1.3/edits.xml
    

    得到edits_0000000000000029321-0000000000000029322的xml文件:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <EDITS>
      <EDITS_VERSION>-64</EDITS_VERSION>
      <RECORD>
        <OPCODE>OP_START_LOG_SEGMENT</OPCODE>
        <DATA>
          <TXID>29321</TXID>
        </DATA>
      </RECORD>
      <RECORD>
        <OPCODE>OP_END_LOG_SEGMENT</OPCODE>
        <DATA>
          <TXID>29322</TXID>
        </DATA>
      </RECORD>
    </EDITS>
    

    这个操作就是开始和结束日志切分。

    以同样的方式查看edits_inprogress_0000000000000029325:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <EDITS>
      <EDITS_VERSION>-64</EDITS_VERSION>
      <RECORD>
        <OPCODE>OP_START_LOG_SEGMENT</OPCODE>
        <DATA>
          <TXID>29325</TXID>
        </DATA>
      </RECORD>
    </EDITS>
    

    同样是日志切分的操作。它的TXID也是最大的操作数ID。secondaryNameNode唯独没有这个文件。可以说,它就是hadoop重启之后新增的操作。

    我们可以通过

     hdfs oiv -p XML -i fsimage_0000000000000029324 -o /opt/module/hadoop-3.1.3/fsimage_new.xml
    

    查看fsimage

    <INodeSection>
    		<lastInodeId>22648</lastInodeId>
    		<numInodes>27</numInodes>
    		<inode>
    			<id>16385</id>
    			<type>DIRECTORY</type>
    			<name/>
    			<mtime>1629345954854</mtime>
    			<permission>root:supergroup:0777</permission>
    			<nsquota>9223372036854775807</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22554</id>
    			<type>DIRECTORY</type>
    			<name>tmp</name>
    			<mtime>1629289108967</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22555</id>
    			<type>DIRECTORY</type>
    			<name>hadoop-yarn</name>
    			<mtime>1629280562345</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22556</id>
    			<type>DIRECTORY</type>
    			<name>staging</name>
    			<mtime>1629289107248</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22557</id>
    			<type>DIRECTORY</type>
    			<name>history</name>
    			<mtime>1629280562367</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22558</id>
    			<type>DIRECTORY</type>
    			<name>done</name>
    			<mtime>1629289269870</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22559</id>
    			<type>DIRECTORY</type>
    			<name>done_intermediate</name>
    			<mtime>1629289110600</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22573</id>
    			<type>DIRECTORY</type>
    			<name>ocean</name>
    			<mtime>1629289107248</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22574</id>
    			<type>DIRECTORY</type>
    			<name>.staging</name>
    			<mtime>1629290164292</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22580</id>
    			<type>DIRECTORY</type>
    			<name>logs</name>
    			<mtime>1629289108990</mtime>
    			<permission>ocean:ocean:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22581</id>
    			<type>DIRECTORY</type>
    			<name>ocean</name>
    			<mtime>1629289108992</mtime>
    			<permission>ocean:ocean:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22582</id>
    			<type>DIRECTORY</type>
    			<name>logs-tfile</name>
    			<mtime>1629290147310</mtime>
    			<permission>ocean:ocean:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22583</id>
    			<type>DIRECTORY</type>
    			<name>application_1629280557686_0001</name>
    			<mtime>1629289238916</mtime>
    			<permission>ocean:ocean:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22584</id>
    			<type>DIRECTORY</type>
    			<name>ocean</name>
    			<mtime>1629290169798</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22608</id>
    			<type>FILE</type>
    			<name>job_1629280557686_0001-1629289108111-ocean-hadoop%2Dmapreduce%2Dclient%2Djobclient%2D3.1.3%2D-1629289231294-10-1-SUCCEEDED-default-1629289112345.jhist</name>
    			<replication>3</replication>
    			<mtime>1629289231358</mtime>
    			<atime>1629289231340</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:supergroup:0777</permission>
    			<blocks>
    				<block>
    					<id>1073744990</id>
    					<genstamp>4174</genstamp>
    					<numBytes>55176</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22609</id>
    			<type>FILE</type>
    			<name>job_1629280557686_0001_conf.xml</name>
    			<replication>3</replication>
    			<mtime>1629289231386</mtime>
    			<atime>1629289231366</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:supergroup:0777</permission>
    			<blocks>
    				<block>
    					<id>1073744991</id>
    					<genstamp>4175</genstamp>
    					<numBytes>216419</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22610</id>
    			<type>FILE</type>
    			<name>hadoop102_35246</name>
    			<replication>3</replication>
    			<mtime>1629289238515</mtime>
    			<atime>1629289238458</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:ocean:0777</permission>
    			<blocks>
    				<block>
    					<id>1073744992</id>
    					<genstamp>4176</genstamp>
    					<numBytes>172209</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22611</id>
    			<type>FILE</type>
    			<name>hadoop103_33694</name>
    			<replication>3</replication>
    			<mtime>1629289238912</mtime>
    			<atime>1629289238844</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:ocean:0777</permission>
    			<blocks>
    				<block>
    					<id>1073744993</id>
    					<genstamp>4177</genstamp>
    					<numBytes>352999</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22612</id>
    			<type>DIRECTORY</type>
    			<name>2021</name>
    			<mtime>1629289269870</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22613</id>
    			<type>DIRECTORY</type>
    			<name>08</name>
    			<mtime>1629289269870</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22614</id>
    			<type>DIRECTORY</type>
    			<name>18</name>
    			<mtime>1629289269870</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22615</id>
    			<type>DIRECTORY</type>
    			<name>000000</name>
    			<mtime>1629290169798</mtime>
    			<permission>ocean:supergroup:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22632</id>
    			<type>DIRECTORY</type>
    			<name>application_1629280557686_0002</name>
    			<mtime>1629290170700</mtime>
    			<permission>ocean:ocean:0777</permission>
    			<nsquota>-1</nsquota>
    			<dsquota>-1</dsquota>
    		</inode>
    		<inode>
    			<id>22645</id>
    			<type>FILE</type>
    			<name>job_1629280557686_0002-1629290147211-ocean-hadoop%2Dmapreduce%2Dclient%2Djobclient%2D3.1.3%2D-1629290163180-10-1-SUCCEEDED-default-1629290150116.jhist</name>
    			<replication>3</replication>
    			<mtime>1629290163238</mtime>
    			<atime>1629290163222</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:supergroup:0777</permission>
    			<blocks>
    				<block>
    					<id>1073745012</id>
    					<genstamp>4196</genstamp>
    					<numBytes>54938</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22646</id>
    			<type>FILE</type>
    			<name>job_1629280557686_0002_conf.xml</name>
    			<replication>3</replication>
    			<mtime>1629290163262</mtime>
    			<atime>1629290163244</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:supergroup:0777</permission>
    			<blocks>
    				<block>
    					<id>1073745013</id>
    					<genstamp>4197</genstamp>
    					<numBytes>216417</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22647</id>
    			<type>FILE</type>
    			<name>hadoop103_33694</name>
    			<replication>3</replication>
    			<mtime>1629290170343</mtime>
    			<atime>1629290170309</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:ocean:0777</permission>
    			<blocks>
    				<block>
    					<id>1073745014</id>
    					<genstamp>4198</genstamp>
    					<numBytes>203114</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    		<inode>
    			<id>22648</id>
    			<type>FILE</type>
    			<name>hadoop102_35246</name>
    			<replication>3</replication>
    			<mtime>1629290170698</mtime>
    			<atime>1629290170675</atime>
    			<preferredBlockSize>134217728</preferredBlockSize>
    			<permission>ocean:ocean:0777</permission>
    			<blocks>
    				<block>
    					<id>1073745015</id>
    					<genstamp>4199</genstamp>
    					<numBytes>281375</numBytes>
    				</block>
    			</blocks>
    			<storagePolicyId>0</storagePolicyId>
    		</inode>
    	</INodeSection>
    

    除了核心文件,已经没有自己建文件或文件夹了。

    同样的我们可以查看另个一个fsimage

    注意到两个对应的数字(在我写博文的途中又发生了合并,所以数字与前面的截图不同了):

    在这里插入图片描述

    这相当于是存了最后的两个版本。那为什么会有两个fsimage呢?

    <property>
      <name>dfs.namenode.num.checkpoints.retained</name>
      <value>2</value>
    <description> The number of image checkpoint files (fsimage_*) that will be retained by the NameNode and Secondary NameNode in their storage directories. 
    All edit logs (stored on edits_* files) necessary to recover an up-to-date namespace from the oldest retained checkpoint will also be retained.</description>
    </property>
    

    默认就是2。

    展开全文
  • HDFS:Edits和Fsimage详解与合并流程 NameNode如何管理和存储元数据 计算机中存储数据有两种:内存或磁盘 元数据存储磁盘: 存储磁盘无法面对客户端对元数据信息的任意的快速低延迟的响应,但是安全性高 元数据存储...

    HDFS:Edits和Fsimage详解与合并流程

    NameNode如何管理和存储元数据

    计算机中存储数据有两种:内存或磁盘
    元数据存储磁盘: 存储磁盘无法面对客户端对元数据信息的任意的快速低延迟的响应,但是安全性高
    元数据存储内存:元数据存放内存,可以高效的查询以及快速响应客户端的查询请求,数据保存在内存,如果断电,内存中的数据全部丢失

    因此,考虑上述两种存储方式的优缺点,HDFS采用了内存+磁盘的形式来管理元数据。即: NameNode(内存)+FsImage文件. 其中,NameNode文件维护了文件与数据块的映射表以及数据块与数据节点的映射表,比如,一个文件它被切分成了几个数据块,这些数据块分别存储在哪些datanode节点上。而Fsimage保存在磁盘上,为某一时刻下内存中元数据在本地磁盘的映射。就是在该时刻下,内存中元数据记录的所有文件块和目录,分别的状态,位于哪些datanode,各自的权限,各自的副本个数等 (可以通过查看Fsimage保存的内容可以看到上述信息)。因此,利用内存+磁盘的方式,内存中的元数据可以快速响应客户端的用户请求,而映射到磁盘中的元数据Fsimage可以实现安全性,防止数据的丢失。

    而接下来的一个新问题是:磁盘和内存中的元数据如何进行划分。
    即两个数据一摸一样,还是两个数据合并到一起才是一份完整的数据呢?

    • 一模一样:client如果对元数据进行增删改操作,需要保证两个数据的一致。FsImage文件操作起来效率也不高,因为FsImage存储在磁盘中,对磁盘中的内容进行写入势必会增加很多的IO操作,也要占用CPU。此时,相当于每一次增删改操作,都需要对两个文件同时进行修改。
    • 两个合并=完整数据:由于如果要将操作写入磁盘会降低运行效率。所以想法就是对于增删改操作,只有内存中的元数据进行响应,而不直接进行磁盘IO读写。此时,为了保证两个数据的一致性,NameNode就引入了以一个edits文件,该日志文件只能进行追加写入,以此来记录client的每次增删改操作。虽然此时仍然有IO流的操作,但是相比于每次将元数据内容写入Fsimage,edits日志文件的写入内容更少,效率更高。

    至此我们知道了HDFS元数据管理机制采用了内存+磁盘的形式,内存中的NameNode来快速响应客户端的查询请求,磁盘中的元数据作为备份,防止数据的丢失。同时,为了保证内存和磁盘中元数据的一致,hdfs采用了呀一个edits的日志文件,该文件记录了客户端对元数据的操作。利用edits+Fsimage的形式,就完成了对元数据的管理和存储。下面就来对这两个部分进行深入介绍,即它们是如何合作来实现管理和存储的。

    FsImage

    • FsImage: 是namenode中关于元数据的镜像,一般称为检查点(checkpoing),这里包含了HDFS文件系统所有目录以及文件相关信息(Block数量,副本数量,权限等信息)

    在机器学习或这深度学习模型训练的过程中,为了防止异常中断,所以都会设定在某一具体时刻或者效果达到最好结果时,将模型参数都保存下来。这样,后续就可以在异常中断后利用来文件直接对模型的参数进行初始化赋值,而不用再重新进行训练。在模型训练中保存的文件也叫作checkpoint。因此这里HDFS的检查点checkpoint也可以同样来理解。即FsImage保存了某一时刻下元数据内的所有信息,这样,当HDFS异常中断或者程序启动时,就可以利用该检查点文件,来对元数据进行初始化,以此还原到异常中断或程序停止前的最新状态。

    Edits文件

    • 存储了客户端对HDFS文件系统所有的更新操作记录,Client对HDFS文件系统所有的更新操作都会被记录到Edits文件中(不包括查询操作)

    最开始提到,Client对HDFS的更新操作会更新内存中的元数据信息,而不会直接写入到FsImage文件中。那么为了保证内存和磁盘中元数据信息的一致性,就利用了edits文件来记录下所有的更新操作。edits文件记录的就是当原FsImage被载入内存后,Client又对元数据进行了哪些操作。 这样,只要在原FsImage中执行这些操作,对保存的元数据信息进行更新,就可以使得内存和磁盘汇总的元数据信息一致。通过此方法,即解决了为了保证一致性,要对FsImage直接进行写入的过程。这也是引入edits文件的关键原因。

    Fsimage与Edits的合并

    介绍完FsImage和Edits文件的概念和各自的作用之后,就方便后续对两者进行合并过程的理解。
    因为edits文件是一个只能追加写入的文件,在程序运行过程中会不断的记录客户端的更新操作。同时,根据上面对Edits中的介绍可知,引入载入的FsImage的元数据内容不是最新状态,所以只有在FsImage的内容上,执行edits文件中的更新操作,才能将FsImage的元数据更新为最新状态。此时,假设edits不断的进行追加写,当某一时刻需要NameNode重启时,此时NameNode会先将FsImage里面的内容映射到内存中,即相当于对元数据进行初始化,同时为了恢复到最新的状态,还需要在一条一条的执行edits中的记录。当edits文件非常大时,会导致NameNode启动过程非常慢,而在这段时间HDFS系统会处于安全模式,即保证了要将元数据恢复到最新后才能接收客户端请求。这显然是不符合用户要求的。因此,就需要设计能不能在NameNode运行的时候使得edits文件小一些,这样就能使启动过程加快。所以就要引入FsImage和Edits的合并过程。

    首先给出合并过程的执行流程图。接下来对每个流程部分进行详细介绍。

    Namenode & SecondaryNamenode & Checkpoint 工作机制

    首先可以看出,除了NameNode节点外,为了进行合并,还引入了另一个SecondaryNameNode节点。SecondaryNanoe是HDFS架构中的一个组成部分,它是用来保存namenode中对HDFS metadata的信息的备份而设定的。一般都是将SecondaryNamenode单独运行在一台机器上。

    详细流程介绍如下:
    首先黑色字体代表的流程表示NameNode自身启动和运行时的流程,下面每个序号对应图中流程。
    1)程序启动时,需要对元数据进行恢复。根据上述对FsImage和edits文件的已经知道,HDFS首先将Fsimage读入内存对元数据进行恢复,然后再读edits文件中的更新操作在恢复后的元数据上进行执行,使得此时的NameNode中保存的是停止前的最新状态。
    2、3)当有客户端执行增删改查操作时,HDFS会记录其中的增删改操作到edits文件中,这样就避免了直接对Fsimage文件的IO操作。
    4) 内存中保存的元数据执行客户端的增删改查操作。(FsImage在此阶段是不改变的)

    粉色字体代表的流程表示SecondaryNameNode对文件的合并流程,下面每个序号对应图中流程。
    1)SecondaryNameNode向NameNode发送请求,询问是否需要进行合并。在Hadoop中通过两个维度来控制是否需要Checkpoint,如图中所示:1) 到定时时间 2) Edits文件是否满或超过了设定的大小范围。
    2)如果满足上面的触发条件,则开始下述执行合并流程。
    3、4)由于NameNode需要将此时的edits文件和FsImage文件发送到SecondaryNamenode,所以在NameNode节点上需要停止使用该edits文件,暂时将新的写操作写到一个新的文件比如edits_inprogress_002中,而将原先的edits_inprogress_001重命名为esits_001进行发送。这样,有inprogress标识的edits表示最新正在写入更新操作的文件,而没有该标识,且后面数字最大的edits文件,即表示最后一个已经合并的文件。比如在实际的文件夹下会生成以下文件:
    在这里插入图片描述

    各文件名的含义即如上所述。

    5)SecondaryNamenode通过HTTP GET方式从NameNode上获取到fsimage和edits文件,并下载到本地的相应目录下。然后,SecondaryNamenode将下载下来的fsimage载入内存,然后一条一条地执行edits文件中的各项更新操作,使得内存中的fsimage保存最新;这个过程就是edits和fsimage文件的合并。(是不是跟我们上面说的在NameNode启动时的载入过程很像。HDFS就是利用了另一台机器的资源来对FsImage进行更新,这样NameNode所在节点的资源就只专注响应对客户端的操作)。

    6)经过合并阶段之后,FsImage的内容即进行了更新,此时并不与NameNode中的元数据内容一致,相差的仍然是edits_inprogress_002中写入的更新操作。

    7、8)SecondaryNameNode会通过post方式将新的FsImage文件发送到NameNode节点上。NameNode将接收到的新的fsimage替换旧的fsimage文件,同时将edit_inprogress_002文件来记录合并后续的更新操作。通过这个过程,edits就变小了。

    总结

    通过上面的描述可以总结几点。

    1. 为了解决元数据备份的问题,HDFS采用了edits+FsImage的策略,edits文件保存了客户端的更新操作,Fsimage保存了元数据的具体内容。HDFS在运行时将客户端的操作响应在内容中的元数据上,同时将所有的更新操作写入edits文件,避免了直接对FsImage进行操作造成效率降低
    2. HDFS为了解决edits不断追加写入过大的问题,采用了edits与FsImage合并的策略。即edits文件不是一直在原文件中进行写入的,而是在一定时间或者条件后,就把该时间段内客户端的更新操作同步到FsImage文件中,来更新FsImage文件的内容。这样,新的edits文件就可以只记录合并之后的更新操作,从而减小了edits文件的大小。
    3. HDFS通过引入SecondaryNameNode来实现上述过程,利用了一台独立的机器资源来处理合并流程。所谓edits与FsImage的合并,其实就是在SecondaryNamenode内存中,将edits中记录一条一条的在fsiamge中执行,来更新Fsiamge的内容。同时,可以知道,SecondaryNameNode获取到的FsImage不是最新的,因为在它从NameNode下载edits和FsImage文件的时候,新的更新操作已经写到新的edits文件里去了(比如这里的edits_inprogress_002)。而这些更新在SecondaryNamenode是没有同步到的。
    4. edits和FsImage利用了文件命名格式来标识各自的最新文件。如fsiamge和edits文件图中所示,edits文件以edits_开头,后面跟一个txid范围端,并且多个edit log之间首尾相连,正在使用的edits文件名字为edits_inprogress_txid。该路径下还会保存两个FsImage文件(dfs.namenode.num.checkpoints.retained在namenode上保存的fsimage个数的默认配置,超过的被删除,默认保存两个)。文件格式为fsimage_txid,txid与edits中的txid对应,表示该fsimage加载的是哪一个edits。比如从图中可以看出,此时的fsimage已经加载到尾数为1545的edits文件内容

    其它补充

    实际查看FsImage文件的内容时,可以发现Fsimage中是没有记录块所对应DataNode的。比如下图所示:
    在这里插入图片描述
    在内存元数据中是有记录块所对应的datanode信息,但是fsimage中就剔除了这个信息;HDFS集群在启动的时候会加载image以及edits文件,block对应的dn信息都没有记录,集群启动时会有一个安全模式(safemode),安全模式就是为了让datanode汇报自己当前所持有的block信息给nn来补全元数据。后续每隔一段时间datanode都要汇报自己持有的block信息。因为即使fsimage中记录了datanode信息,但是在恢复元数据的过程中,可能某些datanode节点出现了问题。所以,其实无论FsImage中是否对block的dn信息进行了记录,恢复的时候都是需要dn来汇报自己持有的block信息,这样才是最真实和安全的。

    展开全文
  • 文章目录前言NameNode fsimage corrupt场景NameNode fsimage corrupt解决办法NN fsimage corrupt的重新行为参考链接 前言 在如今很多用户使用HDFS做为大数据的底层存储时,我们除了关心HDFS的处理性能外,我们经常...

    前言


    在如今很多用户使用HDFS做为大数据的底层存储时,我们除了关心HDFS的处理性能外,我们经常还需要关注其中数据高可用的情况,例如不能出现数据损坏的情况,比如missing block,或者文件block corrupt的情况。但是其中我们忽略掉了一种最为极端同时也是最为棘手的情况:HDFS NameNode fsimage文件坏了,我们怎么办?元数据的损坏比实际数据损坏可是要严重地多得多。在HDFS中,NN fsimage损坏直接会导致NN启动不起来。目前NN fsimage文件损坏的情况只在极端个别操作行为下产生,所以大部分的使用场景是不会导致fsimage corrupt的情况。但是这不意味着这种极端情况发生的可能性。作为HDFS的集群维护者来说,我们有必要了解以及对此情况的解决方案。本文笔者来分享分享这块的一些经验心得。

    NameNode fsimage corrupt场景


    首先,本文所说的fsimage的corrupt原因不是说是物理硬件层面造成的fsimage损坏,而是HDFS内部自身程序运行所产生的corrupt的fsimage。你可以简单理解为NN因为内部元数据的损坏,导致checkpoint出了一个corrupt的fsimage文件。归结源头来说还是NN自身进行请求操作处理时,对内存里的元数据更新的错误处理。

    上面小节已经提到,目前NN fsimage损坏的情况在绝大部分读写操作中不会触发到,只在极个别情况下的case。这里面的case主要集中在用户启用了HDFS snapshot功能下的情况,其中概括起来目前有3类(据笔者的目前接触了解的情况):

    • 目录里的子文件列表里包含了系统内不存在的文件的情况
    • INode引用指向了一个不存在的INode对象
    • 重复的snapshot deleted diff列表

    这3类情况可详见社区JIRA:HDFS-13314和HDFS-13813。在了解完HDFS NN fsimage文件发生corrupt的场景后,下面我们来正式聊聊NN fsimage corrupt的解决办法。

    NameNode fsimage corrupt解决办法


    NN fsimage文件发生corrupt后,我们有两个解决的思路:

    1)第一种,直接修复当前损坏的fsimage文件。
    2)第二种,寻找到损坏fsimage文件之前的一个好的fsimage文件+对应的editlog,然后进行NN重启,随后在内存里进行修复。

    这里我们详细来聊聊上面提到的两个方案。

    方案一,直接修复损坏的fsimage文件。这个方案看似十分的直接,但是倘若我们想做到完美精准地修复,其实并不容易。

    比如说我们遇到了目录内包含系统内non-exist文件的情况,这个时候NN在启动load目录信息的时候会报如下的NPE错误。

    2020-12-19 22:09:05,282 INFO  namenode.FSImage (FSImage.java:loadFSImageFile(731)) - Planning to load image: FSImageFile(file=/xxx/fsimage_0000000000000000024, cpktTxId=0000000000000000024)
    2020-12-19 22:09:05,283 INFO  namenode.FSImageFormatPBINode (FSImageFormatPBINode.java:loadINodeSection(257)) - Loading 6 INodes.
    2020-12-19 22:09:05,283 ERROR namenode.FSImage (FSImage.java:loadFSImage(679)) - Failed to load image from FSImageFile(file=/xxx/fsimage_0000000000000000024, cpktTxId=0000000000000000024)
    java.lang.NullPointerException
      at org.apache.hadoop.hdfs.server.namenode.INodeDirectory.addChild(INodeDirectory.java:550)
      at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.addToParent(FSImageFormatPBINode.java:303)
      at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.loadINodeDirectorySection(FSImageFormatPBINode.java:245)
      at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.loadInternal(FSImageFormatProtobuf.java:263)
      at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.load(FSImageFormatProtobuf.java:182)
      at org.apache.hadoop.hdfs.server.namenode.FSImageFormat$LoaderDelegator.load(FSImageFormat.java:226)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:884)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:868)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImageFile(FSImage.java:741)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:672)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:289)
      at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.loadFSImage(FSNamesystem.java:1152)
      at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.loadFromDisk(FSNamesystem.java:761)
      at org.apache.hadoop.hdfs.server.namenode.NameNode.loadNamesystem(NameNode.java:722)
      at org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:786)
      at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:1026)
      at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:1010)
    

    这个时候如果简单的去catch这个NPE异常,并非是绝对准确的方式。因为这里面如果还涉及到snapshot的引用持有,在别的地方还是有可能抛出异常的。因此这里并不建议直接去catch load fsimage时发生的异常来进行修复的办法。

    接着我们来看方案二,通过一个好的fsimage+editlog的方式进行恢复。这个方案的一个优势在于它至少能够让NN正常启动起来了。但是等NN启动完毕,加载掉后续的editlog后,它此时内存的状态还是corrupt的,它又会checkpoint出一个坏的fsimage文件。

    这个时候呢,我们就要想办法去修复掉那些“坏掉”的元数据信息,一种方式就是找到它们并且删除掉它们。目前社区有对这块的改进,可以帮助我们检测并打印出这些corrupt文件目录的信息,相关JIRA也同样是上面提到的HDFS-13314和HDFS-13813两个JIRA。

    笔者将上述JIRA patch改进apply后,同样执行NN fsimage corrupt的场景后,此时从NN log里能够找到corrupt相关的文件信息了,如下:

    2020-12-19 22:27:21,831 INFO  namenode.FileJournalManager (FileJournalManager.java:finalizeLogSegment(142)) - Finalizing edits file /xxx/edits_inprogress_0000000000000000001 -> /xxx/edits_0000000000000000001-0000000000000000024
    2020-12-19 22:27:21,832 INFO  namenode.FSImageFormatProtobuf (FSImageFormatProtobuf.java:save(417)) - Saving image file /xxx/fsimage.ckpt_0000000000000000024 using no compression
    2020-12-19 22:27:21,832 INFO  namenode.FSImageFormatProtobuf (FSImageFormatProtobuf.java:save(417)) - Saving image file /xxx/fsimage.ckpt_0000000000000000024 using no compression
    2020-12-19 22:27:21,838 ERROR namenode.FSImage (FSImageFormatPBINode.java:serializeINodeDirectorySection(556)) - FSImageFormatPBINode#serializeINodeDirectorySection: Dangling child pointer found. Missing INode in inodeMap: id=16391; parent dir path=dirb; path=file1; parent=null
    2020-12-19 22:27:21,838 ERROR namenode.FSImage (FSImageFormatPBINode.java:serializeINodeDirectorySection(556)) - FSImageFormatPBINode#serializeINodeDirectorySection: Dangling child pointer found. Missing INode in inodeMap: id=16391; parent dir path=dirb; path=file1; parent=null
    2020-12-19 22:27:21,861 INFO  namenode.FSImageFormatProtobuf (FSImageFormatProtobuf.java:save(421)) - Image file /xxx/fsimage.ckpt_0000000000000000024 of size 963 bytes saved in 0 seconds  with 1 errors.
    2020-12-19 22:27:21,861 ERROR namenode.FSImage (FSImage.java:saveFSImage(931)) - Detected 1 errors while saving FsImage /xxx/fsimage_0000000000000000024
    2020-12-19 22:27:21,861 INFO  namenode.FSImageFormatProtobuf (FSImageFormatProtobuf.java:save(421)) - Image file /xxx/fsimage.ckpt_0000000000000000024 of size 963 bytes saved in 0 seconds  with 1 errors.
    2020-12-19 22:27:21,861 ERROR namenode.FSImage (FSImage.java:saveFSImage(931)) - Detected 1 errors while saving FsImage /xxx/fsimage_0000000000000000024
    2020-12-19 22:27:21,863 INFO  namenode.FSEditLog (FSEditLog.java:startLogSegment(1299)) - Starting log segment at 25
    2020-12-19 22:27:21,866 FATAL namenode.FSImage (FSImage.java:saveNamespace(1074)) - NameNode process will exit now... The saved FsImage IMAGE is potentially corrupted.
    2020-12-19 22:27:21,866 INFO  util.ExitUtil (ExitUtil.java:terminate(124)) - Exiting with status -1
    2020-12-19 22:27:21,866 FATAL util.ExitUtil (ExitUtil.java:terminate(127)) - Terminate called
    org.apache.hadoop.util.ExitUtil$ExitException: ExitException
      at org.apache.hadoop.util.ExitUtil.terminate(ExitUtil.java:126)
      at org.apache.hadoop.util.ExitUtil.terminate(ExitUtil.java:192)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.saveNamespace(FSImage.java:1076)
      at org.apache.hadoop.hdfs.server.namenode.FSImage.saveNamespace(FSImage.java:1028)
      at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.saveNamespace(FSNamesystem.java:5490)
      at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.saveNamespace(NameNodeRpcServer.java:1220)
      at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.saveNamespace(ClientNamenodeProtocolServerSideTranslatorPB.java:774)
      at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
      at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:639)
      at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:982)
      at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2412)
      at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:2408)
      at java.security.AccessController.doPrivileged(Native Method)
      at javax.security.auth.Subject.doAs(Subject.java:422)
      at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1869)
      at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2408)
    2020-12-19 22:27:21,871 INFO  hdfs.MiniDFSCluster (MiniDFSCluster.java:shutdown(1791)) - Shutting down the Mini HDFS Cluster
    

    上述的改动源自HDFS-13314,它让NN在每次checkpoint时进行INode信息的corrupt检查,如果发现有损坏的情况,则将这些信息打印出来,同时shutdown当前的NN,以此让管理员人工介入检查。上面显示的这行即是corrupt的信息:

    2020-12-19 22:27:21,838 ERROR namenode.FSImage (FSImageFormatPBINode.java:serializeINodeDirectorySection(556)) - FSImageFormatPBINode#serializeINodeDirectorySection: Dangling child pointer found. Missing INode in inodeMap: id=16391; parent dir path=dirb; path=file1; parent=null
    

    OK,在我们了解了那些损坏了的元数据信息后,我们可以在启动后的NN里进行对应数据的删除操作。删除好后,我们再观察checkpoint新fsimage后,还是否有上述corrupt信息的出现。在笔者的测试case里,删除掉损坏文件信息的方法是能够修复的。当然还有另外一种办法进行corrupt的信息的移除,通过hard-code部分corrupt path来skip这些元数据信息的load。这个办法也是社区JIRA里提到的一种修复办法。

    在这里最坏的一种情况是,我们找到了这些corrupt的元数据信息,但是我们没办法从NN内存里移除掉。这个时候,我们就不得不作出部分数据丢失的一种修复办法;

    1)找到发生corrupt行为对应的editlog文件位置
    2)移除掉1)步骤中的editlog及之后的所有editlog
    3)进行好的fsimage以及剩余editlog的加载

    上面NN启动过程就能确保NN启动完毕是一个正确的元数据状态了,但是它会丢失一部分transaction的更新,对应的用户影响即最近数据的更新写入都将丢失。当然,这种有损恢复的方式只是在迫不得已的情况下使用而已,正常情况还是应尽可能地通过上面提到的方案二的方式来做比较好。文末小节会简单示范一个NN transaction回退的步骤操作。

    NN fsimage corrupt的重现行为


    这里笔者提供一个社区发现的一个能够造成fsimage corrupt的重现操作,(仅供测试使用!!!)

    /apache/hadoop/bin/hadoop fs -mkdir -p /dir1/dira/dirb
    /apache/hadoop/bin/hadoop fs -mkdir -p /dir1/dirx
    /apache/hadoop/bin/hadoop fs -mkdir -p /dir2
    
    /apache/hadoop/bin/hdfs  dfsadmin -allowSnapshot /dir1
    /apache/hadoop/bin/hdfs dfs -createSnapshot /dir1 s0
    /apache/hadoop/bin/hadoop fs -put hdfs-site.xml /dir1/dira/dirb/
    /apache/hadoop/bin/hadoop fs -mv /dir1/dira/dirb /dir1/dirx/dirb
    
    /apache/hadoop/bin/hdfs dfs -createSnapshot /dir1 s1
    /apache/hadoop/bin/hadoop fs -appendToFile hdfs-site.xml /dir1/dirx/dirb/hdfs-site.xml
    /apache/hadoop/bin/hadoop fs -mkdir /dir2/dira
    /apache/hadoop/bin/hadoop fs -mv /dir1/dirx/dirb /dir2/dira/dirb
    
    /apache/hadoop/bin/hadoop fs -rm -r /dir2/dira/dirb
    /apache/hadoop/bin/hadoop fs -rm -r /user/hdfs/.Trash/Current
    /apache/hadoop/bin/hdfs dfs -deleteSnapshot /dir1 s1
    

    这个corrupt的case fix起来很简单,拿之前好的fsimage文件,load启动成功后,执行删除snapshot s0的操作即可:

    /apache/hadoop/bin/hdfs dfs -deleteSnapshot /dir1 s0
    

    有的时候我们为了测试的方便,可以拿fsimage文件直接在一个测试的节点启动进行测试,无须任何的JN,DN节点,笔者的测试做法如下:

    1)启动时将NN HA2个地址中的另外一个随便写出一个假的地址,防止NN启动解析保错。
    2)测试NN启动好后,将上面HA NN地址都写出本地地址,避免hdfs命令执行发现不存在的NN导致执行失败。

    NameNode的transaction状态回退


    有的时候当我们真的发现只能通过回退某些transaction,才能使得NN能够正常启动并服务。这个时候我们应该怎么做呢?

    这里给出一个简单的例子,我们现在有最新的transaction到txid为374911,然后我们发现其中edits_0000000000000374905-0000000000000374910的transaction有问题,想让NN的状态回退至374904(edits_0000000000000374903-0000000000000374904)对应的状态。

    -rw-r--r-- 1 hdfs hdfs 8 Dec 28 23:01 committed-txid
    -rw-r--r-- 1 hdfs hdfs 42 Dec 28 22:57 edits_0000000000000374901-0000000000000374902
    -rw-r--r-- 1 hdfs hdfs 42 Dec 28 22:59 edits_0000000000000374903-0000000000000374904
    -rw-r--r-- 1 hdfs hdfs 385 Dec 28 23:01 edits_0000000000000374905-0000000000000374910
    -rw-r--r-- 1 hdfs hdfs 1048576 Dec 28 23:01 edits_inprogress_0000000000000374911
    

    下面是具体的步骤操作:
    1)执行命令使得Active/Standby NN进入safemode,以此避免新的transaction发生。

    $HADOOP_HOME/bin/hdfs dfsadmin -safemode enter
    

    2)重命名需要丢弃的editlog(包括edit_in_progress)文件到bak文件。

    登录Active NN,执行如下命令

    cd $NN_METADATA_DIR/current
    mv edits_inprogress_0000000000000374911 edits_inprogress_0000000000000374911_bak
    mv edits_0000000000000374905-0000000000000374910 edits_0000000000000374905-0000000000000374910_bak
    

    登录每个JN节点,我们需要额外移除掉committed txid文件,以此避免editlog的恢复行为。

    cd $JN_METADATA_DIR/current
    mv edits_inprogress_0000000000000374911 edits_inprogress_0000000000000374911_bak
    mv edits_0000000000000374905-0000000000000374910 edits_0000000000000374905-0000000000000374910_bak
    mv committed-txid committed-txid_bak
    
    # restart journalnode
    $HADOOP_HOME/sbin/hadoop-daemon.sh stop journalnode
    $HADOOP_HOME/sbin/hadoop-daemon.sh start journalnode
    

    新的editlog文件将会如下所示:

    -rw-r--r-- 1 hdfs hdfs 8 Dec 28 23:01 committed-txid_bak
    ...
    -rw-r--r-- 1 hdfs hdfs 42 Dec 28 22:57 edits_0000000000000374901-0000000000000374902
    -rw-r--r-- 1 hdfs hdfs 42 Dec 28 22:59 edits_0000000000000374903-0000000000000374904
    -rw-r--r-- 1 hdfs hdfs 385 Dec 28 23:01 edits_0000000000000374905-0000000000000374910_bak
    -rw-r--r-- 1 hdfs hdfs 1048576 Dec 28 23:01 edits_inprogress_0000000000000374911_bak
    

    3)重启Standby NN并使其进入safemode模式。此时NN的最新状态txid已被回退。

    # restart standby namenode
    $HADOOP_HOME/sbin/hadoop-daemon.sh stop namenode
    $HADOOP_HOME/sbin/hadoop-daemon.sh start namenode
    # 等待Standby NN退出安全模式后,再执行命令使其进入安全模式
    $HADOOP_HOME/bin/hdfs dfsadmin -safemode enter
    

    4)重启Active NN,此时会触发NN的failover行为。

    # restart active namenode
    $HADOOP_HOME/sbin/hadoop-daemon.sh stop namenode
    $HADOOP_HOME/sbin/hadoop-daemon.sh start namenode
    

    5) 检查Active/Standby NN是否都已经回退至预期的transaction id,如果是的话,执行离开safemode命令。

    $HADOOP_HOME/bin/hdfs dfsadmin -safemode leave
    

    上述的步骤都是在safemode的保护下进行的,因此能够保证一个干净的回退行为。

    以上就是本文所要阐述的关于HDFS NN fsimage corrupt恢复的相关内容了,在参考链接里笔者还附带上了几个关于fsimage corrupt相关的JIRA链接,感兴趣的同学可继续深入了解。

    参考链接


    [1].https://issues.apache.org/jira/browse/HDFS-9406
    [2].https://issues.apache.org/jira/browse/HDFS-13314
    [3].https://issues.apache.org/jira/browse/HDFS-13813
    [4].https://issues.apache.org/jira/browse/HDFS-13101
    [5].https://issues.apache.org/jira/browse/HDFS-15313
    [6].https://issues.apache.org/jira/browse/HDFS-15012

    展开全文
  • 加载数据到hive表 $ hdfs dfs -put /data/fsimage.csv /user/hive/warehouse/fsimage_info_csv/ 查看文件大小分布 根据fsimage文件查看一下文件大小的分布,如下: $ hdfs oiv -p FileDistribution -i fsimage_...
  • FSImage并行加载以及对HDFS OIV的影响

    千次阅读 2022-04-02 16:03:33
    FSImage并行加载 关于NameNode FSImage并行加载现在有很多文章都详细介绍过,比如:https://blog.csdn.net/Androidlushangderen/article/details/100088073,所以这里就不大篇幅说了 简单总结就是开启该功能后,...
  • FSImage处理检查点和命名空间 edits 的日志记录。
  • fsimage文件与editslog文件存储的路径  查看hdfs-site.xml属性: fsimage、editslog文件存储:(可多目录) dfs.namenode.name.dir /opt/data/name blk数据块文件:(可多目录) dfs.datanode.data.dir /opt/...
  • Fsimage和Edits概述及其作用

    千次阅读 2020-10-19 21:14:36
    文章目录NameNode元数据解析 1.预备知识2.元数据 目标:掌握Fsumage和Edits的作用 NameNode元数据解析 1.预备知识 元数据(Metadata),又称中介数据、中继...如果不是第一次启动,直接加载edits和fsimage文件到内存
  • Fsimage和Edits解析

    千次阅读 2020-08-16 16:20:29
    参考尚硅谷hadoop 教程 ... 概念 NameNode被格式化之后,将在/opt/module/hadoop-2.7.2/data/tmp/dfs/name/current...(1)Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和.
  • 加载数据到hive表 $ hdfs dfs -put /data/fsimage.csv /user/hive/warehouse/fsimage_info_csv/ 查看文件大小分布 根据fsimage文件查看一下文件大小的分布,如下: $ hdfs oiv -p FileDistribution -i fsimage_...
  • Hadoop基础-镜像文件(fsimage)和编辑日志(edits)作者:尹正杰版权声明:原创作品,谢绝转载!否则将追究法律责任。一.查看日志镜像文件(如:fsimage_0000000000000000767)内容1>.镜像文件的作用通过查看上面的XML...
  • hadoop fsimage edits关系

    2021-02-01 03:48:58
    fsimage = name table , 存放namonode中所有数据,运行时数据加载到内存中存放edits_inprogress 类似于LSM树中的Log,在向hdfs写的过程都向此文件存放,当存的数据到一定程度/或者定期间隔多久,会把此文件内容跟新...
  • HDFS——fsimage

    千次阅读 2020-05-19 12:53:40
    【前言】在《HDFS——editLog文件》一文中提到了namenode(后面简称nn)的元数据信息由editlog和fsimage文件组成。editlog文件记录对文件系统的写操作,f...
  • hadoop查看fsimage

    2021-05-21 20:39:08
    一,使用hdfs命令获取FsImage数据文件 hdfs dfsadmin -fetchImage ./tmp_meta 注意,这是本地文件系统 二,执行命令解析fsimage文件 hdfs oiv -i ../tmp_meta/fsimage_0000000000000023025 -o ../tmp_meta_rec/fs....
  • fsimage,edits ,SNN介绍

    2020-12-08 10:29:20
    fsimage:命名空间镜像文件,不及时更新,存储多个NameNode信息。(一个文件一个NameNode) 把文件和目录的元数据信息持久化地存储到fsimage文件中,每次启动时从中将元数据加载到内存中构建目录结构树,之后的操作...
  • Hadoop3.1.3版本,关于HDFS的第三篇学习笔记,记录了NN、2NN、DN的工作机制,包括NN中Fsimage和Edits的合并机制,还有如何使用命令查看它们的内容
  • 视频:【美妙人生】Hadoop课程系列之HDFS--手把手教你精通HDFS 【美妙人生】Hadoop课程系列之HDFS--手把手教...hdfs oiv -i fsimage_0000000000000000022 -o ~/fs22.xml -p XML 【 <?xml version="1.0"?> ...
  • 在进入下面的主题之前想来搞清楚edits和fsimage文件的概念: 1,fsimage文件其实是hadoop文件系统元数据的一个永久性的检查点,其中包含hadoop文件系统中的所有目录和文件idnode的序列化信息。 2,edits文件存放的...
  • 关键字:hdfs元数据、fsimage、editsnode在HDFS中,fsimage和eidts是NameNode两个很是重要的文件。数据库其中,fsimage是HDFS文件系统存于硬盘中的元数据检查点,里面记录了自最后一次检查点以前HDFS文件系统中全部...
  • FSimage源码剖析

    千次阅读 2020-08-08 20:14:00
    FSImage文件主要是用来存储hdfs文件系统元数据信息(文件及目录结构 组成文件的块的信息 副本数量信息等),并且NameNode会定期将文件系统的命名空间元数据信息保存到fsimage文件中;以防止NameNode掉电或者进程崩溃;...
  • e2fsimage-开源

    2021-07-01 01:35:37
    此工具为非 root 用户创建 ext2 文件系统映像,如“initrd”。 对于通常以 root 身份创建、格式化、环回挂载、填充和卸载文件映像的每个人都很有用,但希望以非 root 用户身份执行此操作。
  • HDFS fsimage文件解析

    2021-01-01 22:29:22
    nohup hdfs oiv -t /data/tmp/fsimage -p Delimited -delimiter , -i fsimage_000000000xxx -o result_file & 30gb大的fsimage文件解析大概耗时2小时。 结果文件需要删除首行列名 sed -i ‘1d’ result_file
  • HDFS 是一个分布式文件存储系统,文件分布式存储在多个 DataNode ...完整的 metadata 信息就应该由 FSImage 文件和 edit log 文件组成。fsimage 中存储的信息就相当于整个 hdfs 在某一时刻的一个快照。 在某一次启动H
  • 今天上去看了下namenode的fsimage镜像好久没从内存写到硬盘上了,想找找什么问题。对hadoop也不太熟悉,也是慢慢摸索阶段 启动SecondaryNameNode 启动命令 hadoop-daemon.sh start secondarynamenode 然后执行jps...
  • hdfs小文件使用fsimage分析实例

    千次阅读 2022-03-22 17:42:40
    2.2.3 mapreduce 定义规范3fsimage 离线分析hdfs内容3.1创建hive表 存储元数据,做分析用3.2 处理fsimage文件3.2.1获取fsimage文件:3.2.2从2进制文件解析:3.2.3加载进hive中3.2.4 统计文件整体情况3.2.
  • Namenode主要维护两个文件,一个是fsimage,一个是editlog。 fsimage保存了最新的元数据检查点,包含了整个HDFS文件系统的所有目录和文件的信息。对于文件来说包括了数据块描述信息、修改时间、访问时间等;对于...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,366
精华内容 5,746
关键字:

fsimage