为什么说Lucene不好?
在Lingway公司,我们使用了Lucene至进今已有好几年时间。对那些刚接触Lucene的人来说,这里是使用它的关键:Apache Lucene是一个由java编写的高性能,全方位的单词搜索引擎库。
在批评它之前,我必须承认Lucene是一个高性能的划词搜索引擎。几年来,Lucene已经被看作是用java编写的嵌入式搜索引擎中的一等公民。它的声誉每日剧增,并且仍然是开源java搜索引擎中的最佳。每个人都在说:“Doug Cutting做了一项伟大的工作”。然而,最近的几个月内,开发的进程变得缓慢,我认为Lucene将不会满足现代的文档处理需求。不要把东西搞糟:我不是搜索引擎开发者,我只是个开发者,使用搜索引擎,来提供合适信息的检索科技。
Lucene不是最好选择,至少对我们而言如此,并且情况并没有得到改变。我们列出Lucene的局限性:Lingway公司基于语意来生成复杂的查询。例如当你正在查找关于“中东地区冲突”的文章,你也许还需要找关于“伊拉克战争”文章。在上面这个用例中,“战争”和“伊拉克”分别是“冲突”和“中东”的扩展。我们使用一种技术能分析你的查询,产生相应的最合适的扩展,为它们生成查询。然而,为了得到相关的结果,这些还是不够的:通过Lucene实现的类似Google的等级或是经常变化积分的并不能满足语意级别积分。例如,一个包含“中”和“东”短语,但是被超过一个以上的单词隔开,这种情况并不是我们想要查找的。更重要的是,相对常规的单词,我们应该给扩展更低的分数。比如,我们应该给“中东地区冲突”这个短语更高的分数,而不是“伊拉克战争”。
在Lingway公司,我们认为这种文章相关性技术是一种未来的搜索引擎。Google在文章搜索上做的很出色。但我们想要的却是最相关的文章。但是,大部分的当代搜索引擎都没有对这样复杂查询做相关的设计…Lucene被wikipedia使用,如果你注意到当你查询查过一个单词时,大多数的查询结果并不是由关联的…
为了演示需求,这里有一个Lingway公司即将上线的KM3.7产品的界面截图。这里我们用法语写一个查询,用来查找那些同样主题,而用英语写的文章。注意,这可不仅仅是简简单单的翻译,我们称之为语言交叉模式:
注意到那些绿色的匹配:chanteur变成了singer,但是我们也发现singing被匹配了。同样情况流行乐成为蓝调的扩展。
6大理由不选用Lucene
6. 没有对集群的内置支持。
如果你创建集群,你可以写出自己对Directory的实现,或是使用Solr或者使用Nutch+Hadoop。Solr和Nutch都支持Lucene,但不是直接的替代。Lucene是可嵌入的,而你必须支持Solr和Nutch..我认为Hadoop从Lucene团队中产生并不惊讶:Lucene并不是通用的。它的内在性决定了对大多数场合来说它是非常快速的,但是对大型文档集合时,你不得不排除Lucene。因为它在内核级别上并没有实现集群,你必须把Lucene转换到别的搜索引擎,这样做并不直接。转换到Solr或者Nutch上的问题会让你遇到许多不必要的麻烦:Nutch中的集成crawling和Solr中的检索服务。
5.跨度查询太慢
这对Lingway公司来说可能是个特殊的问题。我们对跨度查询有很强要求,Lucene检索结构已经开始添加这一细节,但它们当初可没这么想。最基础的实现导致了复杂的算法并且运行缓慢,尤其是当某些短语在一份文档中重复了许多次出现。这是为什么我倾向说Lucene是一个高性能的划词检索引擎当你仅仅使用基本的布尔查询时。
4.积分不能被插件化
Lucene有自己对积分算法的实现,当条件增加时使用Similarity类。但很快它显示出局限性当你想要表示复杂的积分,例如基于实际匹配和元数据的查询。如果你这样做,你不得不继承Lucene的查询类。因为Lucene使用类似tf/idf的积分算法,然而在我们遇到的场合,在语意上的积分上Lucene的积分机制并不合适。我们被迫重写每一个Lucene的查询类使得它支持我们自定义的积分。这是一个问题。
3.Lucene并非良好设计
作为一个系统架构师,我倾向认为(1)Lucene有一个非常糟糕的OO设计。虽然有包,有类的设计,但是它几乎没有任何设计模式。这让我想起一个由C(++)开发者的行为,并且他把坏习惯带到了java中。这造成了,当你需要自定义Lucene来满足你的需求(你将来必定会遇到这样的需求),你必须面对这样的问题。例如:
<!--[if !supportLists]--> <!--[endif]-->几乎没有使用接口。查询类(例如BooleanQuery,SpanQuery,TermQuery…)都是一个抽象类的子类。如果你要添加其中的一个细节,你会首先想到写一个接口来描述你扩展的契约,但是抽象的Query类并没有实现接口,你必须经常的变化自己的查询对象到Query中并在本地Lucene中调用。成堆的例子如(HitCollecor,…)这对使用AOP和自动代理来说也是一个问题.
<!--[if !supportLists]--> <!--[endif]-->别扭的迭代实现.没有hasNext()方法,next()方法返回布尔类型并刷新对象内容.这对你想要保持对迭代的元素跟踪来说非常的痛苦.我假定这是故意用来节省内存但是它又一次导致了算法上的杂乱和复杂.
2.一个关闭的API使得继承Lucene成为痛苦
在Lucene的世界中,它被称之为特性。当某些用户需要得到某些细节,方针是开放类。这导致了大多数的类都是包保护级别的,这意味着你不能够继承他们(除非在你创建的类似在同一个包下,这样做会污染客户代码)或者你不得不复制和重写代码。更重要的是,如同上面一点提到的,这个严重缺乏OO设计的结构,一些类应该被设为内部类却没有,匿名类被用作复杂的计算当你需要重写他们的行为。关闭API的理由是让代码在发布前变得整洁并且稳定。虽然想法很光荣,但它再一次让人感到痛苦。因为如果你有一些代码和Lucene的主要思路并不吻合,你不得不经常回归Lucene的改进到你自己的版本直到你的补丁被接受。
然而当开发者开始越来越长的限制API的更改,你的补丁很少有机会被接受。在一些类和方法上加上final修饰符会让你遇到问题。我认为如果Spring框架有这样的限制,是觉不会流行起来。
<!--[if !supportLists]-->1. ? Lucene搜索算法不适合网格计算<!--[endif]-->
Lucene被写出来的时候硬件还没有很大的内存,多处理器也不存在。因此,索引结构是被设计成使用线性的内存开销很小的方式。我花了很长的时间来重写跨度查询算法,并使用多线程内容(使用双核处理器),但是基于迭代器的目录读取算法几乎不能实现。在一些罕见的场合你能做一些优化并能迭代一个索引通过并行方式,但是大多数场合这是不可能的。我们遇到的情况是,当我们有一个复杂的,超过50+的内嵌跨度查询,CPU还在空闲但I/O却一直忙?担?踔猎谑褂昧薘AMDirectory.
有没有替代品?
我认为最后一个观点充满疑问:Lucene到达了它的极限当它在现在硬件基础的条件下,检索大型数据集合时。那就是我为什么寻找下一个可以替代Lucene的出现。在阅读了博客目录和 Wikia的讨论后,我发现并没有很多的替代品。然而我最后推荐一个有希望的方案:MG4J。它有一个良好的面向对象设计,性能良好的检索(索引比Lucene慢),内存开销上也很小,达到10倍于Lucene速度的跨度查询,在我的跨度查询基准上,并且是原生上支持集群。同样它也内置了负载平衡,而Lucene最近才加入这项功能并且还是实验性质的。然而MG4J仍然缺少一些特性例如简单的索引指数,文档移除和更简单的使用索引处理。让我感到高兴的是我可以自定义Lucene上的功能在MG4J上只需花几个小时,而在Lucene上却需要数天。
我认为对开源的搜索引擎来说仍然有发展空间,它不是通过单台电脑用有限的内存来索引批量文档,而是通过透明的分布式索引来提供对大型数据集合检索更为快捷的答案。你不必利用应用来获得集群特性。Lucene对第一类搜索引擎有了很好的实现,单我认为它并不符合我们的需求:在一个合理的时间内找到最佳的答案。基于tf/idf的搜索算法和google的等级并不是未来搜索引擎的趋势。实现对原数据和语义的复杂查询并找出相关的信息,这是Lingway公司(通过Lucene和其他搜索引擎技术)所作的,不过它要求有更多支持新硬件的新技术。
使用Lucene的一个好理由
无论我如何指责Lucene,它仍然是java开源解决方案中的最佳实现。