lucene MMapDirectory的使用

遇到一个很困惑的问题,如果把很大的索引放到内存中,会不会把内存撑爆。查了下lucene的api 发现lucene在处理几百兆的索引数据的时候已经不建议,把索引放到内存中了,从而引出了MMapDirectory的概念。

下边是MMapDirectory的一片博客


地址

从3.1版本开始,Lucene和Solr开始在64位的Windows和Solaris系统中默认使用MMapDirectory,从3.3版本开始,64位的Linux系统也启用了这个配置。这个变化使一些Lucene和Solr的用户有些迷茫,因为突然之间他们的系统的某些行为和原来不一样了。在邮件列表中,一些用户发帖询问为什么使用的资源比原来多了很多。也有很多专家开始告诉人们不要使用MMapDirectory。但是从Lucene的commiter的视角来看,MMapDirectory绝对是这些平台的最佳选择。

在这篇blog中,我会试着解释解释关于virtual memory的一些基本常识,以及这些常识是怎么被用于提升lucene的性能。了解了这些,你就会明白那些不让你使用MMapDirectory的人是错误的。第二部分我会列出一些配置的细节,可以避免出现“mmap failed”这样的错误或者由于java堆的一些特性导致lucene无法达到最优的性能。

Virtual Memory[1]

我们从操作系统的内核开始说起。从70年代开始,软件的I/O模式就是这样的:只要你需要访问disk的数据,你就会向kernal发起一个syscall,把一个指向某个buffer的指针传进去,然后读或者写磁盘。如果你不想频繁的发起大量的syscall,(因为用户进程发起syscall会消耗很多资源),你应该使用较大的buffer,这样每次多读一些,访问磁盘的次数也就少了。这也是为什么有人建议把Lucene的整个index都load到Java heap中的一个原因(使用RAMDirectory)。

但是所有的现代操作系统,像Linux,Windows(NT+), Mac OS X, 以及solaris都提供了一个更好的方式来完成I/O:他们用复杂的文件系统cache和内存管理来帮你buffer数据。其中最重要的一个feature叫做Virtual Memory,是一个处理超大数据(比如lucene index)的很好的解决方案。Virtual Memory是计算机体系结构的一个重要部分,实现它需要硬件级的支持,一般称作memory management unit(MMU),是CPU的一部分。它的工作方式非常简单:每个进程都有独立的虚拟地址空间,所有的library,堆,栈空间都映射在这个虚拟空间里。在大多数情况下,这个虚拟地址空间的起始偏移量都是0,在这里load程序代码,因为程序代码的地址指针不会变化。每个进程都会看到一个大的,不间断的先行地址空间,它被称为virtual memory,因为这个地址空间和physical memory没有半毛钱关系,只是进程看起来像memory而已。进程可以像访问真实内存一样访问这个虚拟地址空间,也不需要关心与此同时还有很多其他进程也在使用内存。底层的OS和MMU一起合作,把这些虚拟地址映射到真实的memory中。这个工作需要page table的帮助,page table由位于MMU硬件里的TLBs(translation lookaside buffers, 它cache了频繁被访问的page)支持。这样,OS可以把所有进程的内存访问请求发布到真实可用的物理内存上,而且对于运行的程序来说是完全透明的。

Schematic drawing of virtual memory
(image from Wikipedia [1]http://en.wikipedia.org/wiki/File:Virtual_memory.svg, licensed by CC BY-SA 3.0)

使用了这样的虚拟化之后,OS还需要做一件事:当物理内存不够的时候,OS要能决定swap out一些不再使用的pages,释放物理空间。当一个进程试着访问一个page out的虚拟地址时,它会再次被reload进内存。在这个过程里,用户进程不需要做任何事情,对进程来说,内存管理是完全透明的。这对应用程序来说是天大的好事,因为它不必关心内存是否够用。当然,这对于需要使用大量内存的应用,比如Lucene,也会来带一些问题。

 

Lucene & Virtual Memory

我们来看一个例子,假设我们把整个的索引load进了内存(其实是virtual memory)。如果我们分配了一个RAMDirectory,并且把所有的索引文件都load进去了,那么我们其实违背了OS的意愿。OS本身是会尽力优化磁盘访问,所以OS会在物理内存中cache住所有的磁盘IO。而我们把这些所有本应cache住的内容copy到了我们自己的虚拟地址空间了,消耗了大量的物理内存。而物理内存是有限的,OS可能会把我们分配的这个超大的RAMDirectory踢出物理内存,也就是放到了磁盘上(OS swap file)。事实上,我们是在和OS kernel打架,结果就是OS把我们辛辛苦苦从磁盘上读取的数据又踢回了磁盘。所以RAMDirectory并不是优化索引加载时耗的好主意。而且,RAMDirectory还有一些和GC以及concurrency相关的问题。因为数据存储在swap space,JAVA的GC要清理它是很费劲的。这会导致大量的磁盘IO,很慢的索引访问速度,以及由于GC不方便而导致的长达数分钟的延迟。

 

如果我们不用RAMDirectory来缓存index,而是使用NIOFSDirectory或者SimpleFSDirectory,会有另外的问题:我们的代码需要执行很多syscall来拷贝数据,数据流向是从磁盘或文件系统缓存向Java heap的buffer。在每个搜索请求中,这样的IO都存在。

 

Memory Mapping Files

上面问题的解决方案就是MMapDirectory,它使用virtual memory和mmap来访问磁盘文件。

在本文前半部分讲述的方法,我们都是依赖系统调用在文件系统cache以及Java heap之间拷贝数据。那么怎么才能直接访问文件系统cache呢?这就是mmap的作用!

简单说MMapDirectory就是把lucene的索引当作swap file来处理。mmap()系统调用让OS把整个索引文件映射到虚拟地址空间,这样Lucene就会觉得索引在内存中。然后Lucene就可以像访问一个超大的byte[]数据(在Java中这个数据被封装在ByteBuffer接口里)一样访问磁盘上的索引文件。Lucene在访问虚拟空间中的索引时,不需要任何的系统调用,CPU里的MMU和TLB会处理所有的映射工作。如果数据还在磁盘上,那么MMU会发起一个中断,OS将会把数据加载进文件系统Cache。如果数据已经在cache里了,MMU/TLB会直接把数据映射到内存,这只需要访问内存,速度很快。程序员不需要关心paging in/out,所有的这些都交给OS。而且,这种情况下没有并发的干扰,唯一的问题就是Java的ByteBuffer封装后的byte[]稍微慢一些,但是Java里要想用mmap就只能用这个接口。还有一个很大的优点就是所有的内存issue都由OS来负责,这样没有GC的问题。

What does this all mean to our Lucene/Solr application?

  • 为了不和OS争内存,应该给JVM分尽可能少的heap空间(-Xmx option). 索引的访问全部都交给OS cache。而且这样对JVM的GC也好。
  • 释放尽可能多的内存给OS,这样文件系统缓存的空间也大,swap的概率低。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值