Java实现磁盘的顺序读写

想实现磁盘顺序写的原因:

最近在研究Kafka的过程中,发现kafka性能好的原因之一就是数据的最终落盘采用了磁盘的顺序读写,从各种博客和官方说法来看,磁盘的顺序读写的性能是磁盘的随机读写性能的几千倍,所以就在想,如果我要去开发一个中间件,最终存储也采用顺序读写有没有办法能用我熟悉的JAVA语言来实现这个东西呢。

解决问题过程:

首先我去查询了JDK1.8的api文档,查看了IO包和NIO包下的一些类,发现IO下的类的介绍几乎都是随机读写的,NIO也主要是采用通道和directBytebuffer来提升性能,没能给我灵感,突然我想到了RocketMq是借鉴了Kafka的原理然后采用JAVA编写的,而且RocketMq的存储也是文件系统,然后我去查看了RocketMq的官网以及GitHub并参考了部分博客,最终发现Java实现磁盘的顺序读写主要是两个类,一个是IO包下的RandomAccessFile类和MappedByteBuffer内存映射的类来实现的,实现方案如下:

 /**
     *  顺序写
     * @param filePath
     * @param content
     * @param index
     * @return
     */
    public static long fileWrite(String filePath, String content, int index) {
        File file = new File(filePath);
        RandomAccessFile randomAccessTargetFile;
        //  操作系统提供的一个内存映射的机制的类
        MappedByteBuffer map;
        try {
            randomAccessTargetFile = new RandomAccessFile(file, "rw");
            FileChannel targetFileChannel = randomAccessTargetFile.getChannel();
            map = targetFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, (long) 1024 * 1024 * 1024);
            map.position(index);
            map.put(content.getBytes());
            return map.position();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        return 0L;
    }

    /**
     * 顺序读
     * @param filePath
     * @param index
     * @return
     */
    public static String fileRead(String filePath, long index) {
        File file = new File(filePath);
        RandomAccessFile randomAccessTargetFile;
        //  操作系统提供的一个内存映射的机制的类
        MappedByteBuffer map;
        try {
            randomAccessTargetFile = new RandomAccessFile(file, "rw");
            FileChannel targetFileChannel = randomAccessTargetFile.getChannel();
            map = targetFileChannel.map(FileChannel.MapMode.READ_WRITE, 0, index);
            byte[] byteArr = new byte[10 * 1024];
            map.get(byteArr, 0, (int) index);
            return new String(byteArr);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
        return "";
    }

PageCache与Mmap内存映射:

系统的所有文件I/O请求,操作系统都是通过page cache机制实现的。对于操作系统来说,磁盘文件都是由一系列的数据块顺序组成,数据块的大小由操作系统本身而决定,x86的linux中一个标准页面大小是4KB。操作系统内核在处理文件I/O请求时,首先到page cache中查找(page cache中的每一个数据块都设置了文件以及偏移量地址信息),如果未命中,则启动磁盘I/O,将磁盘文件中的数据块加载到page cache中的一个空闲块,然后再copy到用户缓冲区中。
page cache本身也会对数据文件进行预读取,对于每个文件的第一个读请求操作,系统在读入所请求页面的同时会读入紧随其后的少数几个页面。因此,想要提高page cache的命中率(尽量让访问的页在物理内存中),从硬件的角度来说肯定是物理内存越大越好。从操作系统层面来说,访问page cache时,即使只访问1k的消息,系统也会提前预读取更多的数据,在下次读取消息时, 就很可能可以命中内存。
在page cache机制的预读取作用下,磁盘读性能会比较高近乎内存,即使在有消息堆积情况下也不会影响性能。如果选择合适的系统IO调度算法,比如设置调度算法为“Noop”(此时块存储采用SSD的话),随机读的性能也会有所提升。
NIO中的FileChannel模型直接将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(这里需要注意的是,采用MappedByteBuffer这种内存映射的方式有几个限制,其中之一是一次只能映射1.5~2G 的文件至用户态的虚拟内存)。

最终总结:

学习的过程中,去触类旁通,并且去深究原理,会发现在计算机整体的架构没有改变的情况下,所有出现的第三方优秀框架和中间件的底层知识都是差不多的,只不过采用了不同的思想去应付不同的场景,所以就出现了比如缓存的优秀中间件:redis/mongDb,消息中间件的:kafka/rocketMq等等,认真学习会发现不断出现的新知识其实有没那么难了,加油/努力!

参考博客:https://www.jianshu.com/p/b73fdd893f98
参考博客:http://blog.laofu.online/2020/02/02/2020-02-02-java-io-operate/

你可能感兴趣的:(JAVA的趣闻,kafka,java,大数据)