NIFI源码学习-(四、1)WAL(Write Ahead Log)预写日志

一、什么是 WAL

我们想一想,如果想保证对一个数据的操作可以被恢复,事务执行失败能还原到事务执行之前的状态,可以怎么做?不用去想数据库是怎么实现的,也不用想太高深。其实这是一个很简单的问题,我们常常在处理这种问题。最简单的方法其实就是备份一份数据:

当我需要对一条数据做更新操作前,先将这条数据备份在一个地方,然后去更新,如果更新失败,可以从备份数据中回写回来。这样就可以保证事务的回滚,就可以保证数据操作的原子性了。如果事务成功,则删除备份数据,提交修改。

预写式日志(Write Ahead Log - WAL)是保证数据完整性的一种标准方法。简单来说,WAL的中心概念是数据文件(存储着表和索引)的修改必须在这些动作被日志记录之后才被写入,即在描述这些改变的日志记录被刷到持久存储以后。如果我们遵循这种过程,我们不需要在每个事务提交时刷写数据页面到磁盘,因为我们知道在发生崩溃时可以使用日志来恢复数据库:任何还没有被应用到数据页面的改变可以根据其日志记录重做(这是前滚恢复,也被称为REDO)。

使用WAL可以显著降低磁盘的写次数,因为只有日志文件需要被刷出到磁盘以保证事务被提交,而被事务改变的每一个数据文件则不必被刷出。日志文件被按照顺序写入,因此同步日志的代价要远低于刷写数据页面的代价。在处理很多影响数据存储不同部分的小事务的服务器上这一点尤其明显。此外,当服务器在处理很多小的并行事务时,日志文件的一个fsync可以提交很多事务。

WAL也使得在线备份和时间点恢复能被支持,通过归档WAL数据,我们可以支持回转到被可用WAL数据覆盖的任何时间:我们简单地安装数据库的一个较早的物理备份,并且重放WAL日志一直到所期望的时间。另外,该物理备份不需要是数据库状态的一个一致的快照 — 如果它的制作经过了一段时间,则重放这一段时间的WAL日志将会修复任何内部不一致性。

只是记录日志文件,也会产生一个问题,如果系统需要停机维护,重启要将系统恢复到之前的状态,如果从最开始,一步步执行日志记录,要恢复的话就会特别麻烦,检查点机制就是用来解决这个问题的。每隔一段时间,就将已经完成了修改的数据作为一个新的baseline,新的修改就在这个基础上进行记录,当出现问题时候,以这个快照为基础,执行日志即可。

所以WAL一般可以分为两个部分:

  1. 在对数据进行操作之前,先写日志
  2. checkpoint 机制,保证当系统重启的时候,可以更快地恢复。

注意到WAL是一种方法论,所以NIFI需要自己实现这个过程WAL,从NIFI的文档中我们知道,NIFI的 FlowFile Repository 是WAL 的具体实现。

可能概念上都很清楚,NIFI的文档也都或多或少有讲,本文从代码层面,详细探究下WAL机制的具体实现。

对于NIFI来说,数据可以分为两部分,一个是界面上拖拖拽拽之后形成的流程图,以及各种配置数据,第二个是处理器启动之后,在队列中生成的数据文件。第一种的话,NIFI是将其持久化在了NIFI/conf/flow.xml.gz 文件中,每次界面的更新,都会生成一个新的文件,并将旧文件放入了conf/archive 中。本文重点关注的是第二种。

二、FlowFile Repository

 NIFI的界面上的 flowfile 称为流文件,流文件分为两块内容,属性(attributes)和内容(content。为了运行效率,所有等待处理器处理的流文件,会放内存的hash map中,,内容部分则是指向内容仓库(Content Repository)的指针,需要的时候,才会将其从内容仓库中读进内存。内容仓库的部分后续会详细介绍。

红框当中,就是存放流文件属性和内容指针的地方。NIFI页面上的每个待处理任务就对应一个StandardFlowFileRecord  实例

FlowFile Repository 仓库会在流文件更新之前,先记录此次修改的元数据,再去内存中同步。如果处理数据的过程中,突然出问题导致系统崩溃,则在重启的时候,通过日志恢复到之前的状态。

概念理解了,究竟如何实现的?

三、源码分析

。前文提到,StandardProcessSession 封装了对流文件进行操作,我们以流文件的新建为例,看一看都做了哪些操作。

这个空参的create方法,应用在无需上流连接的处理器上,这里创建了两个对象,一个是flowFile,另外一个是 StandardRepositoryRecord。它就是仓库记录志记录,可以认为是 flowFile的 元数据。

这里可以逐项解释下成员变量的作用

1、RepositoryRecordType type

操作的类型,可选值有:

2、FlowFileRecord workingFlowFileRecord

该日志对应的流文件,如果看代码的话会发现,只有在新建 StandardFlowFileRecord实例的时候(不止在create方法调用的时候会创建),会创建一个新的 StandardRepositoryRecord 对象StandardRepositoryRecord  通过该 workingFlowFileRecord 字段,与StandardFlowFileRecord 实现1对1的对应关系

换句话说,对于NIFI的前端与处理器来说,更多的关注的是FlowFile概念,但是对WAL来说,它更关心 RepositoryRecord。

3 Relationship transferRelationship

指明流文件需要流向哪个关系

4、FlowFileQueue destination

指明流文件需要具体到哪个队列去。这里插一下4和3的区别。看下图就明白:

Relationship  是 success, FlowFileQueue 是两个不同的连线。后面会具体解释。

4、originalFlowFileRecord

源流文件,当前的流文件,是在之前哪个流文件的基础上变动的。

5、originalQueue

之前所在队列

6、swapLocation

看文档可以知道,界面上的待处理队列,是有置换机制的,当满足条件的时候,队列中的内容,会被置换进磁盘

大致说下:流文件的属性位置,存在于两个地方,WAL日志和内存。内存里边持有所有在用流文件的引用,这个对象就是处理器用的那个,实际应该是保存在连接队列当中,处理器只需要从队列中去拿就可以了。

当流文件发生改变的时候,元数据被写入WAL日志,同时内存对象也发生相应的修改,以此保证处理执行的高效,同时当session提交的时候可以记录流文件的变动记录。

类似于操作系统在内存资源不足的时候,会把内存置换到硬盘上,当NIFI中的队列数量超过配置文件中配置的阈值的时候,NIFI会把队列中低优先级的流文件置换到磁盘上的SWAP文件,每次1万条记录,然后被置换的流文件,就会从内存中移除,并在适当的时候置换进内存。以此方式减少资源的占用。

swapLocation 就是当流文件被置换出去的时候,磁盘文件的位置信息。

7、originalAttributes 

 流文件原来的属性 和它的值

8、updatedAttributes

更新后的流文件属性,和对应的值

9、transientClaims

使用机制后续补充

10、startNanos

操作时间

通过这些属性,就完整记录了流文件发生变化的时候,具体的变化时间和变化内容。同时在界面上查看流文件详情的时候,就能看到这些值,同时达到支持流文件重放的操作。

在session中,处理器可以从上游的连接中获取一个流文件,新建一个流文件,读取、修改流文件的属性和内容,路由这个流文件,在进行这些操作的时候,都会记录一个日志。在session中提交之后,这些变更会更新到流文件仓库当中。

现在有这样一个流程:

 GenerateFlowFile 处理器生成了10000条记录,并把它放入 success 中。那么在代码中,在处理器的实现代码中, 要调用session的create方法,write方法,transfer方法,生成三条日志记录,并最后通过commit 将修改日志提交到 FlowFile Repository。这里边有四步操作:

  • 第一步、345 行,判断当前线程是否被结束,如果结束的话,回滚所有内容。
  • 第二步、346 行,在路由flowfile的时候,只是告诉流文件需要到哪个关系,基于前边所讲,一个关系可能有多个队列,在这里判断具体将要去哪个队列去。
  • 第三步、347行,将修改提交到flowfile repos,将流文件路由到具体的队列中。
  • 第四步、348行,清空提交的内容准备下次提交

第二步的关键代码:

先是获取到该关系连的有哪些队列,判断关系是不是需要自动结束,判断关系是不是连回了处理器自己,都正常 则继续往下走。后续的代码更容易理解:

 根据关系对应的队列的数量,clone记录,同时生成相应的日志记录。

最后给checkpoint赋值,静态内部类checkpoint中存放的是本次提交需要做的操作,回滚也是依据这个静态内部类checkpoint中的值来决定回滚哪些操作。

说下这个静态内部类checkpoint:每个线程都会有创建一个session 实例,但是处理器运行时候,是可以多次进行commit操作的,并不只是运行结束自动提交的一次。所以这个静态内部类,来保存当次提交的数据。但处理器一次调度,产生的统计类数据,则保存在session中。

下面看第三步的关键代码:

这个是把生成的日志记录更新到日志仓库

后续则是更新内存中在用的flowfile,比如这一段:

 先把record按照  Destination 也就是要去的队列分组,然后for循环把记录放入队列。

其实看到这里的时候,我心里就有了疑问:

还是以前文中的这张图为例

如果根据前边的流程走下来,生成的仓库记录 StandardRepositoryRecord

的 originalQueue 应该是空,destinationQueue 指向的才是目标队列呀。那这时候如果我把NIFI停掉,仓库记录持久化到磁盘的数据在NIFI恢复的时候,又是怎么让流文件回到正确的队列呢?

带着这个疑问,我们正式进入仓库去看看 FlowFileRepository 的代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值