<?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; lucene</title>
	<atom:link href="http://www.kafka0102.com/tag/lucene/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>searchblox&#8211;一个基于lucene的搜索产品</title>
		<link>http://www.kafka0102.com/2010/12/425.html</link>
		<comments>http://www.kafka0102.com/2010/12/425.html#comments</comments>
		<pubDate>Wed, 08 Dec 2010 15:04:40 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[search]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[searchblox]]></category>
		<category><![CDATA[搜索]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=425</guid>
		<description><![CDATA[前两天在solr邮件组看到一封广告帖，一个叫searchblox的搜索产品可免费使用，好奇心驱使我简单了解并使用了一下。searchblox是基于lucene的搜索解决方案，现在的版本已经是6.1,看来也有些年头了。searchblox不是个开源产品，有免费的版本，也有收费的版本，看文档介绍，收费版本除了提供服务支持还多了复制功能。]]></description>
			<content:encoded><![CDATA[<p>前两天在solr邮件组看到一封广告帖，一个叫<a href="http://www.searchblox.com" target="_blank">searchblox</a>的搜索产品可免费使用，好奇心驱使我简单了解并使用了一下。searchblox是基于lucene的搜索解决方案，现在的版本已经是6.1,看来也有些年头了。searchblox不是个开源产品，有免费的版本，也有收费的版本，看文档介绍，收费版本除了提供服务支持还多了复制功能。</p>
<p>功能上看，searchblox集成了爬虫和搜索功能，也提供Http API接口供索引和查询，可以在<a href="http://www.searchblox.com/developers/documentation" target="_blank">http://www.searchblox.com/developers/documentation</a>上更多的了解searchblox的功能。但搜索功能方面，它也就是solr的子集。试用它很简单，下载解压缩后可直接java -jar start.jar通过jetty启动它，访问它的web admin console观察它的功能。就数据源来说，可以通过配置抓取的网站、RSS feed、本地文件夹数据，供searchblox自动索引和查询。如果索引数据来源自数据库等，searchblox没有提供直接支持，但可以使用它的Http API操作（或许它更应该提供多语言的客户端API）。</p>
<p>下面看下它的架构图，可以更好的睽睽它提供的功能：<br />
<a href="http://www.kafka0102.com/wp-content/uploads/2010/12/Drawing1.png"><img class="aligncenter size-full wp-image-426" title="searchblox arch" src="http://www.kafka0102.com/wp-content/uploads/2010/12/Drawing1.png" alt="" width="507" height="374" /></a><br />
在可扩展性方面，它提供了复制的支持，但没有提供shard支持：<br />
<a href="http://www.kafka0102.com/wp-content/uploads/2010/12/ha.png"><img class="aligncenter size-full wp-image-427" title="searchblox ha" src="http://www.kafka0102.com/wp-content/uploads/2010/12/ha.png" alt="" width="500" height="461" /></a></p>
<p>写到这里，其实我都不想对searchblox再评头论足了，它看起来实在不是很吸引人。尽管有免费版本，但免费版本功能残缺、文档匮乏，不交钱想用好searchblox也是件难事。看它网站上列出的客户列表，用户群还不少，只是不知道其中水分怎么样。国外基于lucene等开源库的收费搜索解决方案还是很多的，毕竟使用lucene开发一套好的搜索产品成本还是很可观的。但搜索解决方案无可避免的，会和开源的solr做比较，像searchblox的搜索功能，就不如solr的强大。如果使用solr和某个开源爬虫，搭起一个类searchblox的产品需要做的也就是admin console。当然，即便是开源的solr，用好它用对它也是需要功夫的，尤其是其在中文圈的资料较少，经验积累方面也较欠缺。</p>
<p>searchblox，再见！！！</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/12/425.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>solr SolrIndexSearcher性能问题分析</title>
		<link>http://www.kafka0102.com/2010/10/366.html</link>
		<comments>http://www.kafka0102.com/2010/10/366.html#comments</comments>
		<pubDate>Thu, 21 Oct 2010 15:22:52 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[solr]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[性能]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=366</guid>
		<description><![CDATA[基于solr的新搜索系统已经使用了有段时间，不过之前上的几个库都很小，也没发现什么性能问题。最近上了thread和post库后，性能问题显现出来。这段时间解决的性能问题有好几个，本文只着重于SolrIndexSearcher的search问题。thread库索引大小有400M多，记录数有400多万，原来的基于lucene的系统的查询处理耗时一般在10-30ms，而同样的请求，新系统耗时在50-100ms。而post库索引大小有 6G多，记录数有3000多万，旧系统的查询处理时间一般在30-80ms，而新系统往往需要400-800ms，性能差距还是很明显的。
因为SolrIndexSearcher是基于lucene的IndexSearcher，想必应该是青出于蓝胜于蓝，并且其wiki上给出的性能数据也很不错，所以当我把新系统做完后并没有去细致的测试其性能。但现在系统的性能相比之下确实有些差，并且因为要做多个库的整合搜索，就要避免像thread 这样耗时长的库就成为短板。总之，问题需要定位和解决。]]></description>
			<content:encoded><![CDATA[<h2>问题背景</h2>
<p>基于solr的新搜索系统已经使用了有段时间，不过之前上的几个库都很小，也没发现什么性能问题。最近上了thread和post库后，性能问题显现出来。这段时间解决的性能问题有好几个，本文只着重于SolrIndexSearcher的search问题。thread库索引大小有400M多，记录数有400多万，原来的基于lucene的系统的查询处理耗时一般在10-30ms，而同样的请求，新系统耗时在50-100ms。而post库索引大小有6G多，记录数有3000多万，旧系统的查询处理时间一般在30-80ms，而新系统往往需要400-800ms，性能差距还是很明显的。</p>
<p>因为SolrIndexSearcher是基于lucene的IndexSearcher，想必应该是青出于蓝胜于蓝，并且其wiki上给出的性能数据也很不错，所以当我把新系统做完后并没有去细致的测试其性能。但现在系统的性能相比之下确实有些差，并且因为要做多个库的整合搜索，就要避免像thread这样耗时长的库就成为短板。总之，问题需要定位和解决。</p>
<h2>原因分析</h2>
<p>分析thread应用给搜索server发来的query，query除了包含用户输入的文本，还有两个filter query:1)是fq=fid:1，fid是int型，表示选择某个版块;2）是fq=atm:[int_time1 TO int_time2]，atm是int型，表示帖子发表的时间，默认int_time2是请求时的时间，int_time1是比int_time2小6个月的起始时间。实际测试发现，当去掉两个fq，处理耗时很短（即便有sort返回结果数很多），而两个fq同时存在或则其中某一个存在时，耗时都会变长，尤其是匹配的结果总数较多时。<br />
因为query分为filter query和text query，基于lucene的IndexSearcher实现方式可以有两种：1）是将各filter query和text query联合起来构成一个大的Query（就是个BooleanQuery，BooleanClause之间是MUST关系）；2）是将filter query转成Filter，和text query区分开。旧系统使用的就是方式2。因为Filter是基于Query构造的，所以两种方式按说性能应该差不多。</p>
<p>翻看SolrIndexSearcher的代码，它除了继承IndexSearcher的一系列search方法，还增加了search(QueryResult qr, QueryCommand cmd)，也是我使用的接口。我当然可以认为，solr的这个search方法会对查询过程做了更高效的实现。除去为提升性能（或者降低性能）引入的三种cache代码，以及一些细枝末节的分支代码，SolrIndexSearcher的search方法主要处理过程其实也简单：</p>
<p>1）将一或多个fq构成List&lt;Query&gt;，然后调用getDocSet(final List&lt;Query&gt; queries)得到DocSet，DocSet存放的是匹配的doc id列表，getDocSet方法内部就是遍历queries，对每一个query执行getPositiveDocSet(final Query q)得到该query的DocSet，然后将得到的多个DocSet做合并，得到满足所有fq的DocSet。</p>
<p>2）由DocSet的Filter getTopFilter()方法得到Filter，再联合TopFieldCollector或者TopScoreDocCollector，以及由text query构造的Qeury，调用lucene的void search(Query query, Filter filter, Collector results)，再结合一些扫尾工作，完成一次查询过程。</p>
<p>单看这个过程，也很难发现问题在哪。为了跟踪整个查询的执行路径，我索性将SolrIndexSearcher从solr源码中提取出来，这其间也是费了一些周折，因为SolrIndexSearcher使用了一些包级别的代码并且我需要构造的SolrIndexSearcher2不能直接从SolrQueryRequest获得。通过加入一些代码段的耗时统计，发现上述1）耗时占70%以上，再分析发现，问题出在查询fq的DocSet getDocSetNC(final Query query, final DocSet filter)方法中使用的DocSetCollector。DocSetCollector的代码如下：</p>

<div class="wp_syntax"><div class="code"><pre class="java" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> DocSetCollector <span style="color: #000000; font-weight: bold;">extends</span> Collector <span style="color: #009900;">&#123;</span>
  <span style="color: #000066; font-weight: bold;">int</span> pos<span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span>
  OpenBitSet bits<span style="color: #339933;">;</span>
  <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #000066; font-weight: bold;">int</span> maxDoc<span style="color: #339933;">;</span>
  <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #000066; font-weight: bold;">int</span> smallSetSize<span style="color: #339933;">;</span>
  <span style="color: #000066; font-weight: bold;">int</span> base<span style="color: #339933;">;</span>
  <span style="color: #666666; font-style: italic;">// in case there aren't that many hits, we may not want a very sparse</span>
  <span style="color: #666666; font-style: italic;">// bit array.  Optimistically collect the first few docs in an array</span>
  <span style="color: #666666; font-style: italic;">// in case there are only a few.</span>
  <span style="color: #000000; font-weight: bold;">final</span> <span style="color: #000066; font-weight: bold;">int</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> scratch<span style="color: #339933;">;</span>
  DocSetCollector<span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">int</span> smallSetSize, <span style="color: #000066; font-weight: bold;">int</span> maxDoc<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">smallSetSize</span> <span style="color: #339933;">=</span> smallSetSize<span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">maxDoc</span> <span style="color: #339933;">=</span> maxDoc<span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">this</span>.<span style="color: #006633;">scratch</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> <span style="color: #000066; font-weight: bold;">int</span><span style="color: #009900;">&#91;</span>smallSetSize<span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
  <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000066; font-weight: bold;">void</span> collect<span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">int</span> doc<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>
    doc <span style="color: #339933;">+=</span> base<span style="color: #339933;">;</span>
    <span style="color: #666666; font-style: italic;">// optimistically collect the first docs in an array</span>
    <span style="color: #666666; font-style: italic;">// in case the total number will be small enough to represent</span>
    <span style="color: #666666; font-style: italic;">// as a small set like SortedIntDocSet instead...</span>
    <span style="color: #666666; font-style: italic;">// Storing in this array will be quicker to convert</span>
    <span style="color: #666666; font-style: italic;">// than scanning through a potentially huge bit vector.</span>
    <span style="color: #666666; font-style: italic;">// FUTURE: when search methods all start returning docs in order, maybe</span>
    <span style="color: #666666; font-style: italic;">// we could have a ListDocSet() and use the collected array directly.</span>
    <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>pos <span style="color: #339933;">&amp;</span>lt<span style="color: #339933;">;</span> scratch.<span style="color: #006633;">length</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
      scratch<span style="color: #009900;">&#91;</span>pos<span style="color: #009900;">&#93;</span><span style="color: #339933;">=</span>doc<span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span> <span style="color: #000000; font-weight: bold;">else</span> <span style="color: #009900;">&#123;</span>
      <span style="color: #666666; font-style: italic;">// this conditional could be removed if BitSet was preallocated, but that</span>
      <span style="color: #666666; font-style: italic;">// would take up more memory, and add more GC time...</span>
      <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>bits<span style="color: #339933;">==</span><span style="color: #000066; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span> bits <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> OpenBitSet<span style="color: #009900;">&#40;</span>maxDoc<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
      bits.<span style="color: #006633;">fastSet</span><span style="color: #009900;">&#40;</span>doc<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    pos<span style="color: #339933;">++;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">public</span> DocSet getDocSet<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;">if</span> <span style="color: #009900;">&#40;</span>pos<span style="color: #339933;">&amp;</span>lt<span style="color: #339933;">;=</span>scratch.<span style="color: #006633;">length</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
      <span style="color: #666666; font-style: italic;">// assumes docs were collected in sorted order!</span>
      <span style="color: #000000; font-weight: bold;">return</span> <span style="color: #000000; font-weight: bold;">new</span> SortedIntDocSet<span style="color: #009900;">&#40;</span>scratch, pos<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span> <span style="color: #000000; font-weight: bold;">else</span> <span style="color: #009900;">&#123;</span>
      <span style="color: #666666; font-style: italic;">// set the bits for ids that were collected in the array</span>
      <span style="color: #000000; font-weight: bold;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">int</span> i<span style="color: #339933;">=</span><span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> i</pre></div></div>

<p>可以看到，DocSetCollector使用两个变量来保存collect的doc id，当collect的doc数小于smallSetSize时使用数组scratch，否则使用OpenBitSet bits。getDocSetNC中构造DocSetCollector的代码是：DocSetCollector collector = new DocSetCollector(maxDoc()&gt;&gt;6, maxDoc());。问题就出在OpenBitSet，以thread用例来说，满足fq=fid:1的doc数有200万之多（总doc数有400万）,而fq=atm:[int_time1 TO int_time2]有时也有百万之多，这使得DocSetCollector在collect时使用OpenBitSet.fastSet(int index)置位。尽管OpenBitSet要比BitSet高效，但我在本机测试发现，OpenBitSet200万次的fastSet需要50ms，所以不难理解，匹配fq的文档数越多，DocSetCollector就越慢，进而SolrIndexSearcher就越慢。不过，另一方面，当多个fq的DocSet做合并后，实际有效的DocSet大小可能很小，而再和text query做合并后，得到的DocSet就会更小。所以，当索引的文档多时，solr的这种处理效率上就低得多。</p>
<p>我粗略浏览了lucene的相关代码，lucence在通过各种Scorer操作匹配Query的结果时没有使用OpenBitSet，而是主要使用队列、堆等集合来操作匹配结果的收集、合并等操作（各种Scorer的具体实现没有细看，有时间再看吧），而Collector.collect的会是最后真正匹配的结果。从实际测试的效果来看，性能要比solr提升数倍。最后，我也就放弃了search(QueryResult qr, QueryCommand cmd)，而是使用lucene的，虽然不能使用上solr的三种cache，但性能还是令人满意的。</p>
<p><font color="red">updated：该文分析问题的角度上出现了偏差，<a href="http://www.kafka0102.com/2010/10/374.html">solr filter query的误用</a>做了一些补充和修正。</font></p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/10/366.html/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<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>[Solr源码分析]Solr复制类ReplicationHandler实现简要分析</title>
		<link>http://www.kafka0102.com/2010/07/249.html</link>
		<comments>http://www.kafka0102.com/2010/07/249.html#comments</comments>
		<pubDate>Sat, 24 Jul 2010 16:42:27 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[solr]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[源码分析]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=249</guid>
		<description><![CDATA[在上一文《solr ReplicationHandler使用介绍》的基础上，本文接着对solr的ReplicationHandler实现细节做些分析，这个分析原则上没有摘取大段代码，窃以为摘了代码后未见得有很好的阐述效果，但不摘取后窃又发现，阐述的效果依旧不好。归结起来，还是窃的表达不够深入浅出所致。闲言少叙，直接上内容。]]></description>
			<content:encoded><![CDATA[<p>在上一文《<a title="Permanent Link to solr ReplicationHandler使用介绍" rel="bookmark" href="http://www.kafka0102.com/2010/07/244.html">solr  ReplicationHandler使用介绍</a>》的基础上，本文接着对solr的ReplicationHandler实现细节做些分析，这个分析原则上没有摘取大段代码，窃以为摘了代码后未见得有很好的阐述效果，但不摘取后窃又发现，阐述的效果依旧不好。归结起来，还是窃的表达不够深入浅出所致。闲言少叙，直接上内容。</p>
<h2>1、master的工作</h2>
<p>对于ReplicationHandler的复制功能来说，核心的问题确定是在一个时间点要复制哪些文件，这就用上了lucene的IndexDeletionPolicy的特性。lucene在初始化时，会调用IndexDeletionPolicy.onInit(List&lt;? extends IndexCommit&gt; commits)方法；lucene在commit（触发的时机也可以是optimize、close，solr在commit时实际上就是close了indexwriter）时，会调用IndexDeletionPolicy.onCommit(List&lt;? extends IndexCommit&gt; commits)。IndexCommit对象中保存了该次提交关联的文件列表等信息，这使得solr中的复制过程中，slave可以从master得到文件列表后跟本地文件做比较，跳过不变的文件，下载新文件，并删除无用的文件。IndexDeletionPolicy的两个针对commits的函数，会对当前存在的commits列表做些处理，比如lucene默认的KeepOnlyLastCommitDeletionPolicy会只保留最新的IndexCommit，对那些过时的IndexCommit执行delete操作以将无用的文件删掉。solr中，SolrDeletionPolicy默认也是保留最新一个IndexCommit，但可以设置maxCommitAge、maxCommitsToKeep、maxOptimizedCommitsToKeep来保留更多的IndexCommit。但solr真正使用的IndexDeletionPolicy实现是IndexDeletionPolicyWrapper，它是SolrDeletionPolicy的wrap。在slave从master复制文件的过程中，要保证当前正在复制的IndexCommit点不能被删除，这就用到了IndexDeletionPolicyWrapper中的void setReserveDuration(Long indexVersion, long reserveTime)方法，该方法会在master向slave响应indexversion、filelist命令前、以及每向slave传送5M的索引文件内容时调用，而默认的reserveTime时间是10s，如果慢速网络传输5M数据需要10秒以上，就需要调整该值了。</p>
<p>ReplicationHandler复制文件没有采用rsync，而是使用http，它在读一个文件内容传输到slave时，默认是按照1M大小分段输出内容到slave（http chunked？），并且默认是对每段内容做了checksum，保证传输的内容的正确性。上面提到的setReserveDuration点，主要就是它在packetsWritten % 5 == 0次数后触发一次修改。</p>
<p>ReplicationHandler还可以备份索引文件。由于lucene的索引文件只是追加新文件而不会修改已有文件，所以只要针对一个IndexCommit点做备份，其过程还是很简单的。</p>
<h2>2、slave的工作</h2>
<p>slave启动时会创建SnapPuller对象，SnapPuller会启动一个线程定时的（pollInterval间隔）从master复制数据（fetchLatestIndex方法）。对于一次复制过程，slave和master交互处理细节如下：<br />
1、slave首先向master询问最新的索引版本号（indexversion命令），slave检查得到的latestVersion、latestGeneration有效后，和本地的IndexCommit的getVersion()、getGeneration()比较，如果不相等，则需要往下进行，否则等待下一次调度。</p>
<p>2、slave向master请求之前得到的indexversion下的文件列表（filelist命令，包括索引文件和可选的配置文件）。如果文件列表为空，则返回等待下一次调度。否则，就需要检查哪些文件需要被下载过来。这里做的判断有：1）如果本地的commit.getGeneration() &gt;= latestGeneration，说明本地索引文件被破坏（比如对slave不小心提交了修改索引的命令），需要完全将master的文件复制过来。2）逐个检查文件列表中的文件是否在本地存在，不存在就下载下来。</p>
<p>3、对于下载文件内容，对应命令是filecontent。下载的文件显然需要放到临时目录中，这个临时目录和已有的索引目录（默认名字index）在同一数据目录下，只是命名为index.&lt;时间戳&gt;。下载完毕后，copy数据有两种情况：1）如果是完全下载，则不需要将临时目录中的文件copy到已有目录中，而是修改数据目录中的index.properties，标识索引目录为新生成的临时目录，而旧索引目录并不会被删除，可以手工删掉，当然，通常是不应该出现slave的Generation大于master的异常情况。2）通常就是把临时索引目录的文件copy到旧索引目录，copy时要把segments_N放到最后copy，避免copy中途出现异常造成数据被毁。</p>
<p>4、当新索引和可选的配置文件copy完毕之后，slave会对solrcore的UpdateHandler做commit操作，这会close掉indexwriter并强制重启新的indexsearcher提供服务。同时，如果solrcore的UpdateHandler是DirectUpdateHandler2（不应该不是），会强制调用handler.forceOpenWriter()来删除旧的无用的索引文件，并调用replicationHandler.refreshCommitpoint()来更新slave的indexCommitPoint。</p>
<p>5、如果索引复制失败，slave会向数据目录下的replication.properties输出复制失败的信息。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/07/249.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[Solr实践]Solr复制类ReplicationHandler使用介绍</title>
		<link>http://www.kafka0102.com/2010/07/244.html</link>
		<comments>http://www.kafka0102.com/2010/07/244.html#comments</comments>
		<pubDate>Sat, 24 Jul 2010 14:30:51 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[solr]]></category>
		<category><![CDATA[lucene]]></category>
		<category><![CDATA[ReplicationHandler]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=244</guid>
		<description><![CDATA[solr1.4中引入ReplicationHandler代替外部脚本来复制索引数据，ReplicationHandler使得复制索引数据更自动化。对于使用者来说，只要简单的配置好，就可以一劳永逸的享受solr的复制功能了。下面介绍其使用相关内容。]]></description>
			<content:encoded><![CDATA[<p>	solr1.4中引入ReplicationHandler代替外部脚本来复制索引数据，ReplicationHandler使得复制索引数据更自动化。对于使用者来说，只要简单的配置好，就可以一劳永逸的享受solr的复制功能了。下面介绍其使用相关内容。</p>
<h2>1、配置</h2>
<p>	ReplicationHandler是个RequestHandler，如果需要使用它，也就是在solrconfig.xml中配置它，下面介绍ReplicationHandler的配置参数。</p>
<h3>1.1、Master</h3>
<p>	master的配置示例如下：</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;requestHandler</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;/replication&quot;</span> <span style="color: #000066;">class</span>=<span style="color: #ff0000;">&quot;solr.ReplicationHandler&quot;</span> <span style="color: #000000; font-weight: bold;">&gt;</span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;lst</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;master&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>
        <span style="color: #808080; font-style: italic;">&lt;!--Replicate on 'startup' and 'commit'. 'optimize' is also a valid value for replicateAfter. --&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;replicateAfter&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>startup<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;replicateAfter&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>commit<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
&nbsp;
        <span style="color: #808080; font-style: italic;">&lt;!--Create a backup after 'optimize'. Other values can be 'commit', 'startup'. It is possible to have multiple entries of this config string.  Note that this is just for backup, replication does not require this. --&gt;</span>
        <span style="color: #808080; font-style: italic;">&lt;!-- &lt;str name=&quot;backupAfter&quot;&gt;optimize&lt;/str&gt; --&gt;</span>
&nbsp;
        <span style="color: #808080; font-style: italic;">&lt;!--If configuration files need to be replicated give the names here, separated by comma --&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;confFiles&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>schema.xml,stopwords.txt,elevate.xml<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
       <span style="color: #808080; font-style: italic;">&lt;!--The default value of reservation is 10 secs.See the documentation below . Normally , you should not need to specify this --&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;commitReserveDuration&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>00:00:10<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/lst<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/requestHandler<span style="color: #000000; font-weight: bold;">&gt;</span></span></span></pre></div></div>

<p>	说明：<br />
	1)replicateAfter可取startup、commit、optimize，表示触发复制的时机。使用中，这三个值都可以配上。<br />
	2)backupAfter表示备份时机，如果需要备份，solr会在配置的时机自动生成备份。<br />
	3)confFiles表示在复制时需要复制到slave的文件列表。<br />
	4)commitReserveDuration默认是10秒，这个值通常你通常不需要修改，除非你的网络慢到传输5M数据需要10秒以上的时间。</p>
<h3>1.2、Slave</h3>
<p>	Slave的配置示例如下：</p>

<div class="wp_syntax"><div class="code"><pre class="xml" style="font-family:monospace;"><span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;requestHandler</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;/replication&quot;</span> <span style="color: #000066;">class</span>=<span style="color: #ff0000;">&quot;solr.ReplicationHandler&quot;</span> <span style="color: #000000; font-weight: bold;">&gt;</span></span>
    <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;lst</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;slave&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>
&nbsp;
        <span style="color: #808080; font-style: italic;">&lt;!--fully qualified url for the replication handler of master . It is possible to pass on this as a request param for the fetchindex command--&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;masterUrl&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>http://master_host:port/solr/corename/replication<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>  
&nbsp;
        <span style="color: #808080; font-style: italic;">&lt;!--Interval in which the slave should poll master .Format is HH:mm:ss . If this is absent slave does not poll automatically. </span>
<span style="color: #808080; font-style: italic;">         But a fetchindex can be triggered from the admin or the http API --&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;pollInterval&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>00:00:20<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>  
        <span style="color: #808080; font-style: italic;">&lt;!-- THE FOLLOWING PARAMETERS ARE USUALLY NOT REQUIRED--&gt;</span>
        <span style="color: #808080; font-style: italic;">&lt;!--to use compression while transferring the index files. The possible values are internal|external</span>
<span style="color: #808080; font-style: italic;">         if the value is 'external' make sure that your master Solr has the settings to honour the accept-encoding header.</span>
<span style="color: #808080; font-style: italic;">         see here for details http://wiki.apache.org/solr/SolrHttpCompression</span>
<span style="color: #808080; font-style: italic;">         If it is 'internal' everything will be taken care of automatically. </span>
<span style="color: #808080; font-style: italic;">         USE THIS ONLY IF YOUR BANDWIDTH IS LOW . THIS CAN ACTUALLY SLOWDOWN REPLICATION IN A LAN--&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;compression&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>internal<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #808080; font-style: italic;">&lt;!--The following values are used when the slave connects to the master to download the index files. </span>
<span style="color: #808080; font-style: italic;">         Default values implicitly set as 5000ms and 10000ms respectively. The user DOES NOT need to specify </span>
<span style="color: #808080; font-style: italic;">         these unless the bandwidth is extremely low or if there is an extremely high latency--&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;httpConnTimeout&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>5000<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;httpReadTimeout&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>10000<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
&nbsp;
        <span style="color: #808080; font-style: italic;">&lt;!-- If HTTP Basic authentication is enabled on the master, then the slave can be configured with the following --&gt;</span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;httpBasicAuthUser&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>username<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
        <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;str</span> <span style="color: #000066;">name</span>=<span style="color: #ff0000;">&quot;httpBasicAuthPassword&quot;</span><span style="color: #000000; font-weight: bold;">&gt;</span></span>password<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/str<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
     <span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/lst<span style="color: #000000; font-weight: bold;">&gt;</span></span></span>
<span style="color: #009900;"><span style="color: #000000; font-weight: bold;">&lt;/requestHandler<span style="color: #000000; font-weight: bold;">&gt;</span></span></span></pre></div></div>

<p>	说明：上面的参数也不需要太多解释，其中pollInterval参数表明slave从master复制数据的频率。如果对实时性要求不高，通常5-10分钟即可，也避免slave的indexsearcher频繁的切换，同时，master的commit频率也可相对保持一致。</p>
<h2>2、HTTP API</h2>
<p>	solr的ReplicationHandler提供了一系列http命令（参数command），支持的可选值如下：<br />
	1）indexversion：slave从master获取最新的索引点信息。<br />
	2）filecontent：slave从master下载指定文件的内容。<br />
	3）filelist：slave从master获取指定indexversion的索引文件列表（及需要复制的配置文件）。<br />
	4）backup：备份索引。如果担心索引有损坏的可能性，可以定期备份索引。<br />
	5）fetchindex：手动复制数据，和slave自动复制相当。<br />
	6）disablepoll：停止slave的复制。<br />
	7）enablepoll：开启slave的复制。<br />
	8）abortfetch：终止slave上正在进行的下载文件过程。<br />
	9）commits：show当前仍旧保留的IndexCommit信息。<br />
	10）details：show slave当前的复制细节信息。<br />
	11）enablereplication：启动master对所有slave的复制功能<br />
	12）disablereplication：关闭master对所有slave的复制功能</p>
<h2>4、性能</h2>
<p>solr的ReplicationHandler使用http的分段连续的下载索引文件数据，而代替经典的rsync，solr wiki上给出的性能测试对比图如下：<br />
<a href="http://www.kafka0102.com/wp-content/uploads/2010/07/transfer_time.png"><img src="http://www.kafka0102.com/wp-content/uploads/2010/07/transfer_time.png" alt="" title="transfer_time" width="800" height="600" class="aligncenter size-full wp-image-245" /></a><br />
<br/><br />
可以看到，性能方面差别不大，不必有太多的担心。</p>
<h2>4、参考文章</h2>
<p>http://wiki.apache.org/solr/SolrReplication</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/07/244.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>避免lucene queryparser中文分词的缺陷</title>
		<link>http://www.kafka0102.com/2010/07/226.html</link>
		<comments>http://www.kafka0102.com/2010/07/226.html#comments</comments>
		<pubDate>Sat, 10 Jul 2010 06:01:19 +0000</pubDate>
		<dc:creator>kafka0102</dc:creator>
				<category><![CDATA[lucene]]></category>
		<category><![CDATA[queryparser]]></category>

		<guid isPermaLink="false">http://www.kafka0102.com/?p=226</guid>
		<description><![CDATA[很多人在使用lucene时会使用其提供的queryparser分析query。不过，lucene的queryparser从一开始到现在都没有充分考虑中文等语言的特点，使得查询中文会出现让人不可理解的查不到结果的情况。这个bug就是LUCENE-2458 。]]></description>
			<content:encoded><![CDATA[<p>很多人在使用lucene时会使用其提供的queryparser分析query。不过，lucene的queryparser从一开始到现在都没有充分考虑中文等语言的特点，使得查询中文会出现让人不可理解的查不到结果的情况。这个bug就是<a href="https://issues.apache.org/jira/browse/LUCENE-2458" target="_blank">LUCENE-2458 </a>。</p>
<p>这个问题简单说来就是，对于一个连续的中文query，queryparser将Analyzer返回的Term序列构成了PhraseQuery（也有可能是MultiPhraseQuery），而PhraseQuery默认的匹配规则是要求Term序列在索引的文档中完全顺序匹配。这对于英文查询来说是可以接受的，因为queryparser在分析query时，首先通过AND、OR、NOT将query进行切分（这可以理解为queryparser 的第一层分析，这样切分后构成的TOP Query就是BooleanQuery），然后将切分后的subquery交由Analyzer分析（当然这要求是满足FieldQuery的情况，否则也可能是RangeQuery、WildcardQuery等），因为英文单词之间以空格分割，相当于OR查询，所以英文中的subquery就可以理解是个短语（比如由多个连字符连接的短语，或者是英文和数字接合的短语，在lucene查询语法中，显示的双引号之间的内容认为是短语）。但对中文来说，如果将subquery分析成PhraseQuery，就很成问题。比如subquery是”诺基亚N97“，如果构成PhraseQuery，则要求索引的文档中必须存在”诺基亚N97“，如果”诺基亚“和”N97“中间有其他词，就不算匹配。对于这个例子，是可以调整PhraseQuery的 slop参数来变相解决，但这种情况，使用AND BooleanQuery更合适，使用BooleanQuery在对文档打分上也要比PhraseQuery好很多。而对于query分词结果，也存在一些TermQuery之间是OR的情况，使用PhraseQuery显然也不合适。</p>
<p>如LUCENE-2458提到，这个bug会在3.1和4中被修复，修复方法是，只有显示通过双引号括起来的subquery才生成 PhraseQuery，否则可以派生子类来自定义处理。就目前使用来说，如果你使用IK做Analyzer，那么它提供的IKQueryParser是很好的替代方案，它构造的就是由AND和OR联合的BooleanQuery。但因为BooleanQuery没有考虑各个Term在文档中的位置关系，一味的根据词频计算得分，检索效果有时也不是很好。不知道大家是怎么处理的？我有想到去扩展它的Query和Scorer，不过看起来有些麻烦，暂且还没精力投入上去。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.kafka0102.com/2010/07/226.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

