IndexDeletionPolicy以及多次commit
关于索引删除的策略IndexDeletionPolicy
IndexDeletionPolicy是一个接口,是有关在建立索引的过程中,对索引文件进行灵活地(删除)操作的一种自定义接口。可以在合适的时机进行删除操作,可以指定删除的时刻,完全可以根据自己的需要进行定制,但是,这可能会造成存储开销,但是相对于删除操作策略的灵活便利性,这也是比较值得的。
IndexDeletionPolicy接口的定义非常简单,而且为我们做了启发式的指引:
package org.apache.lucene.index;
import java.util.List;
import java.io.IOException;
// 该接口示例了两种删除索引文件的时机,即所谓的Points
public interface IndexDeletionPolicy {
// 一个IndexWriter索引器初始化的时候,可以删除一些时刻(提交点)提交的索引文件,这些提交点存放在一个List中,包括最早提交点和最近的提点之间的所有提交点,当然,删除他们是可选的,而且应当慎重,因为一旦删除便不可回滚。
public void onInit(List commits) throws IOException;
// 当新建的索引写入目录,并已经提交的时候,我们仍然可以删除指定List中一些时刻(提交点)提交的索引文件,当然,删除他们是可选的,而且应当慎重
public void onCommit(List commits) throws IOException;
}
通过对IndexDeletionPolicy接口的实现类进行学习,把握最基本的索引删除的动机和行为。通过IndexDeletionPolicy接口的实现类的类名就可以大概了解到它的索引删除策略:
1、KeepOnlyLastCommitDeletionPolicy策略
KeepOnlyLastCommitDeletionPolicy类在初始化一个IndexWriter的时候,在init方法中如果指定IndexDeletionPolicy deletionPolicy为null,则默认的索引删除策略为KeepOnlyLastCommitDeletionPolicy。
KeepOnlyLastCommitDeletionPolicy类的源代码非常容易理解,如下所示:
package org.apache.lucene.index;
import java.util.List;
public final class KeepOnlyLastCommitDeletionPolicy implements IndexDeletionPolicy {
// 初始化IndexWriter的时候,除了保留最近的一个提交点以外,删除所有提交点提交的索引文件
public void onInit(List commits) {
// Note that commits.size() should normally be 1:
onCommit(commits);
}
// 除了最近时刻的提交点保留以外,其余的全部删除
public void onCommit(List commits) {
// Note that commits.size() should normally be 2 (if not
// called by onInit above):
int size = commits.size();
for(int i=0;i<size-1;i++) { // IndexCommitPoint也很重要,因为它涉及到了索引段,比较复杂,在后面系统学习
((IndexCommitPoint) commits.get(i)).delete();
}
}
}
2、KeepNoneOnInitDeletionPolicy策略
初始化时删除所有提交点的索引段,最后提交的时候,保留最近提交点的索引段。
class KeepNoneOnInitDeletionPolicy implements IndexDeletionPolicy {
int numOnInit;
int numOnCommit;
public void onInit(List commits) {
verifyCommitOrder(commits);
numOnInit++;
// 初始化的时候,就删除所有的提交点
Iterator it = commits.iterator();
while(it.hasNext()) {
((IndexCommitPoint) it.next()).delete();
}
}
public void onCommit(List commits) {
// 验证每个提交点提交的索引文件(索引段)名称的正确性
verifyCommitOrder(commits);
int size = commits.size();
// 除了最近时刻的提交点保留以外,其余的全部删除
for(int i=0;i<size-1;i++) {
((IndexCommitPoint) commits.get(i)).delete();
}
numOnCommit++;
}
}
3、KeepAllDeletionPolicy策略
保留所有提交点,不删除任何提交点的索引段。
class KeepAllDeletionPolicy implements IndexDeletionPolicy {
int numOnInit;
int numOnCommit;
public void onInit(List commits) {
verifyCommitOrder(commits);
numOnInit++;
}
public void onCommit(List commits) {
verifyCommitOrder(commits);
numOnCommit++;
}
}
4、KeepLastNDeletionPolicy策略
初始化时,不删除任何提交点;最后提交时,保留指定的最近的N个提交点。
class KeepLastNDeletionPolicy implements IndexDeletionPolicy {
int numOnInit;
int numOnCommit;
int numToKeep;
int numDelete;
Set seen = new HashSet(); // Set seen用于保证不重复删除某个提交点
public KeepLastNDeletionPolicy(int numToKeep) { // 初始化显式指定最近提交点的个数
this.numToKeep = numToKeep;
}
public void onInit(List commits) {
verifyCommitOrder(commits);
numOnInit++;
// false指定了不做任何删除
doDeletes(commits, false);
}
public void onCommit(List commits) {
verifyCommitOrder(commits);
doDeletes(commits, true); // 根据初始化的numToKeep,保留最近numToKeep个提交点
}
private void doDeletes(List commits, boolean isCommit) {
if (isCommit) {
String fileName = ((IndexCommitPoint) commits.get(commits.size()-1)).getSegmentsFileName();
if (seen.contains(fileName)) {
throw new RuntimeException("onCommit was called twice on the same commit point: " + fileName);
}
seen.add(fileName); // seen中加入的是已经删除的提交点
numOnCommit++;
}
int size = commits.size();
for(int i=0;i<size-numToKeep;i++) { // 删除前size-numToKeep个提交点
((IndexCommitPoint) commits.get(i)).delete();
numDelete++;
}
}
}
5、ExpirationTimeDeletionPolicy策略
如果某个提交的响应已经超过指定时间,则删除掉这个提交点。
class ExpirationTimeDeletionPolicy implements IndexDeletionPolicy {
Directory dir;
double expirationTimeSeconds; // 指定提交超时时间
int numDelete;
public ExpirationTimeDeletionPolicy(Directory dir, double seconds) {
this.dir = dir;
this.expirationTimeSeconds = seconds;
}
public void onInit(List commits) throws IOException {
verifyCommitOrder(commits);
onCommit(commits);
}
public void onCommit(List commits) throws IOException {
verifyCommitOrder(commits);
IndexCommitPoint lastCommit = (IndexCommitPoint) commits.get(commits.size()-1);
// 根据索引文件的最后提交时间
double expireTime = dir.fileModified(lastCommit.getSegmentsFileName())/1000.0 - expirationTimeSeconds;
Iterator it = commits.iterator();
while(it.hasNext()) {
IndexCommitPoint commit = (IndexCommitPoint) it.next();
double modTime = dir.fileModified(commit.getSegmentsFileName())/1000.0;
if (commit != lastCommit && modTime < expireTime) {
commit.delete();
numDelete += 1;
}
}
}
}
OpenMode设置是和IndexDeletionPolicy配合使用的
只有注释掉索引删除策略配置,让其使用默认值,openmode的配置才会生效
IKAnalyzer analyzer = new IKAnalyzer(false);
Directory directory = null;
// File file = new File(indexPath);
// Path path = file.toPath();
Path path = Paths.get(indexPath);
// 建立内存索引对象
// directory = newRAMDirectory();
// 建立物理文件索引
directory = FSDirectory.open(path);
// 读取原始文档内容,此处为文件夹路径,内可存放多个文本文档,每个文档代表一个doc
File files = new File("/mnt/lucenetxt");
//IndexWriterConfig和LiveIndexWriterConfig的联系和区别
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//config.setCommitOnClose(false);
config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
config.setMaxBufferedDocs(10);
config.setUseCompoundFile(false);
//创建一个IndexWriter对象,对于索引库进行写操作
//在IndexWriter构造函数中创建写锁,进程级系统锁
IndexWriter indexWriter = new IndexWriter(directory, config);
// 遍历一个文件
for (File f : files.listFiles()) {
String fileName = f.getName();
String fileContent = FileUtils.readFileToString(f);
String filePath = f.getPath();
long fileSize = FileUtils.sizeOf(f);
// 创建一个Document对象
Document document = new Document();
// 向Document对象中添加域信息
// 参数:1、域的名称;2、域的值;3、是否存储;
Field contentField = new TextField("content", fileContent, Store.YES);
Field contentField2 = new TextField("content2", fileContent, Store.YES);
// 将域添加到document对象中
document.add(contentField);
document.add(contentField2);
// 将信息写入到索引库中
indexWriter.addDocument(document);
}
indexWriter.commit();
同一索引的多Commit
单个lucene索引可以保存多个commit,这是一个强大的特性,但往往容易被人们所忽视。每次commit都持有该commit被创建的时间点的过引索视图。
这个特点类似于ZFS和新兴的Btrfs这样的现代文件系统的快照和写克隆功能。事实上,Lucene是能够暴露多次commit状态,其内在的原理是:所有的索引分片和文件只会被写一次,正如ZFS和Btrfs上的块。
为了保存多次commit到你的索引中,只需要实现你自己的IndexDeletionPolicy,并将其传递给IndexWriter。Lucene正是通过这个类才知站定哪些commit是要被删除的:IndexWriter会在打开索引和任何一次完成commit的时候调用它。KeepOnlyLastCommitDeletionPolicy是默认的删除机制实现,它会删除掉除了最近一次commit以外的所有commit。如果采用NoDeletionPolicy,那么每一次commit都会保存。
你可以在commit的时候传送userData (Map<String,String>),用于记录关于本次commit的一些用户自定义信息(对Lucene不透明),然后使用IndexReader.listCommits方法获得索引的所有commit信息。一旦你找到了一次commit,你可以在其上找开一个IndexReader对commit点上的索引数据进行搜索。
你也可以基于之前的commit打开一个IndexWriter,回滚之后的所有变动。这有点类似于rollback方法,不同之处在于它允许你在多个commit间rollback,而不仅是回滚当前IndexWriter会话中所做的变动。
当你使用OpenMode.CREATE参数打开一个索引的时候,老的commit仍会保存。你也可使用OpenMode.CREATE,同时还在老的commit上用IndexReader进行搜索。这使得一些有趣的应用情影成为可能,例如,你可以在不影响当前任何打开的reader前提下,在各个commit间做过索引全量重建工作。
组合上这些有意思的事务特征,你可以完成一些很酷的工作:
-
利用SnapShotDeletionPolicy或者PersistentSnapshotDeletionPolicy实现热备:
these deletion policies make it trivial to take a "live" backup of the index without blocking ongoing changes with IndexWriter. 备份可以轻易的实现增量(只要复制新的文件,并去掉删除文件),而你可以减少IO消耗以减轻对于搜索的干扰。 -
搜索不同版本的目录
也许你在运行一个商业网站,却要辗转于不同版本的目录之间。在这种情况下,你就可以保存更老的commit,允许用户选择搜索哪个版本的目录。 -
在同一个初始索引上进行多次索引实验:也许你想要在同一个大的索引上,运行一系列的性能测试实验,例如尝试不的RAM缓存大小或者merge因子。要想如此,你可以在运行完每次测试之后,不要关闭IndexWriter,而使用回滚方法快迅地恢得到初始状态,以备下次测试。
-
强制merge所有的片段到单一片段中,但依然保存之前的多片段时候的commit。如此,你就可以做多个段vs单一段的性能比较实验。
-
在NFS文件系统上做索引与搜索:
由于NFS无法保证当前打开的文件不被删除,你必须使用IndexDeletionPolicy来保存每次提交,直至所有的reader都不再使用该commit(即,重新在一个更新的commit上打开)。一种简单的解决方案是基本于时间的,例如:不删除该commit,直到创建该commit15分钟之后,然后每5分钟重新打开reader一次。不然你在NFS上进行搜索时,将会遇到各种可怕的异常。 -
分布式commit:
如果你有其他的资源,需要有lucene索引发生变化的同时提交,你就可以使用二阶段提交API。这样的做法很简单,但容易在第二阶段发生失败; 例如lucene在其第二阶段完成了提交,而数据库在第二阶段中发生了一些错误、奔溃或者是断电,这时,你就可以通过在一个较早的commit上重新打开一个IndexWriter来rollback Lucene的这次commit。 -
进行实验性的索引更新:也许你只想对索引的一个子集做一次re-indexing操作,但你又不确定这样的操作是否会成功。这种情况下,只要保存老的commit,如果操作失败,就执行rollback,如果成功则删除老的commit。
-
基于时间的快照:也许你想能够更自由地回滚到1天前,一周前,一个月之前...的索引状态,就保可以根据这些时间点,保存这些commit。
注意:保存多个版本的commit必然会带来更多的磁盘空间消耗。然而,这些消耗往往会比较小,因为多个commit往往会共享一些索引片段,尤其是那些更大更早的片段
public static void CreateIndex(String indexPath) throws IOException
{
// 实例化IKAnalyzer分词器
IKAnalyzer analyzer = new IKAnalyzer(false);
Directory directory = null;
// File file = new File(indexPath);
// Path path = file.toPath();
Path path = Paths.get(indexPath);
// 建立内存索引对象
// directory = newRAMDirectory();
// 建立物理文件索引
directory = FSDirectory.open(path);
// 读取原始文档内容,此处为文件夹路径,内可存放多个文本文档,每个文档代表一个doc
File files = new File("/mnt/lucenetxt");
//IndexWriterConfig和LiveIndexWriterConfig的联系和区别
IndexWriterConfig config = new IndexWriterConfig(analyzer);
//config.setCommitOnClose(false);
config.setIndexDeletionPolicy(NoDeletionPolicy.INSTANCE);
config.setMaxBufferedDocs(10);
config.setUseCompoundFile(false);
//创建一个IndexWriter对象,对于索引库进行写操作
//在IndexWriter构造函数中创建写锁,进程级系统锁
IndexWriter indexWriter = new IndexWriter(directory, config);
// 遍历一个文件
for (File f : files.listFiles()) {
String fileName = f.getName();
String fileContent = FileUtils.readFileToString(f);
String filePath = f.getPath();
long fileSize = FileUtils.sizeOf(f);
// 创建一个Document对象
Document document = new Document();
// 向Document对象中添加域信息
// 参数:1、域的名称;2、域的值;3、是否存储;
Field contentField = new TextField("content", fileContent, Field.Store.YES);
Field contentField2 = new TextField("content2", fileContent, Field.Store.YES);
// 将域添加到document对象中
document.add(contentField);
document.add(contentField2);
// 将信息写入到索引库中
indexWriter.addDocument(document);
}
Map<String, String> map = new HashMap<>();
map.put("1","1");
indexWriter.setCommitData(map);
indexWriter.commit();
map.put("1","2");
indexWriter.setCommitData(map);
indexWriter.commit();
map.put("1","3");
indexWriter.setCommitData(map);
indexWriter.commit();
}