2023-07-30  阅读(5)
原文作者:Ressmix 原文地址:https://www.tpvlog.com/article/320

内存文件目录树,在一些开源的分布式存储系统中,有时也叫做Namespace或者元数据。它是NameNode节点的一种记录客户端文件操作命令的抽象数据结构,维护在NameNode的自身内存中。

在上一章《dfs客户端》中,我提到NameNodeRpcServer在接受到Client的文件操作RPC请求后,会委托给 FSNameSystem 处理:

    public class NameNodeServiceImpl extends NameNodeServiceGrpc.NameNodeServiceImplBase {
        // 负责管理元数据的核心组件
        private FSNameSystem namesystem;
    
        /**
         * 创建目录
         */
        @Override
        public void mkdir(MkDirRequest request, StreamObserver<MkDirResponse> responseObserver) {
            try {
                MkDirResponse response = null;
                if (!isRunning) {
                    response = MkDirResponse.newBuilder().setStatus(STATUS_SHUTDOWN).build();
                } else {
                    this.namesystem.mkdir(request.getPath());
                    response = MkDirResponse.newBuilder().setStatus(STATUS_SUCCESS).build();
                }
                responseObserver.onNext(response);
                responseObserver.onCompleted();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

FSNameSystem 就是NameNode进行元数据管理的核心组件,本章,我就来实现FSNameSystem。

一、元数据管理

FSNameSystem内部包含了两个核心组件:

  • FSDirectory:负责维护元数据,即内存文件目录树;
  • FSEditlog:负责管理edits log日志。
    /**
     * 负责管理元数据的核心组件
     */
    public class FSNameSystem {
    
        // 负责管理内存文件目录树的组件
        private FSDirectory directory;
    
        // 负责管理edits log写入磁盘的组件
        private FSEditlog editlog;
    
        // 最近一次checkpoint更新到的txid
        private long checkpointTxid = 0L;
    
        public FSNameSystem() {
            this.directory = new FSDirectory();
            this.editlog = new FSEditlog();
            recoverNamespace();
        }
    
        /**
         * 创建目录
         *
         * @param path 目录路径
         * @return 是否成功
         */
        public Boolean mkdir(String path) throws Exception {
            this.directory.mkdir(path);
            this.editlog.logEdit("{'OP':'MKDIR','PATH':'" + path + "'}");
            return true;
        }
    
        //...
    }

可以看到,FSNameSystem把对内存文件目录树的操作委托给了FSDirectory

关于FSEditlog组件和edits log日志,我会在后续章节详细讲解,本章我们只关注内存文件目录树。

1.1 数据结构

我们需要先考虑下内存文件目录树的结构是怎么样的?内存文件目录树,本质是一个父子层级关系的数据结构,比如:

    /root
      /usr
      /local
      /app
    /home
      /kafka
        /data
          /access.log

对目录/文件进行增删改,本质都是在更新该内存里的数据结构,所以我们定义一个INode类,代表文件目录树中的一个节点:

    /**
     * 代表文件目录树中的一个目录
     */
    public static class INode {
        private String path;
        private List<INode> children;
    
        public INode() {
        }
    
        public INode(String path) {
            this.path = path;
            this.children = new LinkedList<INode>();
        }
    
        public void addChild(INode inode) {
            this.children.add(inode);
        }
    
        public String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        public List<INode> getChildren() {
            return children;
        }
    
        public void setChildren(List<INode> children) {
            this.children = children;
        }
    
        @Override
        public String toString() {
            return "INode [path=" + path + ", children=" + children + "]";
        }
    }

1.2 FSDirectory

定义完文件目录树的基本结构后,我们还需要一个组件 FSDirectory 对文件和目录进行封装,并暴露一些对外的接口。我这里只实现了mkdir接口,你们可以根据业务需要实现各种其它维护文件树的接口:

    /**
     * 管理文件目录树的核心组件
     */
    public class FSDirectory {
    
        // 内存中的文件目录树
        private INode dirTree;
    
        public FSDirectory() {
            // 初始化时只有根节点
            this.dirTree = new INode("/");
        }
    
        /**
         * 创建目录
         *
         * @param path 目录路径, eg: path = /usr/warehouse/hive
         */
        public void mkdir(String path) {
            synchronized (dirTree) {
                // eg: /usr/warehouse/hive -> ["","usr","warehosue","hive"]
                String[] pathes = path.split("/");
                INode parent = dirTree;
    
                for (String splitedPath : pathes) {
                    if (splitedPath.trim().equals("")) {
                        continue;
                    }
    
                    INode dir = findDirectory(parent, splitedPath);
                    if (dir != null) {
                        parent = dir;
                        continue;
                    }
    
                    INode child = new INode(splitedPath);
                    parent.addChild(child);
                    parent = child;
                }
            }
            // printDirTree(dirTree, "");
        }
    
        /**
         * 查找子目录
         * <p>
         * 在dir目录下查找子目录path
         */
        private INode findDirectory(INode dir, String path) {
            if (dir.getChildren().size() == 0) {
                return null;
            }
    
            for (INode child : dir.getChildren()) {
                if (child instanceof INode) {
                    INode childDir = (INode) child;
                    if ((childDir.getPath().equals(path))) {
                        return childDir;
                    }
                }
            }
            return null;
        }
    
        public INode getDirTree() {
            return dirTree;
        }
    
        public void setDirTree(INode dirTree) {
            this.dirTree = dirTree;
        }
    }

上面需要特别注意的一点是,我使用了synchronized对内存文件目录树进行加锁,以防止并发修改时出现问题。

二、总结

本章,我对NameNode中的文件目录树结构进行了讲解,同时给出了一个比较简单的实现,很多分布式文件系统的实现思想都是类似的。下一章开始,我将对最核心的edits log日志进行讲解。

202307302136392531.png


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文