<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>kafka0102的边城客栈 &#187; 实时检索</title>
	<atom:link href="http://www.kafka0102.com/tag/%e5%ae%9e%e6%97%b6%e6%a3%80%e7%b4%a2/feed" rel="self" type="application/rss+xml" />
	<link>http://www.kafka0102.com</link>
	<description>要有最朴素的生活与最遥远的梦想，即使明日天寒地冻、路远马亡。</description>
	<lastBuildDate>Sat, 18 Jun 2011 04:20:42 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>twitter的新搜索架构</title>
		<link>http://www.kafka0102.com/2010/10/347.html</link>
		<comments>http://www.kafka0102.com/2010/10/347.html#comments</comments>
		<pubDate>Sat, 09 Oct 2010 19:42:46 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[lucene]]></category>
		<category><![CDATA[search]]></category>
		<category><![CDATA[twitter]]></category>
		<category><![CDATA[zoie]]></category>
		<category><![CDATA[实时检索]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=347</guid>
		<description><![CDATA[当习惯于粗略浏览google reader中的未读条目后便似完成任务般的“全部标记已读”，我就经常性的错过不错的精彩条目，比如这篇 Twitter's New Search Architecture。我是订阅了engineering.twitter.com的，但这并不影响我没有注意到它就把它mark过。今天看到这篇文章时，已经有别的网站将该文翻译成中文了，比如新 Twitter，新搜索，比如 新 Twitter，新搜索，我在这也就算是炒冷饭了。]]></description>
			<content:encoded><![CDATA[<p>当习惯于粗略浏览google reader中的未读条目后便似完成任务般的“全部标记已读”，我就经常性的错过不错的精彩条目，比如这篇 <a href="http://engineering.twitter.com/2010/10/twitters-new-search-architecture.html" target="_blank">Twitter&#8217;s New Search Architecture</a>。我是订阅了engineering.twitter.com的，但这并不影响我没有注意到它就把它mark过。今天看到这篇文章时，已经有别的网站将该文翻译成中文了，比如<a href="http://news.cnblogs.com/n/76524/ " target="_blank">新 Twitter，新搜索</a>，比如 <a href="http://www.techcrunchchina.com/5144" target="_blank">新 Twitter，新搜索</a>，我在这也就算是炒冷饭了。</p>
<p>twitter之前的搜索使用的是被它收购的Summize公司的技术，是基于mysql的搜索，具体技术细节不祥。<a href="http://blog.twitter.com/2008/07/finding-perfect-match.html" target="_blank"> Finding A Perfect Match</a>有提到，Summize也是个初创公司，做的就是twitter搜索，而twitter看中了Summize的技术和团队，便收编它为正规军。不过，随着twitter数据量的激增及对实时搜索的高要求（每秒1000条推的索引更新和12000次查询，一天下来有10亿次查询），原有的技术架构越来越不能满足需求，所以twitter转向了lucene。这除了lucene拥有诸多的优点，也是因为twitter自家有人是lucene的commiter。</p>
<p>当然，lucene也有缺点，文中有提到twitter对lucene的一些改进点如下，这些点已经或将被提到lucene代码中：<br />
（1）改进GC性能。<br />
（2）无锁的数据结构和算法<br />
（3）逆序遍历posting列表（是指希望默认的查询排序方式是按照添加的索引顺序，而不是需要显示指定排序字段，以提高速度？）<br />
（4）有效的早期query终止（是指对查询做了超时限制，避免查询因为读磁盘等原因被hang住？）</p>
<p>就像之前的linkedin开源过一个基于lucene的实时搜索项目<a href="http://code.google.com/p/zoie/" target="_blank">zoie</a> （<a href="http://www.kafka0102.com/2010/05/119.html" target="_blank">用Zoie构建实时检索系统</a> <a href="http://www.kafka0102.com/2010/05/133.html" target="_blank">实时检索系统Zoie实现分析</a>），对于social应用，现在越来越追求搜索的实时性。lucene在2.9版本引入了NRT（Near Real-Time search），算是对实时搜索需求的初始回应，但NRT不是RT，根据应用特点不同，一般能做到秒级别到分钟级别的准实时搜索。lucene的RT搜索目前还在branch中缓慢的孕育，twitter为其贡献着代码并充当了很好的实战的先头兵，这个很赞的说。</p>
<p>期待着lucene和solr的RT搜索能早些出来，这样我可能会考虑迁移现在的站内搜索架构。对于查询量不大更新量更不大的站内搜索来说，将建索引和实时查询做到一个server中，而取代master-slave结构，既简化了架构，也提供了更好的查询效果。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/10/347.html/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>实时检索系统Zoie实现分析</title>
		<link>http://www.kafka0102.com/2010/05/133.html</link>
		<comments>http://www.kafka0102.com/2010/05/133.html#comments</comments>
		<pubDate>Sun, 09 May 2010 05:28:29 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[search]]></category>
		<category><![CDATA[LinkedIn zoie]]></category>
		<category><![CDATA[zoie]]></category>
		<category><![CDATA[实时检索]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=133</guid>
		<description><![CDATA[Zoie是LinkedIn开源的基于lucene的实时检索系统，对于它的介绍及初步使用可参考我的上一篇文章“使用Zoie构建实时检索系统”。在初步研究并理解了Zoie的源码实现后，本文分析一下Zoie的实现。]]></description>
			<content:encoded><![CDATA[<p>Zoie是LinkedIn开源的基于lucene的实时检索系统，对于它的介绍及初步使用可参考我的上一篇文章“<a href="http://www.kafka0102.com/2010/05/%E4%BD%BF%E7%94%A8zoie%E6%9E%84%E5%BB%BA%E5%AE%9E%E6%97%B6%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F/">使用Zoie构建实时检索系统</a>”。在初步研究并理解了Zoie的源码实现后，本文分析一下Zoie的实现。</p>
<h2>实时检索的核心原理</h2>
<p>通常的检索系统中，建索引和查询是分开的，即建索引是离线的，新的索引会以一定频率（比如每隔5分钟）供查询端使用。对于一些站内检索来说，这种延迟性使得：不需要建索引的速度足够快（只要能跟的上提交频率就行），查询的效果不必完全精确。而要取得实时检索效果，典型的思路是：建索引和查询是在一个进程内，这样每一次的添加索引都会被下一次的查询用到，但这里面的细节还是需要好好琢磨解决的，下面就给出Zoie的基于Lucene的解决方案：索引分两种，ram index和disk index。建索引的过程是：首先建立ram index，因为是内存操作，这个过程通常较快，建完后会重新打开IndexReader，使查询端能看到最新的索引；当内存中的索引文档数达到阈值（10000）或者间隔时间达到阈值（自定义），一个后台线程就将ram index合并到disk index里去，完成后清空已经无用的ram index，并重新打开disk index的IndexReader供查询使用（这里面有个autowarm IndexReader的过程）。特别指出的是，Zoie的ram index有两个，这使得当一个ram index在和disk index做合并操作时（这个过程可能会很耗时），另一个ram index仍能提供建索引的操作。对于查询，使用的索引就包括两个ram index和一个disk index，所以只要索引在内存里建好，就能查询到最新的数据。</p>
<h2>实现概览</h2>
<p>下面简要说明Zoie的核心接口和类。</p>
<p>ZoieSystem：这个类是对外的核心类，它提供了诸多方法供外界使用，但它本身就像个Facade，封装了其成员的一系列方法。</p>
<p>DataConsumer：顾名思义，这个接口是用来消费数据也就是建索引的。实时建索引时，ZoieSystem默认使用的DataConsumer是RealtimeIndexDataLoader。在consume数据时，RealtimeIndexDataLoader主要是将数据转换成内部结构后交给另一个DataConsumer即RAMLuceneIndexDataLoader真正在内存里建索引，之后如果当前处理的索引数达到阈值，RealtimeIndexDataLoader会notify LoaderThread，而LoaderThread会调用DiskLuceneIndexDataLoader来合并索引。</p>
<p>DiskSearchIndex和RAMSearchIndex：这两个类是Zoie操作索引结构的，比如获取或打开指定目录的IndexReader、IndexWriter，更新索引写盘等操作。</p>
<p>DataProvider：这个结构表示数据提供者。查看Zoie代码，发现如果在索引的过程中程序挂掉，内存中的索引就有可能丢失，解决这个问题的方法可以是，在DataProvider端做控制，最直接的，当重启程序时，重放之前一段时间的数据即可（因为Zoie能做到定期刷数据，所以可计算出需要回放的时间点）。</p>
<h2>建索引的过程</h2>
<p>上面已经对建索引过程做了一些说明，下面配上Zoie wiki上的图再形象化些。分析它的实现时，有个RAM需要重点关注，它包含了两个RAMSearchIndex（Ram A和Ram B）和一个DiskSearchIndex对象成员，并且Ram A和Ram B也同时扮演Ram writable和Ram readable，建索引时用的是Ram writable，查询时用的是Ram readable。通过下面的图可以看到，Ram A和Ram B有个交换和清空的过程：1）RAM交换发生在Ram A要合并到Disk Index前，把A的数据挪到Ram B，使新的Ram A开始接收处理客户端建索引请求，而Ram B不再接收数据而专心合并索引。2）在合并索引完成后，Ram B就需要清空了。</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/05/timeline.jpg"><img class="aligncenter size-full wp-image-135" title="zoie index timeline" src="http://www.kafka0102.com/wp-content/uploads/2010/05/timeline.jpg" alt="" width="635" height="476" /></a></p>
<h2>删除数据</h2>
<p>Zoie没有提供删除索引的接口，它认为每一次的提交或者是add或者是update。在建索引时，Zoie先将document的uid映射成docid，如果发现docid已存在，就需要标记删除该doc。lucene里表示删除标记的文件是xx.del，Zoie当然会最终将标记更新到这个文件，但因为索引结构有两个Ram index和一个disk index，并且不能每一次标记删除就更新disk index，所以Zoie在两种SearchIndex对象里记录了删除标记。当建索引，Zoie同时更新三个SearchIndex内存索引的删除标记，而在查询时会过滤掉被删除的doc。Zoie还提供了expungeDeletes方法来清除disk index中垃圾索引数据，这个操作因为耗时长而适合在凌晨进行，但查看Zoie的代码，这个操作只提供了通过JMX手动实现而没有自动执行的时机。</p>
<h2>ZoieMergePolicy</h2>
<p>Zoie的索引合并策略实现可以说是它的很大亮点。lucene中默认使用的MergePolicy是LogByteSizeMergePolicy，这个MergePolicy在选择合并的segment时，是计算segment的总的字节大小。这种方式的一个缺陷是，像用户profile这种如果update操作多的话（每次update会有一次delete操作），会使得一些segment看起来很大，实际上其中有效的索引数据会很少，这些无用索引数据会给查询带来负担。ZoieMergePolicy在计算索引大小时就去除了已删除的doc，使计算更加精确，下图是Zoie给出的两种MergePolicy的性能对比，随着时间的增长，因为被标记delete的doc越来越多，LogByteSizeMergePolicy的查询性能就下降的很厉害了。但是，如果每天低峰期做一次expungeDeletes操作，并且每天提交的delete操作不多的话，LogByteSizeMergePolicy的问题也不是很大。还有一点，Zoie对segment的数量处理上，默认是最多大段10个、小段20个（可通过合并引子控制），通常段数保持在十几个，因为段数比较多，查询时的性能会受些影响，好处是一些旧的大段不会被频繁合并。</p>
<p><a href="http://www.kafka0102.com/wp-content/uploads/2010/05/mergeperf.png"><img class="aligncenter size-full wp-image-134" title="zoie merge perf" src="http://www.kafka0102.com/wp-content/uploads/2010/05/mergeperf.png" alt="" width="640" height="480" /></a></p>
<h2>总结</h2>
<p>上面是对Zoie的实现的简要分析，如有理解不准确的误人之处，敬请指出并谅解。</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;"><!-- 		@page { margin: 2cm } 		P { margin-bottom: 0.21cm } 		H2 { margin-bottom: 0.21cm } 		H2.western { font-family: "DejaVu Sans", sans-serif; font-size: 14pt; font-style: italic } 		H2.cjk { font-family: "AR PL UKai CN"; font-size: 14pt; font-style: italic } 		H2.ctl { font-family: "AR PL UKai CN"; font-size: 14pt; font-style: italic } 		A:link { so-language: zxx } --></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> Zoie</span>是<span style="font-family: AR PL UMing CN,serif;">LinkedIn</span>开源的基于<span style="font-family: AR PL UMing CN,serif;">lucene</span>的实时检索系统，对于它的介绍及初步使用可参考我的上一篇文章“<span style="font-family: AR PL UMing CN,serif;"><a href="../2010/05/%E4%BD%BF%E7%94%A8zoie%E6%9E%84%E5%BB%BA%E5%AE%9E%E6%97%B6%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F/">http://www.kafka0102.com/2010/05/%E4%BD%BF%E7%94%A8zoie%E6%9E%84%E5%BB%BA%E5%AE%9E%E6%97%B6%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F/</a>”</span>。在初步研究并理解了<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的源码实现后，本文分析一下<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的实现。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk">实时检索的核心原理<span style="font-family: DejaVu Sans,sans-serif;"> </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span>通常的检索系统中，建索引和查询是分开的，即建索引是离线的，新的索引会以一定频率（比如每隔<span style="font-family: AR PL UMing CN,serif;">5</span>分钟）供查询端使用。对于一些站内检索来说，这种延迟性使得，不需要建索引的速度足够快（只要能跟的上提交频率就行），查询的效果不必完全精确。而要取得实时检索效果，典型的思路是：建索引和查询是在一个进程内，这样每一次的添加索引都会被下一次的查询用到，但这里面的细节还是需要好好琢磨解决的，下面就给出<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的基于<span style="font-family: AR PL UMing CN,serif;">Lucene</span>的解决方案：索引分两种，<span style="font-family: AR PL UMing CN,serif;">ram index</span>和<span style="font-family: AR PL UMing CN,serif;">disk index</span>。建索引的过程是：首先建立<span style="font-family: AR PL UMing CN,serif;">ram index</span>，因为是内存操作，这个过程通常较快，建完后会重新打开<span style="font-family: AR PL UMing CN,serif;">IndexReader</span>，使查询端能看到最新的索引；当内存中的索引文档数达到阈值（<span style="font-family: AR PL UMing CN,serif;">10000</span>）或者间隔时间达到阈值（自定义），一个后台线程就将<span style="font-family: AR PL UMing CN,serif;">ram index</span>合并到<span style="font-family: AR PL UMing CN,serif;">disk index</span>里去，完成后清空已经无用的<span style="font-family: AR PL UMing CN,serif;">ram index</span>，并重新打开<span style="font-family: AR PL UMing CN,serif;">disk index</span>的<span style="font-family: AR PL UMing CN,serif;">IndexReader</span>供查询使用（这里面有个<span style="font-family: AR PL UMing CN,serif;">autowarm IndexReader</span>的过程）。特别指出的是，<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的<span style="font-family: AR PL UMing CN,serif;">ram index</span>有两个，这使得当一个<span style="font-family: AR PL UMing CN,serif;">ram index</span>在和<span style="font-family: AR PL UMing CN,serif;">disk index</span>做合并操作时（这个过程可能会很耗时），另一个<span style="font-family: AR PL UMing CN,serif;">ram index</span>仍能提供建索引的操作。对于查询，使用的索引就包括两个<span style="font-family: AR PL UMing CN,serif;">ram index</span>和一个<span style="font-family: AR PL UMing CN,serif;">disk index</span>，所以只要索引在内存里建好，就能查询到最新的数据。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk">实现概览<span style="font-family: DejaVu Sans,sans-serif;"> </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span>下面简要说明<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的核心接口和类。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> ZoieSystem</span>：这个类是对外的核心类，它提供了诸多方法供外界使用，但它本身就像个<span style="font-family: AR PL UMing CN,serif;">Facade</span>，封装了其成员的一系列方法。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> DataConsumer</span>：顾名思义，这个接口是用来消费数据也就是建索引的。实时建索引时，<span style="font-family: AR PL UMing CN,serif;">ZoieSystem</span>默认使用的<span style="font-family: AR PL UMing CN,serif;">DataConsumer</span>是<span style="font-family: AR PL UMing CN,serif;">RealtimeIndexDataLoader</span>。在<span style="font-family: AR PL UMing CN,serif;">consume</span>数据时，<span style="font-family: AR PL UMing CN,serif;">RealtimeIndexDataLoader</span>主要是将数据转换成内部结构后交给另一个<span style="font-family: AR PL UMing CN,serif;">DataConsumer</span>即<span style="font-family: AR PL UMing CN,serif;">RAMLuceneIndexDataLoader</span>真正在内存里建索引，之后如果当前处理的索引数达到阈值，<span style="font-family: AR PL UMing CN,serif;">RealtimeIndexDataLoader</span>会<span style="font-family: AR PL UMing CN,serif;">notify LoaderThread</span>，而<span style="font-family: AR PL UMing CN,serif;">LoaderThread</span>会调用<span style="font-family: AR PL UMing CN,serif;">DiskLuceneIndexDataLoader</span>来合并索引。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> DiskSearchIndex</span>和<span style="font-family: AR PL UMing CN,serif;">RAMSearchIndex</span>：这两个类是<span style="font-family: AR PL UMing CN,serif;">Zoie</span>操作索引结构的，比如获取或打开指定目录的<span style="font-family: AR PL UMing CN,serif;">IndexReader</span>、<span style="font-family: AR PL UMing CN,serif;">IndexWriter</span>，更新索引写盘等操作。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> DataProvider</span>：这个结构表示数据提供者。查看<span style="font-family: AR PL UMing CN,serif;">Zoie</span>代码，发现如果在索引的过程中程序挂掉，内存中的索引就有可能丢失，解决这个问题的方法可以是，在<span style="font-family: AR PL UMing CN,serif;">DataProvider</span>端做控制，最直接的，当重启程序时，重放之前一段时间的数据即可（因为<span style="font-family: AR PL UMing CN,serif;">Zoie</span>能做到定期刷数据，所以可计算出需要回放的时间点）。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk">建索引的过程<span style="font-family: DejaVu Sans,sans-serif;"> </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span>上面已经对建索引过程做了一些说明，下面配上<span style="font-family: AR PL UMing CN,serif;">Zoie wiki</span>上的图再形象化些。分析它的实现时，有个<span style="font-family: AR PL UMing CN,serif;">RAM</span>需要重点关注，它包含了两个<span style="font-family: AR PL UMing CN,serif;">RAMSearchIndex</span>（<span style="font-family: AR PL UMing CN,serif;">Ram A</span>和<span style="font-family: AR PL UMing CN,serif;">Ram B</span>）和一个<span style="font-family: AR PL UMing CN,serif;">DiskSearchIndex</span>对象成员，并且<span style="font-family: AR PL UMing CN,serif;">Ram A</span>和<span style="font-family: AR PL UMing CN,serif;">Ram B</span>也同时扮演<span style="font-family: AR PL UMing CN,serif;">Ram writebal</span>和<span style="font-family: AR PL UMing CN,serif;">Ram readable</span>，建索引时用的是<span style="font-family: AR PL UMing CN,serif;">Ram writebal</span>，查询时用的是<span style="font-family: AR PL UMing CN,serif;">Ram readable</span>。通过下面的图可以看到，<span style="font-family: AR PL UMing CN,serif;">Ram A</span>和<span style="font-family: AR PL UMing CN,serif;">Ram B</span>有个交换和清空的过程：<span style="font-family: AR PL UMing CN,serif;">1</span>）<span style="font-family: AR PL UMing CN,serif;">RAM</span>交换发生在<span style="font-family: AR PL UMing CN,serif;">Ram A</span>要合并到<span style="font-family: AR PL UMing CN,serif;">Disk Index</span>前，把<span style="font-family: AR PL UMing CN,serif;">A</span>的数据挪到<span style="font-family: AR PL UMing CN,serif;">Ram B</span>，使新的<span style="font-family: AR PL UMing CN,serif;">Ram A</span>开始接收处理客户端建索引请求，而<span style="font-family: AR PL UMing CN,serif;">Ram B</span>不再接收数据而专心合并索引。<span style="font-family: AR PL UMing CN,serif;">2</span>）在合并索引完成后，<span style="font-family: AR PL UMing CN,serif;">Ram B</span>就需要清空了。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk">删除数据<span style="font-family: DejaVu Sans,sans-serif;"> </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> Zoie</span>没有提供删除索引的接口，它认为每一次的提交或者是<span style="font-family: AR PL UMing CN,serif;">add</span>或者是<span style="font-family: AR PL UMing CN,serif;">update</span>。在建索引时，<span style="font-family: AR PL UMing CN,serif;">Zoie</span>先将<span style="font-family: AR PL UMing CN,serif;">document</span>的<span style="font-family: AR PL UMing CN,serif;">uid</span>映射成<span style="font-family: AR PL UMing CN,serif;">docid</span>，如果发现<span style="font-family: AR PL UMing CN,serif;">docid</span>已存在，就需要标记删除该<span style="font-family: AR PL UMing CN,serif;">doc</span>。<span style="font-family: AR PL UMing CN,serif;">lucene</span>里表示删除标记的文件是<span style="font-family: AR PL UMing CN,serif;">xx.del</span>，<span style="font-family: AR PL UMing CN,serif;">Zoie</span>当然会最终将标记更新到这个文件，但因为索引结构有两个<span style="font-family: AR PL UMing CN,serif;">Ram index</span>和一个<span style="font-family: AR PL UMing CN,serif;">disk index</span>，并且不能每一次标记删除就更新<span style="font-family: AR PL UMing CN,serif;">disk index</span>，所以<span style="font-family: AR PL UMing CN,serif;">Zoie</span>在两种<span style="font-family: AR PL UMing CN,serif;">SearchIndex</span>对象里记录了删除标记。当建索引，<span style="font-family: AR PL UMing CN,serif;">Zoie</span>同时更新内存索引的删除标记，而在查询时会过滤掉被删除的<span style="font-family: AR PL UMing CN,serif;">doc</span>。<span style="font-family: AR PL UMing CN,serif;">Zoie</span>还提供了<span style="font-family: AR PL UMing CN,serif;">expungeDeletes</span>方法来清除<span style="font-family: AR PL UMing CN,serif;">disk index</span>中垃圾索引数据，这个操作因为耗时长而适合在凌晨进行，但查看<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的代码，这个操作只提供了通过<span style="font-family: AR PL UMing CN,serif;">JMX</span>手动实现而没有自动执行的处理。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk"><span style="font-family: DejaVu Sans,sans-serif;">ZoieMergePolicy </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> Zoie</span>的索引合并策略实现可以说是它的很大亮点。<span style="font-family: AR PL UMing CN,serif;">lucene</span>中默认使用的<span style="font-family: AR PL UMing CN,serif;">MergePolicy</span>是<span style="font-family: AR PL UMing CN,serif;">LogByteSizeMergePolicy</span>，这个<span style="font-family: AR PL UMing CN,serif;">MergePolicy</span>在选择合并的<span style="font-family: AR PL UMing CN,serif;">segment</span>时，是计算<span style="font-family: AR PL UMing CN,serif;">segment</span>的总的字节大小。这种方式的一个缺陷是，像用户<span style="font-family: AR PL UMing CN,serif;">profile</span>这种如果<span style="font-family: AR PL UMing CN,serif;">update</span>操作多的话（每次<span style="font-family: AR PL UMing CN,serif;">update</span>会有一次<span style="font-family: AR PL UMing CN,serif;">delete</span>操作），会使得一些<span style="font-family: AR PL UMing CN,serif;">segment</span>看起来很大，实际上其中有效的索引数据会很少，这些无用索引数据会给查询带来负担。<span style="font-family: AR PL UMing CN,serif;">ZoieMergePolicy</span>在计算索引大小时就去除了已删除的<span style="font-family: AR PL UMing CN,serif;">doc</span>，使计算更加精确，下图是<span style="font-family: AR PL UMing CN,serif;">Zoie</span>给出的两种<span style="font-family: AR PL UMing CN,serif;">MergePolicy</span>的性能对比。但是，如果每天低峰期做一次<span style="font-family: AR PL UMing CN,serif;">expungeDeletes</span>操作，并且每天提交的<span style="font-family: AR PL UMing CN,serif;">delete</span>操作不多的话，<span style="font-family: AR PL UMing CN,serif;">LogByteSizeMergePolicy</span>的问题也不是很大。还有一点，<span style="font-family: AR PL UMing CN,serif;">Zoie</span>对<span style="font-family: AR PL UMing CN,serif;">segment</span>的数量处理上，默认是最多大段<span style="font-family: AR PL UMing CN,serif;">10</span>个、小段<span style="font-family: AR PL UMing CN,serif;">20</span>个（可通过合并引子控制），通常段数保持在十几个，因为段数比较多，查询时的性能会受些影响，好处是一些旧的大段不会被频繁合并。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<h2 class="cjk">总结<span style="font-family: DejaVu Sans,sans-serif;"> </span></h2>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span>上面是对<span style="font-family: AR PL UMing CN,serif;">Zoie</span>的实现的简要分析，仅此而已。<span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
<p style="margin-bottom: 0cm;"><span style="font-family: AR PL UMing CN,serif;"> </span></p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/05/133.html/feed</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>使用Zoie构建实时检索系统</title>
		<link>http://www.kafka0102.com/2010/05/119.html</link>
		<comments>http://www.kafka0102.com/2010/05/119.html#comments</comments>
		<pubDate>Sat, 08 May 2010 17:46:59 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[search]]></category>
		<category><![CDATA[LinkedIn]]></category>
		<category><![CDATA[zoie]]></category>
		<category><![CDATA[实时检索]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=119</guid>
		<description><![CDATA[ Zoie是LinkedIn开源的实时检索系统，它本身用于LinkedIn的用户profile检索。且不说专业的搜索引擎，大多数的站内检索基本都不是实时的，一般都会有几分钟的延迟。但LinkedIn认为，用户profile信息的检索需要实时的效果，因为如果搜索结果不正确或不理想会很影响用户体验。所以，LinkedIn的Zoie实现了秒级别的实时检索效果。Zoie在LinkedIn跑了有两年多时间，所以这个开源项目可以说比较成熟的，这个项目提供了较为丰富的功能和工具，除了核心的API外，它还提供了管理和监控工具、嵌入jetty的Web server demo、一些实用的DataProvider等。对于社区类需要提供用户信息检索的系统来说，使用Zoie或许是个很不错的选择。]]></description>
			<content:encoded><![CDATA[<p>Zoie是LinkedIn开源的实时检索系统，它本身用于LinkedIn的用户profile页检索。且不说专业的搜索引擎，大多数的站内检索基本都不是实时的，一般都会有几分钟的延迟。但LinkedIn认为，用户profile信息的检索需要实时的效果，因为如果搜索结果不正确或不理想会很影响用户体验。所以，LinkedIn的Zoie实现了秒级别的实时检索效果。Zoie在LinkedIn跑了有两年多时间，这个开源项目可以说是起点就比较成熟的，这个项目提供了较为丰富的功能和工具，除了核心的API外，它还提供了管理和监控工具、嵌入jetty的Web server demo、一些实用的DataProvider等。实现上自然是基于lucene了，也使用了一些我需要回顾或者了解的库和框架。对于社区类需要提供用户信息检索的系统来说，使用Zoie或许是个很不错的选择。</p>
<p>下面将要介绍的使用Zoie API来编写检索demo，并且为了省事，我的code sample直接来自于Zoie的Wiki（<a href="http://snaprojects.jira.com/wiki/display/ZOIE/Code+Samples" target="blank">http://snaprojects.jira.com/wiki/display/ZOIE/Code+Samples</a>），但会对其中的使用做些中文版的说明。由于Zoie是个很有针对性的实现，所以相比于Solr这样的通用系统来说，Zoie的代码要“直白”很多，这也促使我拿出精力把它的核心代码研究了一番。总结下来有两点：1）要写一个基于lucene的精致的检索系统需要对lucene有深入的理解，针对实时性检索需求，Zoie本身就对lucene做了很多扩展。2）Zoie的代码结构和代码风格有些糟糕，我看到git里有.project文件，所以可见其作者应该也是基于eclipse开发的，可他的代码就不能好好format下？并且包名和类层次也很诡异。闲言少叙（让我再次想起俞平伯先生），下面罗列下使用Zoie API的code sample。</p>
<p>在检索server端，需要索引的数据总要以一种结构表示，比如一个MAP或POJO之类的。让Zoie来索引数据，就需要Zoie知道你需要索引哪些Field到Document（lucene里的Document）里。说白了，要提供一份要索引的数据及将数据转换成Document结构的接口。这里的数据假定是一个Data：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> Data<span style="color: #009900;">&#123;</span>
<span style="color: #000066; font-weight: bold;">long</span> id<span style="color: #339933;">;</span>
<span style="color: #003399;">String</span> content<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>转换数据的工作由实现ZoieIndexableInterpreter接口的类实现，代码片断如下：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> DataIndexable <span style="color: #000000; font-weight: bold;">implements</span> ZoieIndexable <span style="color: #009900;">&#123;</span>
  <span style="color: #000000; font-weight: bold;">private</span> Data _data<span style="color: #339933;">;</span>
  <span style="color: #000000; font-weight: bold;">public</span> DataIndexable<span style="color: #009900;">&#40;</span>Data data<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    _data <span style="color: #339933;">=</span> data<span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">long</span> getUID<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">return</span> _data.<span style="color: #006633;">id</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> IndexingReq<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> buildIndexingReqs<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// it is possible we want to map 1 data object to multiple lucene documents</span>
    <span style="color: #666666; font-style: italic;">// but not for this example</span>
    <span style="color: #003399;">Document</span> doc <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">Document</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    doc.<span style="color: #006633;">add</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">Field</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;content&quot;</span>,_data.<span style="color: #006633;">content</span>,Store.<span style="color: #006633;">NO</span>,Index.<span style="color: #006633;">ANALYZED</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// no need to add the id field, Zoie will manage the id for you</span>
    <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> IndexingReq<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#123;</span><span style="color: #000000; font-weight: bold;">new</span> IndexingReq<span style="color: #009900;">&#40;</span>doc<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// the following methods in this example are kind of hacky,</span>
  <span style="color: #666666; font-style: italic;">// but it is designed to be used when information needed to determine whether documents</span>
  <span style="color: #666666; font-style: italic;">// are to be deleted and/or skipped are only known at runtime</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">boolean</span> isDeleted<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #0000ff;">&quot;_MARKED_FOR_DELETE&quot;</span>.<span style="color: #006633;">equals</span><span style="color: #009900;">&#40;</span>_data.<span style="color: #006633;">content</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">boolean</span> isSkip<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #0000ff;">&quot;_MARKED_FOR_SKIP&quot;</span>.<span style="color: #006633;">equals</span><span style="color: #009900;">&#40;</span>_data.<span style="color: #006633;">content</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">class</span> DataIndexableInterpreter <span style="color: #000000; font-weight: bold;">implements</span> ZoieIndexableInterpreter <span style="color: #009900;">&#123;</span>
  <span style="color: #000000; font-weight: bold;">public</span> ZoieIndexable interpret<span style="color: #009900;">&#40;</span>Data src<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> DataIndexable<span style="color: #009900;">&#40;</span>src<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>针对上面的代码，我还需要做一些解释。可以看出的是，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建索引到什么阶段了，这个接口的用处就值得怀疑了。</p>
<p>下面再来构建IndexDecorator。Zoie提供的这个接口是允许客户端（相对于Zoie来说是客户端）对给定的ZoieIndexReader装饰成自定义的IndexReader。就像demo给的那样，除非有必要，否则默认实现就可以了。</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> MyDoNothingFilterIndexReader <span style="color: #000000; font-weight: bold;">extends</span> FilterIndexReader <span style="color: #009900;">&#123;</span>
<span style="color: #000000; font-weight: bold;">public</span> MyDoNothingFilterIndexReader<span style="color: #009900;">&#40;</span>IndexReader reader<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
<span style="color: #000000; font-weight: bold;">super</span><span style="color: #009900;">&#40;</span>reader<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> updateInnerReader<span style="color: #009900;">&#40;</span>IndexReader inner<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
in <span style="color: #339933;">=</span> inner<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">class</span> MyDoNothingIndexReaderDecorator <span style="color: #000000; font-weight: bold;">implements</span> IndexReaderDecorator <span style="color: #009900;">&#123;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> MyDoNothingIndexReaderDecorator decorate<span style="color: #009900;">&#40;</span>ZoieIndexReader indexReader<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> MyDoNothingFilterIndexReader<span style="color: #009900;">&#40;</span>indexReader<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> MyDoNothingIndexReaderDecorator redecorate<span style="color: #009900;">&#40;</span>MyDoNothingIndexReaderDecorator decorated,
                                                    ZoieIndexReader copy<span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">throws</span> <span style="color: #003399;">IOException</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// underlying segment has not changed, just change the inner reader</span>
&nbsp;
    decorated.<span style="color: #006633;">updateInnerReader</span><span style="color: #009900;">&#40;</span>copy<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">return</span> decorated<span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>下面是build Zoie的核心：ZoieSystem。indexingSystem的start()方法将启动新的线程准备接收数据来建索引。</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// index directory</span>
<span style="color: #003399;">File</span> idxDir <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">File</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;myIdxDir&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// create an analyzer</span>
Analyzer analyzer <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> StandardAnalyzer<span style="color: #009900;">&#40;</span>Version.<span style="color: #006633;">LUCENE_CURRENT</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// create similarity</span>
Similarity similarity <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> DefaultSimilarity<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
ZoieIndexableInterpreter myInterpreter <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> DataIndexableInterpreter<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
IndexReaderDecorator decorator <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> MyDoNothingIndexReaderDecorator<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
ZoieSystem indexingSystem <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> ZoieSystem<span style="color: #009900;">&#40;</span>idxDir,         <span style="color: #666666; font-style: italic;">// index direcotry</span>
                                           myInterpreter,  <span style="color: #666666; font-style: italic;">// my interpreter</span>
                                           decorator,      <span style="color: #666666; font-style: italic;">// index decorator</span>
                                           analyzer,       <span style="color: #666666; font-style: italic;">// my analyzer</span>
                                           similarity,     <span style="color: #666666; font-style: italic;">// my similarity</span>
                                           <span style="color: #cc66cc;">1000</span>,           <span style="color: #666666; font-style: italic;">// # events to hold in mem before flushing to disk</span>
                                           <span style="color: #cc66cc;">300000</span>,         <span style="color: #666666; font-style: italic;">// time(ms) to wait before flushing to disk</span>
                                           <span style="color: #000066; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>          <span style="color: #666666; font-style: italic;">// true for realtime</span>
&nbsp;
indexingSystem.<span style="color: #006633;">start</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>  <span style="color: #666666; font-style: italic;">// ready to accept indexing even</span></pre></div></div>

<p>下面给出建索引和查询的使用片断，两者可分别在不同线程执行。<br />
索引线程的代码片断：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000066; font-weight: bold;">long</span> batchVersion <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">while</span><span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">true</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
  Data<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> data <span style="color: #339933;">=</span> buildDataEvents<span style="color: #009900;">&#40;</span>...<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #666666; font-style: italic;">// build a batch of data object to index</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// construct a collection of indexing events</span>
  <span style="color: #003399;">ArrayList</span> eventList <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #003399;">ArrayList</span><span style="color: #009900;">&#40;</span>data.<span style="color: #006633;">length</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000000; font-weight: bold;">for</span> <span style="color: #009900;">&#40;</span>Data datum <span style="color: #339933;">:</span> data<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
    eventList.<span style="color: #006633;">add</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> DataEvent<span style="color: #009900;">&#40;</span>batchVersion,datum<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #666666; font-style: italic;">// do indexing</span>
  indexingSystem.<span style="color: #006633;">consume</span><span style="color: #009900;">&#40;</span>events<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
 <span style="color: #666666; font-style: italic;">// increment my version</span>
  batchVersion<span style="color: #339933;">++;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>上面的buildDataEvents方法是产生数据的方法，比如接收客户端请求的数据（也许它还会阻塞在那？），或者是查询数据库的数据之类的。ZoieSystem的consume方法就是用来消费数据建索引，并且是可以批量处理的。</p>
<p>再看看查询线程的代码片断：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;">	<span style="color: #666666; font-style: italic;">// get the IndexReaders</span>
List<span style="color: #339933;">&amp;</span>gt<span style="color: #339933;">;</span> readerList <span style="color: #339933;">=</span> indexingSystem.<span style="color: #006633;">getIndexReaders</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// MyDoNothingFilterIndexReader instances can be obtained by calling</span>
<span style="color: #666666; font-style: italic;">// ZoieIndexReader.getDecoratedReaders()</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// combine the readers</span>
MultiReader reader <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> MultiReader<span style="color: #009900;">&#40;</span>readerList.<span style="color: #006633;">toArray</span><span style="color: #009900;">&#40;</span><span style="color: #000000; font-weight: bold;">new</span> IndexReader<span style="color: #009900;">&#91;</span>readerList.<span style="color: #006633;">size</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span>,<span style="color: #000066; font-weight: bold;">false</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #666666; font-style: italic;">// do search</span>
IndexSearcher searcher <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> IndexSearcher<span style="color: #009900;">&#40;</span>reader<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
Query q <span style="color: #339933;">=</span> buildQuery<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;myquery&quot;</span>,indexingSystem.<span style="color: #006633;">getAnalyzer</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
TopDocs docs <span style="color: #339933;">=</span> searcher.<span style="color: #006633;">search</span><span style="color: #009900;">&#40;</span>q,<span style="color: #cc66cc;">10</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// return readers</span>
indexingSystem.<span style="color: #006633;">returnIndexReaders</span><span style="color: #009900;">&#40;</span>readerList<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>

<p>懒得解释了，跟使用lucene的查询接口差不多。</p>
<p>上面的代码稍加完善就可以真正跑起来了。Zoie也提供了独立的Server功能，但它的那个Server很有可能是你不想要的，而使用上述的嵌入式接口来实现一个Server显然已经极大简化工作了。Zoie还提供了DataProvider机制及实用的实现，对于像通过数据库数据来新建或重建索引，实现自定义的DataProvider就ok了。</p>
<p>总结来说，Zoie是个不错的开源项目，会适合一些站内检索需求。至于它的发展如何就很难说了，LinkedIn开源的几个项目都没什么发展，和Facebook形成了较为鲜明的对比，所以Zoie是否会持续发展下去也很难说，需要看社区及其作者是否能推动这个项目。不过，鉴于这个系统并不复杂，应用及扩展成本是可以控制的。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/05/119.html/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

