HDFS inotify:Linux inotify机制在HDFS中的实现

前言


在文件系统的使用中,在某些场合我们往往会有这样一个需求点:我们想对某个文件/目录进行事件监听,监听的事件包括在目标目录下新增文件了,又或者说是删除了什么文件等等.这其实是对目标文件目录数据的一个比较实时的监控.我们比较传统的方案是去做定期的全盘扫描,然后算出增量值与最新统计值.这种方式的优点是实现简单,但是缺点也很明显,就是太低效了.那么在目前现有的Linux操作系统中,是否有这样的一套event事件通知机制呢?答案是有的,这套机制叫做inotify.对Linux比较有研究的同学,应该不会陌生,但是本文所要叙述的主题是HDFS inotify.换句话说,就是Linux inotify机制在HDFS中的实现.HDFS作为一套分布式文件系统,同样也有这样的需求和相应的使用场景.本人在看完HDFS inotify机制的设计与实现之后,感觉还是非常棒的.相信本文会对大家有帮助.

Linux inotify机制


了解Linux inotify机制是学习HDFS inotify的一个前提.Linux inotify机制的原理用简单的一句话概况如下:

它是一个内核用于通知用户空间程序文件系统变化的机制.

上面这句话的意思表明了它是从内核态到用户态的一套消息通知机制,它能将系统层面的变化通知到用户.本文所指的系统层面指的是文件系统层面,当然了Linux还有针对系统其他层面的类似的通知机制,比如热插拔设备的一些事件通知等.

Linux inotify的使用


基于本人并不是Linux内核专家,所以这里不会大篇幅讨论Linux inotify的底层实现过程,下面简单聊聊它的使用,借此来看看它的具体使用步骤,同时可以拿来与后面将要阐述的HDFS inotify做对比.

下面是具体的使用步骤:
步骤一:首先初始化一个inotify实例对象,得到一个fd文件描述符.

int fd = inotify_init ();

步骤二:在目标需要监听的文件/目录上添加watch监听器(这里的path参数就是目标文件/目录的全路径).

int wd = inotify_add_watch (fd, path, mask);

步骤三:通过调用read方法读取event通知事件.

size_t len = read (fd, buf, BUF_LEN);

通过定期循环的调用read方法,就可以做到对目标文件/目录变化的实时感知了.总的来看,linux的inotify的使用并不是特别复杂.从上面的步骤中,我们还能提取出一个关键信息:Linux inotify的调用模式看起来更像是Pull模式.通过客户端程序定时的去”拉”,而不是服务端直接push的方式.

OK,linux inotify机制就简单介绍到这里,更细节的内容可以点击文章末尾的链接地址进行进一步的学习.

HDFS inotify


正如前言中所提到的,HDFS作为一套文件系统,它同样有这样的需求,社区在HDFS-6634(inotify in HDFS)中,对此进行了实现,发布版本2.6.0.想使用此功能特性的同学可以使用Hadoop 2.6以及往后的版本.

HDFS inotify背景


在HDFS inotify机制实现之前,使用者们一般会通过什么方式来获取文件的事件变更呢?答案是editlog.因为HDFS上的每个事务变化记录都会持久化到editlog中.所以分析editlog是比较常见的做法.但是这种方式有几个弊端:

  • 第一, Editlog是存放在JournalNode上的,这就要求解析程序要指定地址去读取文件,但是如果遇到JournalNode挂掉的现象或者说遇到集群迁移的时候,解析程序还是得要进行修改.
  • 第二, Editlog会动态发生变化,NameNode会不断往里写新的记录数据,这个时候我们的程序是要实时的去读最新的数据呢?还是说去读取一个最近刚刚finalize的editlog?前者实现起来不好把控,后者的读取方式会有数据延时的问题.

HDFS inotify使用场景


Inotify机制在HDFS中有以下两大适用场景:

  • 1.对文件/目录的实时监控(比如一小时内文件数增删了多少等等).通过event事件获取文件变动信息,以此更新统计值.相比于传统方式的定期的分析fsimage文件或者调用文件相关RPC请求获取信息而言,HDFS inotify显然会更加高效,便捷.
  • 2.集群数据的镜像同步.当我们需要对一套线上集群做实时数据同步的时候(比如说为了做一个容灾的备份),这个时候我们可以采用inotify机制,因为我们可以拿到每次变化的文件信息,然后我们只需在对应的镜像集群中进行相应操作即可,拷贝变动的目录或者是删除无效的文件等等.

HDFS inotify实现原理


下面说说HDFS inotify的实现原理,在Linux inotify中,操作系统会发送文件event事件到用户态,那么在HDFS中,这个event事件应该对应到哪个对象呢?没错,它就是editlog中的一条条记录.直接地来说,HDFS inotify的出现,可以使得客户端程序无须自己解析editlog,就可以直接从NameNode中查询当前的变更记录.

在实现模式上,社区采用了更简单化的Pull方式,”拉”模式有以下几点优点:

  • 设计实现起来较为简单,通过客户端主动的拉取,从而获取数据信息.
  • 减小对NameNode的压力.因为采用Push的方式,NameNode势必要维护与各个客户端的socket连接,这对于本身负载压力就很大的NameNode来说,这么做并不太好.

在HDFS inotify的实现中,实现者新定义了一种名叫DFSInotifyEventInputStream的输入流类,从此输入流中,我们就能读到各个event事件,这写event事件其实就是editlog中的记录转化而来的.读取的方式有2种:

  • 第一种:不传起始id,默认获取当前实时的event事件.
  • 第二种,传入lastReadTxid,获取此id后的event事件.

对应的API如下:

  // 不传入lastReadTxid,默认获取的是当前txid
  public DFSInotifyEventInputStream getInotifyEventStream() throws IOException {
    return dfs.getInotifyEventStream();
  }

  public DFSInotifyEventInputStream getInotifyEventStream(long lastReadTxid)
      throws IOException {
    return dfs.getInotifyEventStream(lastReadTxid);
  }

这样的读取方式还是比较好理解的,通过传入最后读取的id值,然后获取此id后的记录信息,从而保证读取数据的连续性.

HDFS inotify后续优化


HDFS-6634的inotify实现中,目前只是实现了功能的基本可用性,其实还有许多别的提升点,主要有以下3点:

  • 第一, 目前还不能对指定文件/目录路径的event获取,而这点在linux中是支持的.社区目前也的确是有在做这块的补充,相关JIRAHDFS-6939(Support path-based filtering of inotify events).
  • 第二, 安全问题,由于每次获取到的event事件内包含了大量的详细信息,这需要做一个权限校验,防止普通用户获取到了敏感文件数据信息.
  • 第三, 性能问题, 尽管说目前已经采用了Pull模式的方式来实现HDFS inotify,但是它真正会对NameNode的处理性能造成怎样的影响,这个还需要再观察以及后续的优化,如果客户端频繁的请求,NameNode是否能够承受的住呢?

HDFS inotify调用实例

学习使用一个新特性功能最快速的方式就是去看它的test case.下面是HDFS inotify的测试实例,调用的方式还是比较简单的:

public void testBasic() throws IOException, URISyntaxException,
      InterruptedException, MissingEventsException {
    Configuration conf = new HdfsConfiguration();
    conf.setLong(DFSConfigKeys.DFS_BLOCK_SIZE_KEY, BLOCK_SIZE);
    conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_ACLS_ENABLED_KEY, true);
    // so that we can get an atime change
    conf.setLong(DFSConfigKeys.DFS_NAMENODE_ACCESSTIME_PRECISION_KEY, 1);

    // 构建测试用例集群
    MiniQJMHACluster.Builder builder = new MiniQJMHACluster.Builder(conf);
    builder.getDfsBuilder().numDataNodes(2);
    MiniQJMHACluster cluster = builder.build();

    try {
      cluster.getDfsCluster().waitActive();
      cluster.getDfsCluster().transitionToActive(0);
      // 构造DFSClient客户端
      DFSClient client = new DFSClient(cluster.getDfsCluster().getNameNode(0)
          .getNameNodeAddress(), conf);
      FileSystem fs = cluster.getDfsCluster().getFileSystem(0);
      // 创建初始文件
      DFSTestUtil.createFile(fs, new Path("/file"), BLOCK_SIZE, (short) 1, 0L);

      // 获取inotify输入流对象
      DFSInotifyEventInputStream eis = client.getInotifyEventStream();
      // 执行rename重命名操作
      client.rename("/file", "/file4", null); // RenameOp -> RenameEvent
      client.rename("/file4", "/file2"); // RenameOldOp -> RenameEvent

      Event next = null;

      // 从输入流中读取到rename事件
      // RenameOp
      next = waitForNextEvent(eis);
      Assert.assertTrue(next.getEventType() == Event.EventType.RENAME);
      Event.RenameEvent re = (Event.RenameEvent) next;
      Assert.assertTrue(re.getDstPath().equals("/file4"));
      Assert.assertTrue(re.getSrcPath().equals("/file"));
      Assert.assertTrue(re.getTimestamp() > 0);

      long eventsBehind = eis.getEventsBehindEstimate();

      // RenameOldOp
      next = waitForNextEvent(eis);
      Assert.assertTrue(next.getEventType() == Event.EventType.RENAME);
      Event.RenameEvent re2 = (Event.RenameEvent) next;
      Assert.assertTrue(re2.getDstPath().equals("/file2"));
      Assert.assertTrue(re2.getSrcPath().equals("/file4"));
      Assert.assertTrue(re.getTimestamp() > 0);

      } finally {
      cluster.shutdown();
    }
  }

参考资料


[1].Linux inotify机制
[2].https://issues.apache.org/jira/browse/HDFS-6634
[3].https://issues.apache.org/jira/browse/HDFS-6939
[4].https://issues.apache.org/jira/secure/attachment/12663753/inotify-design.4.pdf

原文地址:https://www.cnblogs.com/bianqi/p/12183733.html