Page Cache和MappedByteBuffer

  参考自 https://www.cnblogs.com/zhaoyl/p/5515317.html

一  Page Cache和 mmap

  关于kakfa的高性能是怎么实现的问题,网上的答案很多但大多数都会有一条,Page Cache,我们今天就来看看Page Cache

  page cache。内核会为每个文件单独维护一个page cache,用户进程对于文件的大多数读写操作会直接作用到page cache上,内核会选择在适当的时候将page cache中的内容写到磁盘上(当然我们可以手工fsync控制回写),这样可以大大减少磁盘的访问次数,从而提高性能。Page cache是linux内核文件访问过程中很重要的数据结构,page cache中会保存用户进程访问过得该文件的内容,这些内容以页为单位保存在内存中,当用户需要访问文件中的某个偏移量上的数据时,内核会以偏移量为索引,找到相应的内存页,如果该页没有读入内存,则需要访问磁盘读取数据。为了提高页得查询速度同时节省page cache数据结构占用的内存,linux内核使用树来保存page cache中的页。

     在了解了以上的基础之后,我们就来比较一下mmap和read/write的区别,先说一下read/write系统调用,read/write系统调用会有以下的操作:

  1. 访问文件,这涉及到用户态到内核态的转换
  2. 读取硬盘文件中的对应数据,内核会采用预读的方式,比如我们需要访问100字节,内核实际会将按照4KB(内存页的大小)存储在page cache中
  3. 将read中需要的数据,从page cache中拷贝到用户缓冲区中

     整个过程还是比较艰辛的,基本上涉及到用户内核态的切换,还有就是数据拷贝接下来继续说mmap吧,mmap系统调用是将硬盘文件映射到用内存中,说的底层一些是将page cache中的页直接映射到用户进程地址空间中,从而进程可以直接访问自身地址空间的虚拟地址来访问page cache中的页,这样会并涉及page cache到用户缓冲区之间的拷贝,mmap系统调用与read/write调用的区别在于:

  1. mmap只需要一次系统调用,后续操作不需要系统调用
  2. 访问的数据不需要在page cache和用户缓冲区之间拷贝

  总结下来说

  1 page cache底层所用的技术就是mmap,mmap通俗的说就是把磁盘上文件映射到虚拟内存上,用户进程写这块虚拟内存,而系统负责落盘。

  2 如果不用mmap,用户态写了一块内存之后,这块内存要复制到内核态,内核态才能落盘,比如要从JVM的堆一块内存复制到内核态的读写缓冲区

  3 如果采用的是Kafka的同步提交 也就是acks = 1 或者 -1 ,leader在写日志的时候,page cache中的数据会在每次write之后,会立即调用fsync立即将page cache中的内容回写到磁盘中。

  4 Kafka使用了Page Cache,而 Page Cache用到了 mmap

  5 linux官方说明,MMAP文件不能超过1.5G,所以就不难理解为啥每个日志段是1G了

二  JAVA 中的 mmap

  java中使用mmap就要靠MappedByteBuffer 

  RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        //获取对应的通道
        FileChannel channel = randomAccessFile.getChannel();

        /**
         * 参数1: FileChannel.MapMode.READ_WRITE 使用的读写模式
         * 参数2: 0 : 可以直接修改的起始位置
         * 参数3:  5: 是映射到内存的大小(不是索引位置) ,即将 1.txt 的多少个字节映射到内存
         * 可以直接修改的范围就是 0-5
         * 实际类型 DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        mappedByteBuffer.put(0, (byte) 'H');
        mappedByteBuffer.put(3, (byte) '9');
        mappedByteBuffer.put(5, (byte) 'Y');//IndexOutOfBoundsException
      Thread.sleep(10000);//我增加10s,结果发现很快就能落盘了,不过我是在windows平台做的测试
        randomAccessFile.close();
        System.out.println("修改成功~~"); //看到了吗 根本不用做什么写入操作
    
// 在关闭资源时执行以下代码释放内存
Method m = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class);
m.setAccessible(true);
m.invoke(FileChannelImpl.class,mappedByteBuffer);

  上面的代码我跑了一下结果是这样的

   HNULNUL9NUL  用NodePad++打开的

  其实 MappedByteBuffer  只是一个抽象类,实际返回的都是DirectByteBuffer,关于DirectByteBuffer要讲的就太多了,之后会单独写一篇的。

posted on 2020-12-04 15:50  MaXianZhe  阅读(545)  评论(0编辑  收藏  举报

导航