Lucene多线程操作实现

本文介绍了在多线程环境下如何安全地使用Lucene。Lucene允许多个读操作并发,但不允许并发修改,内部通过锁机制保证线程安全。文章提供了一个IndexModifier Singleton类的实现,用于管理IndexWriter和IndexReader,确保线程安全地添加、删除和检索文档。此外,还讨论了使用Compass框架实现与Hibernate和Spring的集成,以实现实时索引和搜索功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Lucene多线程操作实现

对于并发,Lucene 遵循以下规则:

1.
允许任意多的读操作并发,即任意数量用户可同时对同一索引做检索操作。
2.
即便正在进行索引修改操作(索引优化、添加文档、删除文档),依然允许任意多的检索操作并发执行。
3.
不允许并发修改操作,也就是说同一时间只允许一个索引修改操作。

Lucene
内部已经对多线程安全进行了处理,很多操作都使用了 lock 进行多线程同步锁定。只要遵循一定的规则,就可以在多线程环境下安全运行 Lucene
方案一:
建议:

1. Directotry
Analyzer 都是多线程安全类型,只需建立一个 Singleton 对象即可。
2.
所有线程使用同一个 IndexModifier 对象进行索引修改操作。
3. IndexWriter/IndexReader/IndexModifier/IndexSearcher
最好使用同一个 Directory 对象,否则多线程并发读写时可能引发 FileNotFoundException

IndexModifier
对象封装了 IndexWriter IndexReader 的常用操作,其内部实现了多线程同步锁定。使用 IndexModifier 可避免同时使用 IndexWriter IndexReader 时需要在多个对象之间进行同步的麻烦。等所有修改操作完成后,记住调用 Close() 方法关闭相关资源。并不是每次操作都需要调用 Optimize(),可以依据特定情况,定期执行优化操作。

--------

以下演示代码简单封装了一个 IndexModifier Signleton 类型,确保多线程使用同一个对象,且只能由最后一个多线程调用 Close 方法关闭。
代码不完善,仅供参考!需要做些修改才能应用于实际项目。

//索引修改器的获取和关闭

import java.io.File;

import java.io.IOException;

import java.io.StringReader;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.Map;

 

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.index.CorruptIndexException;

import org.apache.lucene.index.IndexModifier;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.LockObtainFailedException;

import org.apache.lucene.store.RAMDirectory;

 

 

public class MyIndexModifier {

    private static Analyzer analyzer = new StandardAnalyzer();

    private static IndexModifier modifier;

   

    private static ArrayList<Thread> threadList = new ArrayList<Thread>();

 

   

    private MyIndexModifier() { }

 

    static final File INDEX_DIR = new File("D:/docindex");

      public static IndexModifier GetInstance()

      {

          synchronized (threadList)

        {

          if (modifier == null)

          {

            try {

               

                modifier = new IndexModifier(INDEX_DIR, analyzer, false);

            

                //索引性能测试参数配置

               

                modifier.setMergeFactor(1000); 

           

               

                 System.out.println("MergeFactor: " + modifier.getMergeFactor());

                 System.out.println("MaxBufferedDocs: " + modifier.getMaxBufferedDocs());

            } catch (CorruptIndexException e) {

                e.printStackTrace();

            } catch (LockObtainFailedException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            }

          }

 

          if (!threadList.contains(Thread.currentThread()))

            threadList.add(Thread.currentThread());

 

          return modifier;

        }

      }

 

      public static void Close()

      {

          synchronized (threadList)

        {

          if (threadList.contains(Thread.currentThread()))

            threadList.remove(Thread.currentThread());

 

          if (threadList.size() == 0)

          {

           try {

               if (modifier != null)

                {

                 

                modifier.close();

                modifier = null;

                }

            } catch (CorruptIndexException e) {

                e.printStackTrace();

            } catch (IOException e) {

                e.printStackTrace();

            }

          }

        }

      }

}

 

 

//线程处理类

import java.io.IOException;

import java.util.Date;

 

import org.apache.log4j.LogManager;

import org.apache.log4j.Logger;

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.index.CorruptIndexException;

import org.apache.lucene.index.IndexModifier;

import org.apache.lucene.index.StaleReaderException;

import org.apache.lucene.store.LockObtainFailedException;

 

import com.miracle.dm.framework.common.TimestampConverter;

 

public class  TestModifer extends Thread{

   

    private static Logger logger =  LogManager.getLogger(TestModifer.class);

    @Override

    public void run() {

        IndexModifier writer = MyIndexModifier.GetInstance();

 

         try {

                writer.deleteDocument(0);

            } catch (StaleReaderException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            } catch (CorruptIndexException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            } catch (LockObtainFailedException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            } catch (IOException e1) {

                // TODO Auto-generated catch block

                e1.printStackTrace();

            }

         

       

        for (int x = 0; x < 10; x++)

        {

          Document doc = new Document();

          TimestampConverter converter = new TimestampConverter();

          Date date = new Date();

          String docDate = converter.timestampToShortStr(date);

          doc.add(new Field("docDate",  docDate  , Field.Store.YES, Field.Index.TOKENIZED));

      

             

         

          try {

            writer.addDocument(doc);

        } catch (CorruptIndexException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (LockObtainFailedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        } catch (IOException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

        }

        logger.debug(""+ Thread.currentThread()+","+ writer.docCount());

        MyIndexModifier.Close(); // 注意不是调用 IndexModifier.Close()

    }

}
多线程测试代码

import java.io.Console;

import java.io.IOException;

import java.util.Date;

 

import org.apache.log4j.LogManager;

import org.apache.log4j.Logger;

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.index.CorruptIndexException;

import org.apache.lucene.index.IndexModifier;

import org.apache.lucene.store.LockObtainFailedException;

 

import com.miracle.dm.framework.common.TimestampConverter;

 

public class test {

    private static Logger logger =  LogManager.getLogger(test.class); 

   

    public test(){

    }

   

    /**

     * @param args

     */

    public static void main(String[] args) {

       

        for (int i = 0; i < 100; i++)

        {

          new TestModifer().start();

        }

    }

}

注意:使用lucene现在的新版本的朋友一定会发现,现在并不推荐使用。而查看API发现IndexModifier已经被IndexWriter代替。再查看IndexWriter,其中提供了新增,删除,更新索引文档的方法。

 

 

这里是自己编码来实现,但是我不知道当几千或更多用户在对索引进行操作,那会不会导致close长时间没有运行,而无法检索到最新的更新索引。希望大家帮我考虑一下是否会存在这方面的问题,如果存在该如何解决?

 

 

方案二:利用已有的lucene框架,例如compass

它对lucene实现了实时索引。可基于hibernate,当更新数据库时,系统会自动更新索引。

·              CompassluceneSpringHibernate三者结合
转自:http://wemyss.blogbus.com/logs/8014799.html

1.概述

CompassluceneSpringHibernate三者的起来,以很低很低的成本快速实现企业应用中的搜索功能。

HomePage: http://www.opensymphony.com/compass/ 

springside里用了compass来做图书搜索,快速建立的流程如下:

1.用简单的compass annotationBook对象映射到Lucene

2.配置compass默认提供的基于Spring MVCIndex Controller Search Controller

3.编写查询结果的显示页面,将controller返回的变量显示出来。

2.Object/Search Engine Mapping Annotations配置

使用JDK5 annotation 来进行OSEM(Object/Search Engine Mapping)比用xml文件按简单许多,下面就是简单的搜索类,可见@SearchableID, @SearchableProperty@SearchableComponent 三个标记,分别代表主键、可搜索的属性与关联的,另一个可搜索的对象,另外Compass要求POJO要有默认构造函数,要实现equals()hashcode():

详细请点击查看springside中的Product.javaBook.java, Category.java

 

 

 public class Product  {        

 @SearchableId    

private Integer id;   

 private Category category;   

 private String name;   

 private Double unitprice;   

 @SearchableProperty(name = "name")  

  public String getName() {     

   return this.name;   

 }    

@SearchableComponent (refAlias = "category")  

  public Category getCategory() {    

return this.category;   

 }    

public Double getUnitprice() {  

      return this.unitprice; 

   }

 

 

3. spring,hibernate集成配置

3.1 spring配置文件

hiberante中的sessionFactory,transactionManager相比大家也是轻车熟路了.这里还是带过(因为不牵扯稿费的问题吗^_^ ).compass已经对对spring集成做了很好的封装,让我们的使用更加简单,我们可以不为compass编写一行代码,就可以做完搜索引擎的检索.下面是compassspring中的简明配置. 详情点击查看springside中的applicationContext-lucene.xml  

<beans>
<bean id="annotationConfiguration"          class="org.compass.annotations.config.CompassAnnotationsConfiguration"></bean>

<bean id="compass" class="org.compass.spring.LocalCompassBean">
   
<!-- anontaition式设置     -->  
  
<property name="classMappings">

     
<list>
        
<value>org.springside.bookstore.domain.Book</value>
     
</list>
  
</property>

  
<property name="compassConfiguration" ref="annotationConfiguration"/>

   
<property name="compassSettings">
        
<props>
            
<prop key="compass.engine.connection">file://${user.home}/springside/compass</prop>
            
<prop key="compass.transaction.factory">org.compass.spring.transaction.SpringSyncTransactionFactory</prop>
        
</props>
    
</property>

   
<property name="transactionManager" ref="transactionManager"/>
 
</bean>
  
<bean id="hibernateGpsDevice" class="org.compass.spring.device.hibernate.SpringHibernate3GpsDevice">
     
<property name="name">
        
<value>hibernateDevice</value>
     
</property>
     
<property name="sessionFactory" ref="sessionFactory"/>
 
</bean>
<bean id="compassGps" class="org.compass.gps.impl.SingleCompassGps" init-method="start" destroy-method="stop">
  
<property name="compass" ref="compass"/>
  
<property name="gpsDevices">
      
<list>
          
<ref local="hibernateGpsDevice"/>
      
</list>
  
</property>
 
</bean>
</beans>

上面要留意的配置有:

annotationConfiguration: 使用annotation配置,指定要转换的POJOBook
compass.engine.connection :
索引文件在服务器上的存储路径
.
hibernateGpsDevice:
hibernate的绑定,用Hibernate 3 事件系统,支持Real Time Data Mirroring .Hiberante的数据改变会自动被反射到索引里面.

3.2 web Controller的配置

两个Controller都是现成的,只要配置相关选项即可。

详情请查看springsidebookstore-servlet.xml

 

  <bean id="indexBookController" class="org.compass.spring.web.mvc.CompassIndexController">   

  <property name="compassGps" ref="compassGps"/>   

  <property name="indexView" value="/admin/indexBook.jsp"/>   

  <property name="indexResultsView" value="/admin/indexBook.jsp"/> </bean> <bean id="searchBookController" class="org.compass.spring.web.mvc.CompassSearchController">   

 <property name="compass" ref="compass"/>   

 <propertyname="searchView"value="/home/top.jsp"/>  

 <property name="searchResultsView" value="/home/searchBook.jsp"/>    <property name="pageSize" value="5"/>

</bean>

 

 

3.3 View JSP

  简单搜索页面:只需要一个query 参数:

<INPUT type="text" size="20" name="query">

结果页面:

结果页面将返回几个变量,包括:

searchResults(搜索结果) 包括hits(结果) searchtime(耗时)
    pages(
分页信息) 包括page_from page_to

    command(
原来的查询请求)

具体使用见springsideadvancedSearch.jsp ,下面是简版的代码:

 

<c:if test="${not empty searchResults}">        耗时:

<c:out value="http://www.zhmy.com/${searchResults.searchTime}"/>ms      <c:forEach var="hit" items="${searchResults.hits}">            

    <c:choose>                   

 <c:when test="${hit.alias == 'book'}">                        

  <div class="left_content">                             

     <href="#" class= "title"> ${hit.data.name}</a>                                 

  <br/> 作者:${hit.data.author}<br/>                   

   </div>                    

 </c:when>             

</c:choose>        

</c:forEach>

</c:if>

 

 

4.扩展高级搜索

扩展高级搜索其实很简单,SpringSide已经初步封装了加入包含以下任意单词,不包含以下任何单词,分类选择等条件及每页显示条数的确定。

如果需要更多条件:

1. 加强搜索页面,加入更多条件的显示。

2. 扩展compasscommand class,接受从搜索条件页传过来的条件。 可从springsideAdvancedSearchCommand  扩展或从Compass的原类扩展。

3. 扩展compasssearchController, command中的变量重新处理为一个符合Lucene语法规则的query变量 即可(springside中的AdvancedSearchController ),同时可以为搜索条件页查询图书分类列表一类的变量。

   你可以从springsideAdvancedSearchController扩展,重载onSetupCommand (),参考父类的做法,加装自己的条件。重载referenceData(),把图书分类列表这种条件加入到AdvancedSearchCommand referenceData Map中供搜索条件页显示,例子见BookSearchController

也可以参考BookSearchControllerAdvancedSearchController的做法,完全自行扩展。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值