使用Zoie构建实时检索系统
Posted in search on 五月 8th, 2010 by kafka0102
Zoie是LinkedIn开源的实时检索系统,它本身用于LinkedIn的用户profile页检索。且不说专业的搜索引擎,大多数的站内检索基本都不是实时的,一般都会有几分钟的延迟。但LinkedIn认为,用户profile信息的检索需要实时的效果,因为如果搜索结果不正确或不理想会很影响用户体验。所以,LinkedIn的Zoie实现了秒级别的实时检索效果。Zoie在LinkedIn跑了有两年多时间,这个开源项目可以说是起点就比较成熟的,这个项目提供了较为丰富的功能和工具,除了核心的API外,它还提供了管理和监控工具、嵌入jetty的Web server demo、一些实用的DataProvider等。实现上自然是基于lucene了,也使用了一些我需要回顾或者了解的库和框架。对于社区类需要提供用户信息检索的系统来说,使用Zoie或许是个很不错的选择。
下面将要介绍的使用Zoie API来编写检索demo,并且为了省事,我的code sample直接来自于Zoie的Wiki(http://snaprojects.jira.com/wiki/display/ZOIE/Code+Samples),但会对其中的使用做些中文版的说明。由于Zoie是个很有针对性的实现,所以相比于Solr这样的通用系统来说,Zoie的代码要“直白”很多,这也促使我拿出精力把它的核心代码研究了一番。总结下来有两点:1)要写一个基于lucene的精致的检索系统需要对lucene有深入的理解,针对实时性检索需求,Zoie本身就对lucene做了很多扩展。2)Zoie的代码结构和代码风格有些糟糕,我看到git里有.project文件,所以可见其作者应该也是基于eclipse开发的,可他的代码就不能好好format下?并且包名和类层次也很诡异。闲言少叙(让我再次想起俞平伯先生),下面罗列下使用Zoie API的code sample。
在检索server端,需要索引的数据总要以一种结构表示,比如一个MAP或POJO之类的。让Zoie来索引数据,就需要Zoie知道你需要索引哪些Field到Document(lucene里的Document)里。说白了,要提供一份要索引的数据及将数据转换成Document结构的接口。这里的数据假定是一个Data:
class Data{ long id; String content; }
转换数据的工作由实现ZoieIndexableInterpreter接口的类实现,代码片断如下:
class DataIndexable implements ZoieIndexable { private Data _data; public DataIndexable(Data data) { _data = data; } public long getUID() { return _data.id; } public IndexingReq[] buildIndexingReqs() { // it is possible we want to map 1 data object to multiple lucene documents // but not for this example Document doc = new Document(); doc.add(new Field("content",_data.content,Store.NO,Index.ANALYZED)); // no need to add the id field, Zoie will manage the id for you return new IndexingReq[]{new IndexingReq(doc)}; } // the following methods in this example are kind of hacky, // but it is designed to be used when information needed to determine whether documents // are to be deleted and/or skipped are only known at runtime public boolean isDeleted() { return "_MARKED_FOR_DELETE".equals(_data.content); } public boolean isSkip(){ return "_MARKED_FOR_SKIP".equals(_data.content); } } class DataIndexableInterpreter implements ZoieIndexableInterpreter { public ZoieIndexable interpret(Data src){ return new DataIndexable(src); } }
针对上面的代码,我还需要做一些解释。可以看出的是,Zoie在建索引时,需要使用public IndexingReq[] buildIndexingReqs()方法来取出数据,在这个方法里,需要自己build document结构。而方法public long getUID()表明,Zoie需要索引的数据包含uid(可以认为是唯一性主键的概念,而不必须是实际意义的uid),并且这个uid不要在buildIndexingReqs()方法里做成Field,Zoie自己会处理uid,实际上它内部是将uid和lucene的doc id对应上了。这有什么用处呢?对于像用户profile情景,是假设没有删除profile而只有add或update的情况,这使得Zoie在内存里记录了uid和doc id的对应关系,当检查发现是update时,就标记该doc需要在search时过滤掉。对于需要删除数据的场景,一个折中的方法是仅保留uid而将其他Field都去掉。还有那个isDeleted()方法,不要以为它是提供要删除的doc的判断的,它的作用和isSkip差不多,是在Zoie建索引时临时的将当前的Data去掉而不建它的索引(这需要外部修改Data的内容才行),就好像你发现建的数据有问题想中止操作一样,但可想见的是,你根本不知道Zoie建索引到什么阶段了,这个接口的用处就值得怀疑了。
下面再来构建IndexDecorator。Zoie提供的这个接口是允许客户端(相对于Zoie来说是客户端)对给定的ZoieIndexReader装饰成自定义的IndexReader。就像demo给的那样,除非有必要,否则默认实现就可以了。
class MyDoNothingFilterIndexReader extends FilterIndexReader { public MyDoNothingFilterIndexReader(IndexReader reader) { super(reader); } public void updateInnerReader(IndexReader inner) { in = inner; } } class MyDoNothingIndexReaderDecorator implements IndexReaderDecorator { public MyDoNothingIndexReaderDecorator decorate(ZoieIndexReader indexReader) throws IOException { return new MyDoNothingFilterIndexReader(indexReader); } public MyDoNothingIndexReaderDecorator redecorate(MyDoNothingIndexReaderDecorator decorated, ZoieIndexReader copy) throws IOException { // underlying segment has not changed, just change the inner reader decorated.updateInnerReader(copy); return decorated; } }
下面是build Zoie的核心:ZoieSystem。indexingSystem的start()方法将启动新的线程准备接收数据来建索引。
// index directory File idxDir = new File("myIdxDir"); // create an analyzer Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT); // create similarity Similarity similarity = new DefaultSimilarity(); ZoieIndexableInterpreter myInterpreter = new DataIndexableInterpreter(); IndexReaderDecorator decorator = new MyDoNothingIndexReaderDecorator(); ZoieSystem indexingSystem = new ZoieSystem(idxDir, // index direcotry myInterpreter, // my interpreter decorator, // index decorator analyzer, // my analyzer similarity, // my similarity 1000, // # events to hold in mem before flushing to disk 300000, // time(ms) to wait before flushing to disk true); // true for realtime indexingSystem.start(); // ready to accept indexing even
下面给出建索引和查询的使用片断,两者可分别在不同线程执行。
索引线程的代码片断:
long batchVersion = 0; while(true){ Data[] data = buildDataEvents(...); // build a batch of data object to index // construct a collection of indexing events ArrayList eventList = new ArrayList(data.length); for (Data datum : data){ eventList.add(new DataEvent(batchVersion,datum)); } // do indexing indexingSystem.consume(events); // increment my version batchVersion++; }
上面的buildDataEvents方法是产生数据的方法,比如接收客户端请求的数据(也许它还会阻塞在那?),或者是查询数据库的数据之类的。ZoieSystem的consume方法就是用来消费数据建索引,并且是可以批量处理的。
再看看查询线程的代码片断:
// get the IndexReaders List> readerList = indexingSystem.getIndexReaders(); // MyDoNothingFilterIndexReader instances can be obtained by calling // ZoieIndexReader.getDecoratedReaders() // combine the readers MultiReader reader = new MultiReader(readerList.toArray(new IndexReader[readerList.size()]),false); // do search IndexSearcher searcher = new IndexSearcher(reader); Query q = buildQuery("myquery",indexingSystem.getAnalyzer()); TopDocs docs = searcher.search(q,10); // return readers indexingSystem.returnIndexReaders(readerList);
懒得解释了,跟使用lucene的查询接口差不多。
上面的代码稍加完善就可以真正跑起来了。Zoie也提供了独立的Server功能,但它的那个Server很有可能是你不想要的,而使用上述的嵌入式接口来实现一个Server显然已经极大简化工作了。Zoie还提供了DataProvider机制及实用的实现,对于像通过数据库数据来新建或重建索引,实现自定义的DataProvider就ok了。
总结来说,Zoie是个不错的开源项目,会适合一些站内检索需求。至于它的发展如何就很难说了,LinkedIn开源的几个项目都没什么发展,和Facebook形成了较为鲜明的对比,所以Zoie是否会持续发展下去也很难说,需要看社区及其作者是否能推动这个项目。不过,鉴于这个系统并不复杂,应用及扩展成本是可以控制的。
=============================== 华丽的终止符 ================================
相关日志
4 Responses
留下评论
五月 8th, 2010 at 9:29 下午
[...] Zoie是LinkedIn开源的基于lucene的实时检索系统,对于它的介绍及初步使用可参考我的上一篇文章“使用Zoie构建实时检索系统”。在初步研究并理解了Zoie的源码实现后,本文分析一下Zoie的实现。 [...]
十月 10th, 2010 at 3:43 上午
[...] (用Zoie构建实时检索系统 [...]
十月 10th, 2011 at 1:58 下午
不知道博主有没有将zoie与solr整合过。。
[回复]
kafka0102 回复:
十月 10th, 2011 at 9:59 下午
zoie是有solr的plugin的。我没整合过。
[回复]