分享

扩展Lucene的索引文件存储

 软件团队头目 2007-03-08

扩展Lucene的索引文件存储

2007/02/01  作者:刘冬   心情: sunny  星期四 (12:01)

本文主要叙述如何通过引入Commons-VFS项目来扩展Lucene的索引文件存储方式。在阅读本文之前,您必须对Lucene有一定的了解,最好是有编写过Lucene代码。另外文章中所提到的Lucene如果不做特殊说明指的是LuceneJava版本。

使用过Lucene来做为搜索引擎的朋友知道,Lucene默认的使用文件系统来存储索引文件。一般我们需要指定一个路径做为参数来初始化索引的读写类。例如下面语句准备在D盘的lucene_idx目录下写索引信息。

IndexWriter idx_writer = new IndexWriter(“D:/lucene_idx”,new StandardAnalyzer(),false);

因此整个系统的索引一般就会集中在某台服务器的某个目录。为了保证写索引不会产生冲突,经常我们只运行一个负责写索引的进程,其他的应用可以访问索引目录进行查询。当应用程序是集群环境下的不同机器时,其他的机器如何访问到索引服务器上的索引数据目录呢?你肯定已经想到很多种方法来访问其他机器的目录:例如Windows机器可以通过网上邻居;Linux服务器之间可以通过NFS访问;或者LinuxWindows服务器之间可以通过SAMBA来相互访问各自的文件系统。还有没有其他更加灵活的解决方案吗?

当然有,通过扩展Lucene的索引存储接口,你可以通过FTPHTTP来读写索引文件,你甚至可以将Lucene的索引数据保存在数据库中,虽然我不知道这到底是不是一个好主意,但起码技术上是可行的。

为了使我们对Lucene的扩展有着实际的意义,我们将引入另外一个Apache组织新的开源项目—— Commons-VFS,我们简称为VFSVFS项目把对各种各样的文件系统的访问封装成统一的应用程序接口,这大大的简化了应用程序本身代码的复杂度。目前VFS支持下面一些文件系统,而这个列表会越来越多,当然你也可以自行进行扩展。

FTP
Local Files
HTTP and HTTPS
SFTP
Temporary Files
Zip, Jar and Tar (uncompressed, tgz or tbz2)
gzip and bzip2
res
ram

或许你已经猜出来了引入VFS项目的目的:我们要利用VFS 项目和Lucene结合,使Lucene的索引文件可以存放在VFS所支持的各种可读写的文件系统中。

因此接下来我们的任务就是让Lucene使用VFS来存取索引数据,至于这些数据放在哪里,是放在数据库、文件系统还是其他地方,这个问题就交给VFS的配置去处理了。

你准备好了嘛?下面我们要深入到Lucene的代码中去探个究竟了。首先我们注意到了IndexReaderIndexWriterIndexSearcher等这几个索引数据的读写类的构造函数都可以接受名为org.apache.lucene.store.Directory 的类做为参数。再查看api文档发现Directory有两个子类分别是:FSDirectoryRAMDirectory,从类名上可以猜出FSDirectory是基于文件系统的,而RAMDirectory是基于内存的。再读一下IndexWriter类的源码后证实了这个猜想,当我们传递路径字符串而不是Directory对象给IndexXxxxx类时,Lucene会初始化一个FSDirectory对象来处理索引数据的读写操作。

而另外一个RAMDirectory类是基于内存的索引数据存取,它是Lucene所提供的第二种索引数据存取方式。它可以接受一个FSDirectory做为构造函数的参数,相当于把存放在磁盘中的索引数据全部加载到内存中,以提高索引搜索的性能,我们暂不理会这种方式。但我们已经了解到org.apache.lucene.store包中的类便是Lucene存储的核心,我们可以通过扩展Directory类来实现存储自定义。姑且把所要扩展的这个类命名为VFSDirectory

先来看看Directory都有什么方法:

Method Summary

abstract void

close() 关闭存储.

abstract IndexOutput

createOutput(String name) 使用给定的名字创建一个新的空文件.

abstract void

deleteFile(String name) 删除指定名字的文件.

abstract boolean

fileExists(String name) 判断文件是否存在.

abstract long

fileLength(String name) 获取文件长度.

abstract long

fileModified(String name) 返回文件最近修改的时间.

abstract String[]

list() 列出所有的文件.

abstract Lock

makeLock(String name) 构造一个锁对象.

abstract IndexInput

openInput(String name) 获取文件输入流.

abstract void

renameFile(String from, String to) 文件重命名.

abstract void

touchFile(String name) 设置文件最近修改时间为当前时间.

上面这些都是跟文件操作密切相关的方法,应该也是大家天天要打交道的文件操作方法。为了不至于误解这些方法的意思,我们不妨看看FSDirectory的源码。以下VFSDirectory类的完整代码,该类已经在实际的应用中测试无误。此类的构造函数接受两个uri做为参数,分别是存放索引文件的URI以及锁文件的URI,关于URI的格式请参考VFS项目的文档。性能方面由于采用了跟FSDirectory一致的BufferedIndexInputBufferedIndexOuput,因此二者并没有太大区别。

package dlog.common.search;

import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.RandomAccessContent;
import org.apache.commons.vfs.VFS;
import org.apache.commons.vfs.util.RandomAccessMode;
import org.apache.lucene.store.BufferedIndexInput;
import org.apache.lucene.store.BufferedIndexOutput;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.Lock;

/**
*
扩展Lucene索引的存储,使用commons-vfs做为lucene的存储引擎
* @author Winter Lau
* @email javayou@gmail.com
* @home http://www./javayou
*/

public class VFSDirectory extends Directory {
private FileSystemManager fileManager;
private FileObject directory;
private FileObject locker;

public VFSDirectory(String repository_uri, String lock_uri) throws IOException {
this.fileManager = VFS.getManager();
this.directory = fileManager.resolveFile(repository_uri);
if(!directory.exists())
directory.createFolder();
this.locker = fileManager.resolveFile(lock_uri);
if(!locker.exists())
locker.createFolder();
}

@Override
public void close() throws IOException {
directory.close();
}

protected void finalize() throws IOException {
close(); // close the directory
}

@Override
public IndexOutput createOutput(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
return new VFSIndexOutput(file);
}

@Override
public IndexInput openInput(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
return new VFSIndexInput(file);
}

@Override
public void deleteFile(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
//System.out.println(file.getName().getFriendlyURI());
if(!file.delete())
throw new IOException("Cannot delete " + file);
}

@Override
public boolean fileExists(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
return file.exists();
}

@Override
public long fileLength(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
return file.getContent().getSize();
}

@Override
public long fileModified(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
return file.getContent().getLastModifiedTime();
}

@Override
public String[] list()throws IOException {
List names = new ArrayList();
FileObject[] files = directory.getChildren();

for(int i=0;i

@Override
public Lock makeLock(String name) {

final StringBuffer buf = getLockPrefix();
buf.append("-");
buf.append(name);

try{

final FileObject lockFile = this.fileManager.resolveFile(locker, buf.toString());

return new Lock() {
public boolean obtain() throws IOException {
lockFile.createFile();
return lockFile.exists();
}

public void release() {
try {
lockFile.delete();
} catch (FileSystemException e) {
throw new RuntimeException("Cannot release lock : " + buf.toString(), e);
}
}

public boolean isLocked() {
try {
return lockFile.exists();
} catch (FileSystemException e) {
throw new RuntimeException("Cannot check locking status : " + buf.toString(), e);
}
}

public String toString() {
return "Lock@" + lockFile;
}
};
} catch (IOException e){
throw new RuntimeException("Request lock failed : " + buf.toString(), e);
}
}

private static MessageDigest DIGESTER;

/**
* So we can do some byte-to-hexchar conversion below
*/
static char[] HEX_DIGITS = { ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘, ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘ };

static {
try {
DIGESTER = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.toString(), e);
}
}

private StringBuffer getLockPrefix() {

String dirName; // name to be hashed

try {
dirName = directory.getURL().toString();
} catch (IOException e) {
throw new RuntimeException(e.toString(), e);
}

byte digest[];

synchronized (DIGESTER) {
digest = DIGESTER.digest(dirName.getBytes());
}

StringBuffer buf = new StringBuffer();
buf.append("lucene-");
for (int i = 0; i < digest.length; i++) {
int b = digest[i];
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
buf.append(HEX_DIGITS[b & 0xf]);
}
return buf;
}

@Override
public void renameFile(String name1, String name2) throws IOException {
FileObject file1 = this.fileManager.resolveFile(directory, name1);
FileObject file2 = this.fileManager.resolveFile(directory, name2);
try{
IOUtils.copy(file1.getContent().getInputStream(), file2.getContent().getOutputStream());
}finally{
close(file2);
close(file1);
}
}

@Override
public void touchFile(String name) throws IOException {
FileObject file = this.fileManager.resolveFile(directory, name);
file.getContent().setLastModifiedTime(System.currentTimeMillis());
}

private void close(FileObject file){
if(file!=null){
try{
file.close();
}catch (IOException e) {
throw new RuntimeException("Cannot close input stream: " + e.toString(), e);
}
}
}

private static class VFSIndexInput extends BufferedIndexInput {

private long length;
private RandomAccessContent content;

public VFSIndexInput(FileObject file) throws IOException {
this.content = file.getContent().getRandomAccessContent(RandomAccessMode.READ);
this.length = content.length();
}

@Override
public void close() throws IOException {
this.content.close();
}

@Override
public long length() {
return length;
}

@Override
protected void readInternal(byte[] b, int off, int len) throws IOException {
this.content.readFully(b, off, len);
}

@Override
protected void seekInternal(long position) throws IOException {
this.content.seek(position);
}
}

private static class VFSIndexOutput throws BufferedIndexOutput {

private RandomAccessContent content;

public VFSIndexOutput(FileObject file) throws IOException{
if(!file.exists())
file.createFile();
this.content = file.getContent().getRandomAccessContent(RandomAccessMode.READWRITE);
}

@Override
protected void flushBuffer(byte[] b, int size) throws IOException {
this.content.write(b, 0, size);
}

@Override
public void seek(long pos) throws IOException {
super.seek(pos);
//此行代码非常重要,否则会导致写文件错误
this.content.seek(pos);
}

@Override
public long length() throws IOException {
return this.content.length();
}

@Override
public void close() throws IOException {
super.close();
this.content.close();
}

protected void finalize() throws IOException {
close(); // close the file
}
}
}

有了VFSDirectory这个类后,使用就非常方便了。我们只需要构造一个VFSDirectory对象,并传递给IndexReaderIndexWriterIndexSearcher等即可。为了方便各位读者进行测试,本文中的所有代码请看参考资料中的源码下载。

完成了VFSDirectory后,就相当于给Lucene注入更多的功力。任何VFSDirectory所支持的可读写文件系统都可以被Lucene用来存放索引数据。如果是VFS本身不支持的文件系统我们也可以通过扩展VFS来实现对Lucene的扩展。

最后补充一点关于本文所涉及的源码的使用说明,编译源码需要lucene以及vfs包的支持,编译成功后直接运行TestLuceneVFS类即可进行测试。

参考资料:

源码下载地址:http://www./uploads/files/lucene_and_vfs.zip
Lucene
官方网站:http://lucene.
Commons-VFS
项目网站:http://jakarta./commons/vfs

关于作者:

刘冬,一直使用J2EE/J2ME从事移动互联网方面的开发。您可以通过Java自由人网站来跟他联系,网址是:http://www./javayou ,另外他的邮件地址是javayou@gmail.com
关键字: Lucene  VFS 
引用地址: http://www./html/trackback.do?id=5903&type=1 (复制地址)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多