lucene2.4优化索引过程中遇到的问题
最近开源项目compass升级至2.1版本,它的内核--开源项目lucene也升级到2.4版本,compass2.1 使用了最新版本的lucene, 据说性能提升了很多,我们决定对本公司网站中使用的compass组件进行升级。
工作进行的很顺利,但最后在索引优化环节出了问题,反复试验都问题依旧。具体表现如下:
我们的做法是把索引优化做为EJB服务组件放在jboss容器中运行,索引共有5个子索引。在其中的一部机器上,当优化进行到最后一个子索引时,程序似乎陷入死锁,长时间没有反应,且cpu的使用率很高,而客户端程序则在无限期等待。在另一部机器上,当索引优化进行到第三个子索引时就会出现上述问题。更加令人捉摸不定的是,在第一部机器上,整个优化过程有时又可顺利完成,而在第二部机器上,优化总是在进行到第三个子索引时陷入停顿。
进一步观察发现,在优化陷入停顿时,jboss服务器的控制台总是会打印出以下信息:
WARN [org.jboss.tm.TransactionImpl] Transaction TransactionImpl:XidImpl [FormatId=257, GlobalId=chengwenkan//15, BranchQual=] timed out. status=STATUS_ACTIVE
在网上搜了一下,这是jboss提示事务运行超时的信息。Jboss把EJB对外服务接口的每一个方法都做为一个事务来管理。如果方法执行超时,jboss会中断正在执行该方法的线程(调用线程的interrupt()方法)。缺省的超时时间是5分钟,可在server/conf/jboss-service.xml中进行设置。如下:
<mbean code="org.jboss.tm.TransactionManagerService"
name="jboss:service=TransactionManager"
xmbean-dd="resource:xmdesc/TransactionManagerService-xmbean.xml">
<attribute name="TransactionTimeout">300</attribute>
既然是方法执行时间超时导致了该问题,加长jboss事务的超时时间是否可以解决问题呢?我们把jboss事务的超时时间放大10倍,改为3000秒,即50分钟,这大大超出了优化整个索引所需的时间,应该可以解决问题。
重启jboss服务器,再次进行测试,优化果然顺利完成了。
然而,这并不是问题的全部。
之前我们使用compass1.2,索引更加庞大,优化所需的时间会更长,jboss服务器的事务超时时间也是缺省的5分钟,但优化每次都能顺利完成。
可见,一定是新版本的compass或lucene出了问题。
从网上下载了compass2.1和lucene2.4的源码,经过一番跟踪调试,终于发现了问题所在。
原来,老版本的lucene在对索引优化时用的是单线程模式,即在对某个索引优化时一直会等到优化完成才退出。而在2.4版本下,优化用的是多线程模式,在对某个(子)索引优化时,会启动一条线程在后台对索引进行优化,而前台线程则可以根据需要进行设置以决定是否等待,如果等待,前台线程会对优化过程进行监控,当优化结束后才退出;如果不需要等待,则前台线程会直接退出,后台线程则继续进行优化工作,直至完成。应用程序则可以继续启动下一个子索引的的优化。最多时,可能所有的子索引都处在优化过程之中。
正是由于前台线程和后台线程协同工作时的不严谨,导致了我们所遇到的问题。
为了解释这个问题,我们先来了解一下Thread类interrupt()方法的用法。
Interrupt()方法的作用是使线程退出阻塞状态。更为精确的讲,如果线程被阻塞在方法Object.wait()、Thread.join()或是Thread.sleep(),它将接收一个InterruptedException,从而提前终结阻塞方法。(在其它一些情况下线程也可终结阻塞状态,请参阅sun java api 文档。)Thread.interrupt()方法并没有中断一个正在运行的线程。
请看以下例子:
class Example1 extends Thread { public static void main(String args[]) throws Exception { Example1 thread = new Example1(); System.out.println("Starting thread..."); thread.start(); Thread.sleep(3000); System.out.println("Interrupting thread..."); thread.interrupt(); Thread.sleep(3000); System.out.println("Stopping application..."); System.exit(0); } public void run() { while (true) { System.out.println("Thread is running..."); synchronized(this){ try{ wait(1000); }catch(InterruptedException e){ return; } } } } } if (doWait) { synchronized(this) { while(true) { ... if (optimizeMergesPending()){ doWait(); } else break; } } ...}private synchronized void doWait() { try { // NOTE: the callers of this method should in theory // be able to do simply wait(), but, as a defense // against thread timing hazards where notifyAll() // falls to be called, we wait for at most 1 second // and then return so caller can check if wait // conditions are satisified: wait(1000); } catch (InterruptedException ie) { Thread.currentThread().interrupt();//将此行注释掉了就可以了 } }