基于本地的高性能文件队列实现

架构小魔方 2024-07-05 17:52:31

在目前很多微服务架构的服务之间的异步解耦均是通过消息MQ的方式去实现的,消息MQ这种基于生产者-消费者广泛应用于服务中,在JDK里面也有很多基于内存方面的队列,还有基于Disruptor的高性能内存队列,然而有时候使用基于MQ中间件消息队列又显得过于笨重,使用内存队列又存在着数据丢失的问题,因此有没有一种能像内存队列一样整合在本地,又在一定程度保证数据不丢失的队列呢?本文分享一种基于文件使用的队列。

文件队列的存储机制

参考了基于Rocketmq的存储结构使用基于数据存储+索引文件的方式去实现,数据文件由多个文件连接组成,而索引文件IndexFile记录当前读和写的文件名以及文件偏移量。

在文件读写方面,为了追求极致的读写性能,参考了Rocketmq的顺序读写的方式,目前主流的写文件的方式有BufferedOutputStream,RandomAccessFile,MappedByteBuffer三种方式。

通过循环针对于10字节循环1000千万次进行基准测试,发现BufferedOutputStream速度最快,但是它是基于JVM堆内内存,存在着数据丢失的风险,而RandomAccessFile写入数据最慢,但是数据可靠性最高,MappedByteBuffer使用的是堆外内存,采用的是操作系统层面的内存映射,速度较快,数据可靠性也有一定的保障,也是Rocketmq采用的方式,因此综合考虑,采用MappedByteBuffer更合适这种文件队列的方式。

文件队列核心类

FileQueue:由FileQueue实现JDK的Queue接口,使其对外使用JDK提供统一的API接口,方便使用者可在内存队列和文件队列进行灵活的切换.

FileQueueServiceImpl:具体的文件队列内部实现类,它持有文件数据读和写FileEntity以及索引文件FileIndex.

为了避免文件队列在写溢出的时候能提前创建文件,会采用定时调度器提前进行预创建文件FileAddRunner,也为了避免消费完成的文件占用不必要的存储空间将使用FileDelRunner定时的去清理文件。FileEntity会持有实际的对应的读写文件对象File。写入的内容最终也会转换成为byte[],消息格式会变成【偏移值+内容长度+内容】。

FileInex:文件队列的索引文件,持有对应的索引文件File,采用RandomAccessFile进行读写,一个消息索引占用20个字节,记录了:读的位置(4位)+写的位置(4位)+读的文件编号(4位)+写的文件编号(4位)+队列大小(4位)。

总结

整个基于文件队列的实现就是一个简易版本的Rocketmq,部分的设计思想也是参考了Rokcetmq,比如文件的存储采用Data+Index的方式,文件的读写采用了MappedByteBuffer等等,基于这种实现的方式,其性能基本接近于内存操作,在面临一些既不想整合类似于Rocketmq这种重量级的消息中间件,也不想担心数据在内存中会丢失的场景,可以考虑使用基于这种文件实现的队列的方式,让整个服务能享受到异步队列带来的解耦优势,同时还能保证数据的可靠性。

0 阅读:1

架构小魔方

简介:感谢大家的关注