上篇文章理清楚了NIFI WAL机制实现的大致流程,但是想一想的话,还只是实现了在操作数据之前先写日志,但是每次每次修改都写日志,都得持久化到文件中,那效率这块儿是如何保证的呢?
带着这个疑问,继续跟着上篇文章暂时搁置的 update 方法来一探究竟。
进入到 SequentialAccessWriteAheadLog 成员变量 journal
在checkpoint的时候,会对它进行更新,同时把 streamPool 也传了进去。
这里使用了对象池化技术,因为应该是这个对象创建的时候,还是比较耗时的。
这里只有两个方法,从对象池中获取对象,另一个是把对象还回对象池。
来看下对象池中存放的对象:
ByteArrayOutputStream 是用来把数据写入类型为字节数组的缓存中
DataOutputStream 可以很方便地让应用把原始的java类型写入输出流。同时可以注意到,这个DataOutputStream 最终也是要写入到ByteArrayOutputStream 的缓存中的。
接着看update方法,实现选择 LengthDelimitedJournal
进入红框中的 serializeEdit() 方法,具体的实现选这个:
这个方法看着就比较直观了,就是按照顺序往流中写入record的各个部分了,注意到 81-84 的注释
如果record中有目的连接,那么这个连接就是与这条记录相关联的连接,,启动时,我们将会把这个连接作为flowfile的源连接,因为我们是在把流文件路由到队列之前,对流文件序列化的。所以在写入时候,当流文件 Destination 不为空,日志中序列化的是 Destination 。反序列化的时候,把它作为流文件的 originalQueue ,这样,序列化入文件的队列就能回到界面上它应该在的位置。以下是反序列化的代码:
这样就解答了我们之前的疑问,NIFI初始化的时候是如何让流文件恢复到它所在的位置的。
这个时候,数据写入到了 ByteArrayOutputStream 的字节数组缓存中。
这个out 指向了journal文件,baos.writeTo(out),最终把数据写入了文件,就是下边这个文件。
这里边还有一个细节:
这个判断,当缓存中的数据大小,超过了设置的阈值的时候,会把后续的内容,写入overflow文件。
所以这里可以看到,WAL把更新日志,顺序地写入了单个日志文件中,单个文件的顺序写操作,肯定要快于对实际数据的随机更新。注释中也有提到,速度是充分利用了磁盘的写入速度的。