FsDataOutputStream 之 hflush和hsync的区别

本文是基于hadoop-2.8.0的源码实现

1. linux上的sync, fsync, fdatasync的区别

  1. sync会将输出流缓冲区数据排入到OS维护的写磁盘对列,而不会等在磁盘操作完成,因此sync调用返回后数据依然有可能会丢失。
  2. fsync和sync不同的是它会等待写磁盘操作完成,同时它还会修改文件meta信息。因此fsync往往对应两次磁盘IO:一次写数据到磁盘,一次修改文件meta信息到磁盘。
  3. fdatasync 和fsync一样等到磁盘操作完成,但是它不会修改文件的meta信息。

2 FsDataOutputStream hflush和hsync

回到hdfs的hflush和hsync, 文件的meta信息存放在namenode上,文件的data信息存放在datanode上。
client调用hflush和hsync流程都是一样的,首先这里再次说明一下write的流程:

client端: 
输出流底层会缓冲数据,缓冲到一定大小后flush到datanode,这个缓冲区是一个block。
写到block的数据又被切分成一个个packet,发送block的时候是一个个packet发送,发送完packet后会将packer移到等待确认对列中,然后等待datanode的确认。

datanode端:
写数据采用pipeline机制,比如数据需要在3个datanode(dn1, dn2, dn3)上备份,那么dn1收到 client端的packet后,写packet到dn2, 然后将packet写到本地磁盘并flush, dn2如法炮制写dn3并写本地。

确认:
dn3是pipeline中最后的datanode, dn3写完后发送对packet的ack到dn2, dn2发送ack到dn1,
dn1 发送ack到client。client收到packet的确认后将packet从确认对列移除。

下面比较两者的区别
下面是FsDataoutputStream中hflush和hsync的实现:

@Override  // Syncable
  public void hflush() throws IOException {
    if (wrappedStream instanceof Syncable) {
      ((Syncable)wrappedStream).hflush();
    } else {
      wrappedStream.flush();
    }
  }
  
  @Override  // Syncable
  public void hsync() throws IOException {
    if (wrappedStream instanceof Syncable) {
      ((Syncable)wrappedStream).hsync();
    } else {
      wrappedStream.flush();
    }
  }
-----------------------------------------------------
调用的是wrappedStream的hflush和hsync,此处wrappedStream只讨论是DFSOutputStream的情况,下面是DFSOutputStream的hflush和hsync:
 @Override
  public void hflush() throws IOException {
    try (TraceScope ignored = dfsClient.newPathTraceScope("hflush", src)) {
      flushOrSync(false, EnumSet.noneOf(SyncFlag.class));
    }
  }

  @Override
  public void hsync() throws IOException {
    try (TraceScope ignored = dfsClient.newPathTraceScope("hsync", src)) {
      flushOrSync(true, EnumSet.noneOf(SyncFlag.class));
    }
  }
调用的都是flushOrSync,第一个参数的不同最终被导致了datanode端写数据处理方式的不同,不深挖代码了,后面的代码跟hdfs写数据流程重合,如果有空的化还会记录一下hdfs写数据过程。
  1. hflush
    上面简要的写流程可以看出,关键是datanode的收到packet后的flush。 client端调用FsDataoutputStream # hflush, 在datanode上实际上调用的是OutputStream # flush, 从jdk关于flush的解释可以知道,flush类似linux的sync操作,只是将文件输出流缓冲的的数据移动到os的写磁盘缓冲区,并不会等待写磁盘操作完成。
  2. hsync
    和hflush的不同只在与datanode write packet后不是调用flush,而是调用下面一段代码:
public void syncDataOut() throws IOException {
    if (dataOut instanceof FileOutputStream) {
      ((FileOutputStream)dataOut).getChannel().force(true);
    }
  }

dataOut是本地文件输出流。force(true)的语义保证会等待数据写到磁盘, 指定参数true还会是的刷新datanode上dataOut输出流对应文件的meta信息。

所以hsync类似linux上的fsync调用。
但是hdfs上文件的meta信息保存在namenode上,无论是hflush还是hsync都没有更改namenode的文件meta信息,主要是length信息。这和DFSoutputStream中hflush和hsync调用的flushOrSync参数有关,其第二个参数都被设置成
EnumSet.noneOf(SyncFlag.class);
实际上SyncFlag枚举了两个值如下:

public enum SyncFlag {

    /**
     * When doing sync to DataNodes, also update the metadata (block length) in
     * the NameNode.
     */
    UPDATE_LENGTH,

    /**
     * Sync the data to DataNode, close the current block, and allocate a new
     * block
     */
    END_BLOCK
  }

如果指定UPDATE_LENGTH 才会在flush之后修改namenode的meta信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 思考问题 HDFS的架构 设计前提和目标 专为存储超大文件而设计:hdfs应该能够支持GB级别大小的文件;它应该能...
    Sakura_P阅读 763评论 0 2
  • 首先,我们在使用前先看看HDFS是什麽?这将有助于我们是以后的运维使用和故障排除思路的获得。 HDFS采用mast...
    W_Bousquet阅读 4,506评论 0 2
  • 概述 前面很多大侠都分享过MySQL的InnoDB存储引擎将数据刷新的各种情况。我们这篇文章从InnoDB往下,看...
    木木飞阅读 1,595评论 0 3
  • 最近在新梦想老师们的指引下开始阅读《羊皮卷》。起初我认为只是写关于口才的相关读物,可是在经过差不多20多天...
    小朗子阅读 190评论 0 0
  • 你听,那微笑里有偶尔的无奈 你听到了吗? 细细地听 你一定是听到了,一丝丝地无奈 你听,那深深埋下的头下有泪珠 你...
    海心沉思之花阅读 232评论 0 0

友情链接更多精彩内容