HDFS的写数据过程分析

我们通过FileSystem类可以操控HDFS, 那我们就从这里开始分析写数据到HDFS的过程。
在我们向 HDFS 写文件的时候,调用的是 FileSystem.create(Path path)方法,我们查看这个方法的源码,通过跟踪内部的重载方法,可以找到
  /**
   * Opens an FSDataOutputStream at the indicated Path with write-progress
   * reporting.
   * @param f the file name to open
   * @param permission
   * @param overwrite if a file with this name already exists, then if true,
   *   the file will be overwritten, and if false an error will be thrown.
   * @param bufferSize the size of the buffer to be used.
   * @param replication required block replication for the file.
   * @param blockSize
   * @param progress
   * @throws IOException
   * @see #setPermission(Path, FsPermission)
   */
  public abstract FSDataOutputStream create(Path f,
      FsPermission permission,
      boolean overwrite,
      int bufferSize,
      short replication,
      long blockSize,
      Progressable progress) throws IOException;

这个方法是抽象类,没有实现。那么我们只能向他的子类寻找实现。FileSystem 有个子类是 DistributedFileSystem,在我们的伪分布环境下使用的就是这个类。我们可以看到DistributedFileSystem 的这个方法的实现

点create方法 按ctrl+T 点击DistributedFileSystem 进进去了:
  public FSDataOutputStream create(Path f, FsPermission permission,
    boolean overwrite,
    int bufferSize, short replication, long blockSize,
    Progressable progress) throws IOException {

    statistics.incrementWriteOps(1);
    return new FSDataOutputStream
       (dfs.create(getPathName(f), permission,
                   overwrite, true, replication, blockSize, progress, bufferSize),
        statistics);
  }

注意返回值 FSDataOutputStream。这个返回值对象调用了自己的构造方法, 构造方法的第一个参数是 dfs.create()方法。 我们关注一下这里的 dfs 对象是谁,create 方法做了什么事情。现在进入这个方法的实现,

  /**
   * Create a new dfs file with the specified block replication
   * with write-progress reporting and return an output stream for writing
   * into the file.
   *
   * @param src stream name
   * @param permission The permission of the directory being created.
   * If permission == null, use {@link FsPermission#getDefault()}.
   * @param overwrite do not check for file existence if true
   * @param createParent create missing parent directory if true
   * @param replication block replication
   * @return output stream
   * @throws IOException
   * @see ClientProtocol#create(String, FsPermission, String, boolean, short, long)
   */
  public OutputStream create(String src,
                             FsPermission permission,
                             boolean overwrite,
                             boolean createParent,
                             short replication,
                             long blockSize,
                             Progressable progress,
                             int buffersize
                             ) throws IOException {
    checkOpen();
    if (permission == null) {
      permission = FsPermission.getDefault();
    }
    FsPermission masked = permission.applyUMask(FsPermission.getUMask(conf));
    LOG.debug(src + ": masked=" + masked);
    final DFSOutputStream result = new DFSOutputStream(src, masked,
        overwrite, createParent, replication, blockSize, progress, buffersize,
        conf.getInt("io.bytes.per.checksum", 512));
    beginFileLease(src, result);
    return result;
  }

final DFSOutputStream result = new DFSOutputStream(src, masked,

返回值创建的对象。这个类有什么神奇的地方吗?我们看一下他的(点击DFSOutputStream)源码,
    /**
     * Create a new output stream to the given DataNode.
     * @see ClientProtocol#create(String, FsPermission, String, boolean, short, long)
     */
    DFSOutputStream(String src, FsPermission masked, boolean overwrite,
        boolean createParent, short replication, long blockSize, Progressable progress,
        int buffersize, int bytesPerChecksum) throws IOException {
      this(src, blockSize, progress, bytesPerChecksum, replication);

      computePacketChunkSize(writePacketSize, bytesPerChecksum);

      try {
        // Make sure the regular create() is done through the old create().
        // This is done to ensure that newer clients (post-1.0) can talk to
        // older clusters (pre-1.0). Older clusters lack the new  create()
        // method accepting createParent as one of the arguments.
        if (createParent) {
          namenode.create(
            src, masked, clientName, overwrite, replication, blockSize);
        } else {
          namenode.create(
            src, masked, clientName, overwrite, false, replication, blockSize);
        }
      } catch(RemoteException re) {
        throw re.unwrapRemoteException(AccessControlException.class,
                                       FileAlreadyExistsException.class,
                                       FileNotFoundException.class,
                                       NSQuotaExceededException.class,
                                       DSQuotaExceededException.class);
      }
      streamer.start();
    }

可 以 看 到 , 这 个 类 是 DFSClient 的 内 部 类 。 在 类 内 部 通 过 调用namenode.create()方法创建了一个输出流。我们再看一下 namenode 对象是什么类型

  public final ClientProtocol namenode;

可以看到 namenode 其实是 ClientProtocal 接口。那么,这个对象是什么时候创建的?点击outline点击下面这个DFSClient:

  /**
   * Create a new DFSClient connected to the given nameNodeAddr or rpcNamenode.
   * Exactly one of nameNodeAddr or rpcNamenode must be null.
   */
  DFSClient(InetSocketAddress nameNodeAddr, ClientProtocol rpcNamenode,
      Configuration conf, FileSystem.Statistics stats)
    throws IOException {
    this.conf = conf;
    this.stats = stats;
    this.nnAddress = nameNodeAddr;
    this.socketTimeout = conf.getInt("dfs.socket.timeout",
                                     HdfsConstants.READ_TIMEOUT);
    this.datanodeWriteTimeout = conf.getInt("dfs.datanode.socket.write.timeout",
                                            HdfsConstants.WRITE_TIMEOUT);
    this.timeoutValue = this.socketTimeout;
    this.socketFactory = NetUtils.getSocketFactory(conf, ClientProtocol.class);
    // dfs.write.packet.size is an internal config variable
    this.writePacketSize = conf.getInt("dfs.write.packet.size", 64*1024);
    this.maxBlockAcquireFailures = getMaxBlockAcquireFailures(conf);

    this.hdfsTimeout = Client.getTimeout(conf);
    ugi = UserGroupInformation.getCurrentUser();
    this.authority = nameNodeAddr == null? "null":
      nameNodeAddr.getHostName() + ":" + nameNodeAddr.getPort();
    String taskId = conf.get("mapred.task.id", "NONMAPREDUCE");
    this.clientName = "DFSClient_" + taskId + "_" +
        r.nextInt()  + "_" + Thread.currentThread().getId();

    defaultBlockSize = conf.getLong("dfs.block.size", DEFAULT_BLOCK_SIZE);
    defaultReplication = (short) conf.getInt("dfs.replication", 3);

    if (nameNodeAddr != null && rpcNamenode == null) {
      this.rpcNamenode = createRPCNamenode(nameNodeAddr, conf, ugi);
     this.namenode = createNamenode(this.rpcNamenode, conf);
    } else if (nameNodeAddr == null && rpcNamenode != null) {
      //This case is used for testing.
      this.namenode = this.rpcNamenode = rpcNamenode;
    } else {
      throw new IllegalArgumentException(
          "Expecting exactly one of nameNodeAddr and rpcNamenode being null: "
          + "nameNodeAddr=" + nameNodeAddr + ", rpcNamenode=" + rpcNamenode);
    }
    // read directly from the block file if configured.
    this.shortCircuitLocalReads = conf.getBoolean(
        DFSConfigKeys.DFS_CLIENT_READ_SHORTCIRCUIT_KEY,
        DFSConfigKeys.DFS_CLIENT_READ_SHORTCIRCUIT_DEFAULT);
    if (LOG.isDebugEnabled()) {
      LOG.debug("Short circuit read is " + shortCircuitLocalReads);
    }
    this.connectToDnViaHostname = conf.getBoolean(
        DFSConfigKeys.DFS_CLIENT_USE_DN_HOSTNAME,
        DFSConfigKeys.DFS_CLIENT_USE_DN_HOSTNAME_DEFAULT);
    if (LOG.isDebugEnabled()) {
      LOG.debug("Connect to datanode via hostname is " + connectToDnViaHostname);
    }
    String localInterfaces[] =
      conf.getStrings(DFSConfigKeys.DFS_CLIENT_LOCAL_INTERFACES);
    if (null == localInterfaces) {
      localInterfaces = new String[0];
    }
    this.localInterfaceAddrs = getLocalInterfaceAddrs(localInterfaces);
    if (LOG.isDebugEnabled() && 0 != localInterfaces.length) {
      LOG.debug("Using local interfaces [" +
          StringUtils.join(",",localInterfaces)+ "] with addresses [" +
          StringUtils.join(",",localInterfaceAddrs) + "]");
    }
  }

可以看到34行namenode 对象是在 DFSClient 的构造函数调用时创建的,即当 DFSClient 对象存在的时候,namenode 对象已经存在了。


至此,我们可以看到,使用 FileSystem 对象的 api 操纵 HDFS,其实是通过 DFSClient 对象访问 NameNode 中的方法操纵 HDFS 的。 这里的 DFSClient 是 RPC 机制的客户端, NameNode是 RPC 机制的服务端的调用对象,整个调用过程如图 




在整个过程中,DFSClient 是个很重要的类,从名称就可以看出,他表示 HDFS 的 Client,是整个 HDFS 的 RPC 机制的客户端部分。我们对 HDFS 的操作,是通过 FileSsytem 调用的DFSClient 里面的方法。FileSystem 是封装了对 DFSClient 的操作,提供给用户使用的





HDFS的写数据过程分析的更多相关文章

  1. Linux启动kettle及linux和windows中kettle往hdfs中写数据(3)

    在xmanager中的xshell运行进入图形化界面 sh spoon.sh 新建一个job

  2. HDFS 读/写数据流程

    1. HDFS 写数据流程 客户端通过 Distributed FileSystem 模块向 NameNode 请求上传文件, NameNode 检查目标文件是否已存在,父目录是否存在: NameNo ...

  3. HDFS源码解析:教你用HDFS客户端写数据

    摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...

  4. 通过FSDataOutputStream向HDFS上写数据

    FSDataOutputStream,这个类重载了很多write方法,用于写入很多类型的数据:比如字节数组,long,int,char等等. 像FSDataInputStream一样,要获得FSDat ...

  5. HDFS数据流——写数据流程

    剖析HDFS文件写入 假设文件ss.avi共200m,其写入HDFS指定路径/user/atguigu/ss.avi流程如下: 1)客户端向namenode请求上传文件到指定路径,namenode通过 ...

  6. Hadoop源码分析之客户端向HDFS写数据

    转自:http://www.tuicool.com/articles/neUrmu 在上一篇博文中分析了客户端从HDFS读取数据的过程,下面来看看客户端是怎么样向HDFS写数据的,下面的代码将本地文件 ...

  7. Hadoop(8)-HDFS的读写数据流程以及机架感知

    1. HDFS的写数据流程 1.客户端通过fs模块向NameNode申请文件上传,NameNode检查请求是否合法,如用户权限,目标文件是否已存在,父目录是否存在等等 2.NameNode返回是否可以 ...

  8. HDFS写数据和读数据流程

    HDFS数据存储 HDFS client上传数据到HDFS时,首先,在本地缓存数据,当数据达到一个block大小时.请求NameNode分配一个block. NameNode会把block所在的Dat ...

  9. HDFS写文件过程分析

    转自http://shiyanjun.cn/archives/942.html HDFS是一个分布式文件系统,在HDFS上写文件的过程与我们平时使用的单机文件系统非常不同,从宏观上来看,在HDFS文件 ...

随机推荐

  1. python 装饰器统计某个函数的运行时间

    import datetime def count_time(func): def int_time(*args, **kwargs): start_time = datetime.datetime. ...

  2. [JSOI 2008]星球大战starwar

    Description 题库链接 给你一张 \(n\) 点, \(m\) 条边的无向图,每次摧毁一个点,问你剩下几个联通块. \(1\leq n\leq 2m,1\leq m\leq 200000\) ...

  3. NGUI制作可滚动的文本框(摘,如有侵权,联系删除)

    NGUI制作可滚动的文本框 1.首先创建一个UI Root 2.选择UI Root右键 Create 选择Scoll View创建. 3.Scroll view的大小是可以调节的,调节为你需要的适当大 ...

  4. 修改firefox的默认缩放比

    我们在使用firefox时,发现页面的缩放比只能一个一个手动调整,当跳转到新的页面时默认缩放比又还原了,这个时候又要重新调整,相当不方便. 下面介绍修改默认缩放比的方法: 首先,打开firefox,然 ...

  5. 【OCP|052】OCP最新题库解析(052)--小麦苗解答版

    [OCP|052]OCP最新题库解析(052)--小麦苗解答版 OCP最新题库解析历史连接(052):http://mp.weixin.qq.com/s/bUgn4-uciSndji_pUbLZfA ...

  6. URLDecoder: Illegal hex characters in escape (%) pattern - For input string

    原因:后台发布文章的时候,内容里面有%,导致后台URLDecoder.decode()转码的时候报错. 看了java.net.URLDecoder的decode()的源码,原来是转码错误. 贴出部分代 ...

  7. SSH构造struts2项目

    第一在pom.xml导入相应的包 (网上有很多导入多个包的教程,我缩减到一个了) <project xmlns="http://maven.apache.org/POM/4.0.0&q ...

  8. MySQL数据类型DECIMAL用法

    MySQL DECIMAL数据类型用于在数据库中存储精确的数值.我们经常将DECIMAL数据类型用于保留准确精确度的列,例如会计系统中的货币数据. 要定义数据类型为DECIMAL的列,请使用以下语法: ...

  9. 3.2 2-dim Vector Initialization

    声明3行4列的数组 const int m = 3, n = 4; vector<vector<int> > A(m); // 3 rows for(int i = 0; i ...

  10. ERP中的序列管理

    1.序列管理 序列管理主要实现系统用到序列生成规则的配置.主要包含序列配置.序列生产两个功能点. 2.术语说明 序列号:指序列中按步长递进的数字. 序列值:指按规则组合了 "拥有者.序列类型 ...