本文将介绍有关索引并发控制的问题,以结束对Lucene.net建立索引问题的讨论.
1. 允许任意多的读操作并发.即可以有任意多的用户在同一时间对同一份索引做查询工作.
2. 允许任意多的读操作在索引被正在被修改的时候进行.即哪怕索引正在被优化,添加删除文档,这时也是允许用户对索引进行查询工作. (it’s so cool.)
3. 同一时间只允许一个对索引修改的操作.即同一时间只允许IndexWriter或IndexReader打开同一份索引.不能允许两个同时打开一份索引.
Lucene提供了几种对索引进行读写的操作.添加文档到索引,从索引中删除文档,优化索引,合并Segments.这些都是对索引进行写操作的方法. 查询的时候就会读取索引的内容.
有关索引并发的问题是一个比较重要的问题,而且是Lucene的初学者容易忽略的问题,当索引被破坏,或者程序突然出现异常的时候初学者往往不知道是自己的误操作造成的.
下面让我们看看Lucene是如何处理索引文件的并发控制的.
首先记住一下三点准则:
1. 允许任意多的读操作并发.即可以有任意多的用户在同一时间对同一份索引做查询工作.
2. 允许任意多的读操作在索引被正在被修改的时候进行.即哪怕索引正在被优化,添加删除文档,这时也是允许用户对索引进行查询工作. (it’s so cool.)
3. 同一时间只允许一个对索引修改的操作.即同一时间只允许IndexWriter或IndexReader打开同一份索引.不能允许两个同时打开一份索引.
第一个准则很容易理解,第二个准则说明Lucene对并发的操作支持还是不错的.第三个准则也很正常,不过需要 注意的是第三个准则只是表明IndexWriter和IndexReader不能并存,而没有反对在多线程中利用同一个IndexWriter对索引进行 修改.这个功能可是经常用到的,所以不要以为它是不允许的.不过这个时候的并发就需要你自己加以控制,以免出现冲突.
(注: 在前面的系列中已说过IndexReader不是对Index进行读操作,而是从索引中删除docuemnt时使用的对象)
有关这三个原则在实际使用Lucene API时候的体现,让我们先看看下面这张表:
表中列出了有关索引的主要读写操作.其中空白处表示X轴的操作和Y轴的操作允许并发.
而X处表明X轴的操作和Y轴的操作不允许同时进行.
比如Add document到索引的时候不允许同时从索引中删除document.
其实以上这张表就是前面三个准则的体现.Add Optimize Merge操作都是由IndexWriter来做的.而Delete则是通过IndexReader完成.所以表中空白处正是第一条和第二条准则的体现,而X(冲突)处正是第三个原则的具体表现.
为了在不了解并发控制的情况下对Lucene API的乱用. Lucene提供了基于文件的锁机制以确保索引文件不会被破坏.
当你对index 进行修改的时候, 比如添加删除文档的时候就会产生 ***write.lock文件,而当你从segment进行读取信息或者合并segments的时候就会产生***commit.lock文件.在默认 情况下,这些文件是放在系统临时文件夹下的. 简而言之, write.lock文件存在的时间比较长,也就是对index进行修改的锁时间比较长,而commit.lock存在的时间往往很短.具体情况见下表.
如果索引存在于server, 很多clients想访问的时候,自然希望能看到其他用户的锁文件,这时把锁文件放到系统临时文件夹就不好了.此时可以通过配置文件来改变锁文件存放的位置.
比如在一个ASP.NET的应用下,你就可以象下面这样利用web.config文件来实现你的目的.
<configuration>
<appSettings>
<add key="Lucene.Net.lockdir" value="c:yourdir" />
</appSettings>
</configuration>
<appSettings>
<add key="Lucene.Net.lockdir" value="c:yourdir" />
</appSettings>
</configuration>
不仅如此,在某些情况下比如你的索引文件存放在一个CD-ROM中,这时根本就无法对索引进行修改,也就不存在所谓的并发冲突,这种情况下你甚至可以讲锁文件的机制取消掉.同样通过配置文件.
<configuration>
<appSettings>
<add key="disableLuceneLocks" value="true" />
</appSettings>
</configuration>
<appSettings>
<add key="disableLuceneLocks" value="true" />
</appSettings>
</configuration>
不过请注意不要乱用此功能,不然你的索引文件将不再受到安全的保护.
下面用一个例子说明锁机制的体现.
using System;
using System.IO;
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.Store;
using NUnit.Framework;
using Directory = Lucene.Net.Store.Directory;
using System.IO;
using Lucene.Net.Analysis;
using Lucene.Net.Index;
using Lucene.Net.Store;
using NUnit.Framework;
using Directory = Lucene.Net.Store.Directory;
[TestFixture]
public class LockTest
{
private Directory dir;
public class LockTest
{
private Directory dir;
[SetUp]
public void Init()
{
String indexDir = "index";
dir = FSDirectory.GetDirectory(indexDir, true);
}
public void Init()
{
String indexDir = "index";
dir = FSDirectory.GetDirectory(indexDir, true);
}
[Test]
[ExpectedException(typeof(IOException))]
public void WriteLock()
{
IndexWriter writer1 = null;
IndexWriter writer2 = null;
try
{
writer1 = new IndexWriter(dir, new SimpleAnalyzer(), true);
writer2 = new IndexWriter(dir, new SimpleAnalyzer(), true);
}
catch (IOException e)
{
Console.Out.WriteLine(e.StackTrace);
}
finally
{
writer1.Close();
Assert.IsNull(writer2);
}
}
[ExpectedException(typeof(IOException))]
public void WriteLock()
{
IndexWriter writer1 = null;
IndexWriter writer2 = null;
try
{
writer1 = new IndexWriter(dir, new SimpleAnalyzer(), true);
writer2 = new IndexWriter(dir, new SimpleAnalyzer(), true);
}
catch (IOException e)
{
Console.Out.WriteLine(e.StackTrace);
}
finally
{
writer1.Close();
Assert.IsNull(writer2);
}
}
[Test]
public void CommitLock()
{
IndexReader reader1 = null;
IndexReader reader2 = null;
try
{
IndexWriter writer = new IndexWriter(dir, new SimpleAnalyzer(),
true);
writer.Close();
reader1 = IndexReader.Open(dir);
reader2 = IndexReader.Open(dir);
}
finally
{
reader1.Close();
reader2.Close();
}
}
}
public void CommitLock()
{
IndexReader reader1 = null;
IndexReader reader2 = null;
try
{
IndexWriter writer = new IndexWriter(dir, new SimpleAnalyzer(),
true);
writer.Close();
reader1 = IndexReader.Open(dir);
reader2 = IndexReader.Open(dir);
}
finally
{
reader1.Close();
reader2.Close();
}
}
}
不过很令人失望的是在Lucene(Java)中应该收到的异常在dotLucene(1.4.3)我却没有捕获到.随后我在dotLucene的论坛上问了一下,至今尚未有解答.这也是开源项目的无奈了吧.