Flume - FileChannel (一)
概述
当使用Flume的时候,每个流程都包含了输入源、通道和输出。一个典型的例子是一个web服务器将事件通过RPC(搬入AvroSource)写入输入源中,输入源将其写入MemoryChannel,最后HDFS Sink消费事件将其写入HDFS中。
MemeoryChannel提供了高吞吐量但是在系统崩溃或者断电时会丢失数据。因此需要开发一个可持久话通道。FileChannel是在FLUME-1085里实现的。目标是提供一个高可用高吞吐量的通道。FileChannle保证了在失误提交之后,在崩溃或者断电后不丢失数据。
需要注意的是FileChannel自己不做任何的数据复制,因此它只是和基本的磁盘一样高可用。使用FileChannle的用户需要购买配置更多的硬盘。硬盘最好是RAID、SAN或者类似的。
很多需要通过损失少量的数据(每隔几秒将内存数据fsync到硬盘)换取高吞吐量。Flume团队决定使用另一种方式实现FileChannel。Flue是一个事务型的系统,在一次存或取的事务中可以操作多个事件。通过改变批量大小来控制吞吐量。使用大的批量,Flume可以以比较高的吞吐量传送数据,同时不丢失数据。批量的大小完全由客户端控制。使用RDBMS的用户对这种方式会比较熟悉。
一个Flume事务由存或取组成,但不能同时做两种操作,同样提交和回滚也是一样。每个事务实现了存和取的方法。数据源将事件存入通道,输出从通道中将事件取出。
设计
FileChannel在WAL(预写式日志)的基础上添加了一个内存队列。每个事务都被写成一个基于事务类型(存或取)的WAL,内存队列也相应的被更新。每次是事务提交,正确的文件被fsync保证数据被真正地保存到磁盘上,同时该事件的指针也被保存到了内存队列中。这个队列提供的功能跟其他队列没有区别:管理那些还没有被输出消费的事件。在取的过程中,指针被从队列中删除。事件直接从WAL中读取。得益于当前大容量的RAM,从操作系统的文件缓存中读取很常见。
在系统崩溃之后,WAL可以被重现到队列中保持原来的状态,没有被提交的事务会丢失。重现WAL是耗时的,因此队列也被周期性地写到磁盘上。写队列到磁盘被称作checkpoint。崩溃后,从磁盘读取队列。只有队列保存到磁盘之后提交的事务被重现,这样可以显著的减少需要读取的WAL的数量。
例如,如下有两个事件的通道:
WAL包含了三个重要的元素:事务id、序列号和事件数据。每个事务都有一个唯一的事务id,每个事件都有一个唯一的序列号。事务id只被用来标识事务中的一组事件,序列号在重演日志的时候被用到。上面的例子中,事务id是1,序列号是1、2、3。
当队列被保存到硬盘后 – 一次checkpoint – 序列号自动增加并同样被保存。在重启时,队列最先被从硬盘上加载,所有序列号大于队列的WAL项被重现。在checkpoint操作时,channle被锁住以保证没有存取操作改变它的状态。如果允许修改,会导致保存到硬盘上的队列快照不一致。
上面例子中的队列,checkpoint发送在事务1提交之后,因此事件a、b的指针和序列号4被保存到硬盘。
之后,事件a在事务2中被从队列中取出:
如果这时系统崩溃,队列的checkpoint从硬盘中加载。注意这个checkpoint发生在事务2之前,事件a、b的指针存在队列中。因此WAL中序列号大于4的已提交的事务被重现,事件a指针被从队列中删除。
上面的设计有两点没提到。checkpoint时发生的存和取操作会丢失。假设checkpoint在取事件a之后发生:
如果这时系统崩溃,根据上面的设计,事件b指针保存在队列中,所有序列号大于5的WAL项被重现:事务2的回滚被重现。但是事务2的取操作不会被重现。因此事件a指针不会被放回队列因而导致数据丢失。存的场景也类似。因此在队列checkpoint的时候,进行中的事务操作也会被重现,这样这种情况能被正确处理。
实现
FileChannel被保存在flume项目的flume-file-channel模块中,他的java包名是org.apache.flume.channel.file。上面提到队列被叫做FlumeEventQueue,WAL被叫做 Log。队列是一个环形数组,使用Memory Mapped File。WAL是一组以LogFile或其子类序列化的文件。
总结
FileChannle在硬件、软件和系统故障下的持久化并同时保证高吞吐量。如果这亮点都看中的话,FileChannel是推荐使用的通道。