`
dingjob
  • 浏览: 180915 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

基于Lucene的商业搜索应用架构研究(下)——遇到的问题

阅读更多
     由于也是初次开发基于Lucene的商业应用,经验比较缺乏,在开发过程中遇到了不少问题,有些比较大众化的问题,这里不细讲,对于稍微复杂的问题展开来写下,也希望得到博友的一些分享。

  

     1.单分词的研究

      对于 “上海apen 公司”进行搜索,要能够搜索出  上海a的模糊查询。首先这个需求有一定弊端,索引会因此变大,搜索效率会变低,目前的解决方案有:



1.类似系统A的like搜索 ,用wizardFiter对query进行通配符的过滤,这种过滤会增加上去这些词条。

2. 使用庖丁解牛,辅助生成分词方法

3. javaCC,根据自定义的规则生成查询方法。

4. 已经有网上比较成熟的分词。



对于chineseAnalyzer进行研究



bufferIndex  读进缓冲区的数据集长度,

dataLen  本次term值的长度。





 if (bufferIndex >= dataLen) {
    dataLen = input.read(ioBuffer);
    bufferIndex = 0;
   }

 

读取一个字符

 

c = ioBuffer[bufferIndex++]; 

 // 本段拆分的词

 private final Token flush() {

 

  if (length > 0) {
   // System.out.println(new String(buffer, 0, length));
   return new Token(new String(buffer, 0, length), start, start
     + length);
  } else
   return null;
}




  其中对于英文的分词逻辑是:如果下一个不是英文字符(_是英文字符),则会继续走,直到出现空格,中文等,就调用flush 函数,建立新的token.

 if (charType != -1 && charType != 1 && length > 0) {}


  项目中改造了chinese分词,目前命名为sigleLetterAnyaler,分词要实现analyer的接口,主要工作包括切分,然后计算其起至位置,这里修改了它对于英文的分词逻辑.

  

    2. Digester读取嵌套的文件

采用模板加载一些控制参数,如,有哪些字段,如何分词,索引路径等。(需要进一步看下这个包的应用)





    3.多线程和线程池

设计中,建立索引时,在提取数据时采用多个线程,线程采集好数据放在线程池内,线程池内线程会被放入等待队列,但是执行顺序是随机的。(线程池的应用还需进一步深化)。





    4. Cache

     应该说,开发搜索引擎不用cache是不行的,系统A非常巧妙的应用了缓存,而且写了比较强大的三级缓存,在读数据的时候,如果创建indexReader对象,则效率非常低,需要把所有索引的数据取到内存。所以要用cache。indexWriter使用了线程池,线程池的执行原理是放进去就开始执行,多个线程可以共用一个indexWriter对象。效率非常高,他重写了indexWriter.close 方法,当调用时,数量减一,并不销毁对象,这样以后再从cache取就直接取到了这个对象indexWriter close时是不是销毁对象

@Override
 public IndexReader getIndexReader(String directory) throws IOException {
  Lock lock = this.getLock(directory);
  lock.lock();
  try {
   CachedIndexWriter indexWriter = cache.get(directory);
   if (indexWriter != null) {
    //从cache里得到的indexWriter都要调用close方法
    indexWriter.close();
    if (log.isDebugEnabled()) {
     log.debug("the index writer exist in cache.remove it now.");
    }
    if (!cache.remove(directory)) {
     throw new IOException("the directory '" + directory
       + "' may be in use. cann't create IndexReader.");
    }
   }
   return super.getIndexReader(directory);
  } finally {
   lock.unlock();
  }



indexReader 的cache 比较简单,可以采用map将其缓存起来,每个indexType一个即可。



     5.  Lucene新的方法使用



      Lucene 2.4 对于搜索废弃了原来的search尽量使用新的方法。也即将废弃了indexModifer,它的效率也是非常低的,不建议使用。



     6.  jboss 直接关闭会有 write.lock,导致每次取索引都会报错的问题。

   正常的使用linux 命令是不会有的,只有强杀进程和直接关闭的方式是会有的,程序是无法控制被强杀进程时再做相应操作的,jboss 怎么回收这个的jboss正常关闭会调用程序的实现了DisposableBean的destory方法的,所以一定要维护好一个对象的生命周期

     7.  发布hessian原理

    ASF 中使用内置的jetty 发布hessian,hessian要求的不仅仅是参数实体要序列化,内部所有对象都要序列化,lucene内部query等对象都是没有序列化的,不能传输.这里采用了java内建对象,采用map和set 等进行传输,实现客户端和服务端的完全独立,服务端用java反射机制进行解析,解析为相应的conditon方法,然后对改conditon进行query条件构造.

    8.  Too many Files的解决方案


  搜索过程中发现经常会报这个错误,出现这个问题的原因分析下有两个方面,

(1)indexReader对象没有关闭导致的,cache种维护的indexReader会认为一直在打开这个文件。

(2)当前打开的文档数确实超过linux文档数限制,这个可以系统配置。


   开始直接在新的indexReader产生时,调用indexReader的close方法,close旧的对象实验证明这样是不行的,可能会关掉正在调用中的对象(其实这个是lucene的机制不够合理,正在使用中的对象都可以关闭) 会爆出 indexReader is closed的错误,最终还是放到线程池来关闭,这样我们以为解决了,看打出来的日志,有新建的有close的,我们认为这个线程池已经起到了作用,能够维护好线程了,第二天一看,还是不行,我统计了一下开启了 150个reader对象,只有40几个关闭掉了,看来还有没有关闭的对象,最终发现原来是new CachedindexReader后没有及时更新缓存的对象,导致关闭的是旧的引用对象,这也导致了另外诸如查询结果不匹配的情况。

     9. 内存溢出的解决方案

    这个问题和上面的问题有一定联系,这里放在一起总结,刚开始的内存溢出是indexReader对象没有全部关闭导致的,发现关闭后还是内存占用超大,Jprofile跟踪内存后发现,每次大数据量查询消耗200M左右内存,主要从如下几个方面作了优化
(1)排序方面,lucene的sort排序如果不使用类型来指定它,采用auto类型,会导致数据排序时占用很大内存,这里采用了指定其类型为Sting类型
(2) 主动的垃圾回收机制,当数据量超大时,查询结果返回超过10000条,使用gc回收 (3)页面级别控制,不能只输入公司来查询
(4)并发机制,如果出现同时访问量达到40个,返回系统正忙,不允许查询。
(5)健全的邮件通知机制,发生内存溢出时有通知邮件,但是系统继续执行,不能崩溃,因为在内存溢出时,垃圾回收器会回收部分内存,可能不影响接下来的访问。

      10. 并发时,JBOSS无响应的解决方案

     在线上环境,建索引时进行查询,或者大量用户并发查询时,会导致线程一致等待,直到JBOSS没有响应
     经确认,我们在同步时,锁了log4j 日志这个常量,结果造成死锁,写日志的方法在等
我们的同步方法,我们的同步方法在等写日志的方法释放出来。锁log这个常量是大家经常做的,但是这里最好定义一个没有用的常量来同步代码块,否则容易造成死锁。
1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics