Index Data
Author: David Smiley Eric Pugh
译者:Koala++ / 屈伟
在这一章中我们将了解如何将数据传入Solr。这个传入的过程称之为索引,尽管中间还包含了导入的过程。本章的结构如下:
l 与Solr交互。
l 以Solr的Update-XML格式发送数据。
l 提交,优化,回滚和删除。
l 以CSV 格式发送数据。
l 通过Solr的DataImportHandler直接读数据库和XML。
l 通过Solr的ExtractingRequestHandler从富文档中抽取数据。
l 用UpdateRequestProcessors进行文档后处理(post-processing)。
你会在第九章看到一些相关的内容,第九章中有语言绑定,框架集成,包括爬虫。大都用Solr的Update-XML格式。
Solr提供了很多导入数据的方式。在本节中,我们将先介绍一些方法,给出一些交互的例子。一些特定格式,比如Solr的Update-XML的细节随后介绍。
应用与Solr通过HTTP方式交互,你可以选择直接用你喜欢的HTTP客户端API,也可以使用与Solr集成的API,比如SolrJ或是Sunspot,它们将处理与HTTP交互的细节。这些API将在第九章介绍。HTTP Solr交互并不意味着需要索引的数据一定要通过HTTP传输,你马上会学习到如何告诉Solr去取数据。
尽管一个应用通过HTTP方式与Solr通信,并不意味着它需要将文档通过HTTP发送给Solr。Solr支持一种它称为remote streaming的方式,这种方式需要提供给它一个URL,它可以是一个HTTP URL,但一般它是一个基于文件系统的URL,基于文件系统的URL,可以在数据已经在Solr所在的本机或是在网络驱动中时可以使用。这种方式减少了HTTP方式的代价。另一种方式是让Solr通过DataImportHandler去拉取数据,这种方式可以从数据库和其它来源拉取数据。DIH提供了一个可扩展的框架,它可以扩展以适应自定义的数据源。
下面是多种在Solr用来建索引的格式:
l Solr的Update-XML:Solr接受一种通过XML格式表达的Solr特定的格式。它也有删除,优化和提交的命令。
? 其它XML:任意的XML带上一个XSLT文件给Solr,Solr会将XML转化成Update-XML格式以进行后面的处理。
? Solr的Update-JSON:Solr的Update-XML的一个JavaScript Object Notation变形。更多细节见http://wiki.apache.org/solr/UpdateJSON。
? Java-Bin:Solr的Update-XML的一个高效的二进制变形。正式地只有SolrJ客户端API支持,但也有第三方的Ruby支持。
? CSV:逗号(或其它符号)分隔符的格式。
? 富文档:大多数常见的文件格式,比如PDF,XLS,DOC,PPT。文本和元数据都可以从这些格式中抽取出来,并放入Solr的域中。这可以通过Solr Cell Contrib模式完成。
我们将通过把MusicBrainz的数据以XML,CSV和数据库的方式导入Solr来展示Solr的能力。其它的例子将展示通过DIH将爬取的文件导入,和通过Solr Cell导入。但是通常来说一个应用只会用一种格式来导入。
在我们介绍这些方法之前,我们先介绍一下cURL和remote streaming,这两个是基本知识。
Solr通过HTTP POST接收命令,还可以接收文档数据。
发送HTTP POST的方法之一是使用UNIX命令行工具curl,我们将用它来介绍例子。另一个跨平台的工具是Solr中post.jar,它在Solr的example/exampledocs目录下。要得到一些使用信息,用下面的命令运行:
>> java –jar example/exampledocs/post.jar -help
有几种让Solr索引数据的方式,并所有的方式都是通过HTTP POST:
l 通过POST方式发送数据。curl的--data-binary参数可以做到这点,并会带一个与格式相符的content-type头。
l 发送一些类似一个HTML格式的键值对。Curl使用-F来进行。如果你不是在数据库中得到数据,你可以用下面的方式来进行:
? 将数据放在stream.body参数中。如果它比较小,也许小于1M,这种方式没有问题。大小的限制是在solrconfig.xml的multipartUpdateLimitInKB中,默认是2GB。如果你想提高限制,你应该再考虑一下你的方式。
? 用stream.file参数引用Solr服务器上的一个本地文件,或是通过stream.url参数通过一个URL去取数据。这些方式Solr称之为remote streaming。
下面是第一种选择的例子。我们假设有一个artists.xml在当前目录。我们可以用下面的命令Post这个文件。
>> curl http://localhost:8983/solr/mbartists/update -H 'Contenttype:text/xml; charset=utf-8' --data-binary @artists.xml
如果它成功了,你会得到下面的输出:
要用stream.body来完成上例,你可以写:
curl http://localhost:8983/solr/mbartists/update -F [email protected]
在两个例子中,@符号指示curl从文件中取得数据。如果XML比较短,你可以直接在命令行中写:
curl http://localhost:8983/solr/mbartists/update -F stream.body='
注意在值中有一个空格,这是有意为之的。在本例中,curl对待@和<有特殊含义。在本例中应该用form-string而不是-F。但是我懒得打字了。
在前面的例子中,我们通过HTTP方式将数据发给Solr建索引。另外,我们可以通过POST给Solr一个数据的位置让它去取数据,数据的位置可以是文件路径也可以是HTTP的URL。
像前面一样,如果Solr没有处理完请求,那么是不会返回响应的。如果文件大小合适或是它已经在某一已知的URL中了,那么你会发现remote streaming更快并且/或者更方便。
下面是一个Solr访问一个本地文件的例子:
curl http://localhost:8983/solr/mbartists/update -F stream.file=/tmp/artists.xml
如果要使用URL,那么参数就改为stream.url,并且将值指定为一个URL。我们现在传递的键值对参数,而不是真正的数据。
你可以通过使用一个XML格式化的方式,来提供建索引的文档,告诉Solr提交改变,来优化索引,删除文档。下面是一个示例XML文件,你可以通过HTTP POST给Solr增加(或替换)两个文档:
其中overwirte属性默认为true保证你在schema中指定为unique的域的值唯一,如果你添加的另一个文档在unique的域中有相同的值,那么这个文档会替换前一个文档。你不会得到一个错误。
其中boost值会影响匹配文档时的得分。在文档或是域级别可选提供一个boost值。默认值是1.0,即无boost。技术上讲,不应该对文档进行boost,只应该对域进行boost。域最终的boost值是文档的boost值乘以域的boost值。
你可以通过unique域删除一个文档。下面的例子是我们删除两个文档:
为更灵活地删除文档,你可以用Lucene/Solr查询删除文档:
内容中的delete标签可以有多个你想删除的id和query标签,这样一次可以批量删除多个文档。
查询语法会在第四章讨论。我简单解释上面的例子,我们假设我们的文档中有一个时间戳域,它是被索引的,并且你会每天进行一次数据全量重建。在一次全量数据更新后,就要删除以前的老数据。上面的查询会删除所有不在12小时以前建立索引的文档。12小时是随意选择一个值,但它需要小于24个小时并且大于加载所有数据的耗时。
发送给Solr的数据不能立即搜索到,删除的文档也不会立即失效。像数据库一样,改动需要先提交(commit)。最简单的方式是在Solr的更新URL后加上commit=true请求参数。这个请求可以是包含更新数据的请求也可以是一个空的请求。比如,你可以通过访问URL产生一个提交到我们的mbreleases索引:http://localhost:8983/solr/mbreleases/update?commit=true。你也可以通过下面的XML语法提交,你只需要将它发送给Solr:
你需要知道关于Solr提交的三个重要的点:
l 提交是缓慢的。速度依赖于索引的大小,Solr的auto-warming配置,和Solr的Cache状态的提交,一次提交会花费一些时间。通常,它需要几秒钟,但在极端情况下,它会花费几分钟。要了解如何减少提交时间,可以参考第十章。
l 没有事务隔离:这意味着如果多个Solr客户端提交修改,并且提交的时间重叠,那么就可能一个客户端的在发出提交命令之前,一部分修改已经提交了。这种情况也适用于回滚(rollback)。如果你的应用中存在这个问题,你应该考虑只使用一个客户端处理Solr的更新。
l 同时提交是可以避免的,特别是多个客户端的情况。这个问题其实属于同时query warming,query warming是影响提交时间的主要因素。如果有太多同时进行的warming Solr会使用大量的资源,甚至会产生一个错误,但是提交最后还是会正常提交。
如果你批量载入数据,在最后进行一次提交,这次提交你倒不用担心。但如果Solr由多个独立的客户端异步更新数据,提交可能很频繁也可能重复。为了解决这个问题,Solr有两个相似的特性,autoCommit和commitWithin。autoCommit是solrconfig.xml中一小段注释掉的配置,配置后Solr会在达到文档数阈值或是时间阈值(最老未提交文档的时间)后自动提交。这样,你的应用不用再发送提交,Solr会自己来处理提交。commitWithin是一个类似的时间阈值选项。这个选项可以由客户端提交的更新信息设置,信息是放到XML更新数据的
Lucene的索引内部是由一个或是多个Segments组成的。当索引文档的缓冲区写入磁盘时,它会创建一个新的Segment。删除信息是在另一个文件中,但它们也要写入文件。有时,当一个新Segment写入时,Lucene会将多个Segment合并。当Lucene只有一个Segment时,它处在已优化(optimized)状态。Segment个数越多,则查询的效率就越低下。当然,优化一个索引是需要代价的,你的索引越大,那么优化花费的时间就越长。最后优化命令的语法与提交是相同的。如果你想在URL中使用,你可以用http://localhost:8983/solr/mbreleases/update?optimize=true。对于XML格式,可以发送:
建议在比如批量载入数据时,并且/或是如果有零星的更新时,可以在一天内比较空闲的时间显式地进行索引优化。后面章节会介绍如果优化时间过长的情况下,对多个索引进行优化。
提交和优化都有两个布尔选项,它们默认设置为true:
如果你把它们设置为false,那么提交和优化命令会立即返回,即使操作并没有真正完成。所以如果你写一个脚本进行提交,并将上面两个选项设置为false,再进行查询。你会发现查询结果并没有反应出改变。通过等待数据入写磁盘(waitFlush)和等待新的索引可以反应数据改变(waitSearcher),则可以避免上述情况。
最后还有一个索引命令回滚(rollback)。它可以将未提交的改变回滚。Solr的回滚命令可以通过URL参数:http://localhost:8983/solr/mbreleases/update?rollback=true或是XML:
如果你已经有一个CSV格式的数据或是对你来说得到CSV文件比XML或是JSON格式要容易,那么你可以选择CSV方式导入数据。Solr的CSV支持比较灵活。但你不能指定一个索引时的boost,但是它也不常用。
要得到MusicBrainz的Track数据,可以从一个本地的PostgreSQL数据中用下面命令导出数据:
psql -U postgres -d musicbrainz_db -c "COPY (\
select 'Track:' || t.id as id, 'Track' as type, t.name as t_name,
t.length/1000 as t_duration, a.id as t_a_id, a.name as t_a_name,
albumjoin.sequence as t_num, r.id as t_r_id, r.name as t_r_name, array_
to_string(r.attributes,' ') as t_r_attributes, albummeta.tracks as t_r_
tracks \
from (track t inner join albumjoin on t.id = albumjoin.track \
inner join album r on albumjoin.album = r.id left join albummeta on
albumjoin.album = albummeta.id) inner join artist a on t.artist = a.id \
) to '/tmp/mb_tracks.csv' CSV HEADER"
它大约会产生7百万行数据像下面一样的数据(前三行):
id,type,t_name,t_duration,t_a_id,t_a_name,t_num,t_r_id,t_r_name,t_r_
attributes,t_r_tracks
Track:183326,Track,In the Arms of Sleep,254,11650,The Smashing
Pumpkins,4,22471,Mellon Collie and the Infinite Sadness (disc 2: Twilight
to Starlight),0 1 100,14
Track:183328,Track,Tales of a Scorched Earth,228,11650,The Smashing
Pumpkins,6,22471,Mellon Collie and the Infinite Sadness (disc 2: Twilight
to Starlight),0 1 100,14
…
代码和CSV文件都在本书提供的补充资料中。要将CSV文件导入Solr,运行下面的命令:
curl http://localhost:8983/solr/update/csv -F f.t_r_attributes.split=true
-F f.t_r_attributes.separator=' ' -F overwrite=false -F commit=true -F
stream.file=/tmp/mb_tracks.csv
CSV选项通过-F来指定。
下面是对每个配置选项参数的解释。对于前面的MusicBrainz CSV文件例子,命令中只设置了多值域的分隔符t_r_attributes,并为了效率而禁用了唯一键(unique key)处理,其它的都采用默认值。
l separator:用于分隔域的分隔符。默认为逗号。
l header:如果设置为true,则文件的第一行是域名。
l fieldnames:如果第一行没有包含域名,那么你需要使用它来指定域名。用逗号分隔它们。如果某一列没有指定域名,这一列的值会被忽略。
l skip:指定不用导入的域。
l skipLines,指定要忽略输入文件中多少行。默认为0.
l trim:如果为true,则在最后一步移除域值开始和结尾的空格,即使是那些被引号引起来的空格。默认为false。Solr已经进行了初步的去空白字符了,但引号引起的空格不会被去除。
l encapsulator:这个符号是用于将一个域的值引起来,因为一个域中的值可能包括域分隔符,引起来后解析就不会错误地将域值解析成两个域值。它默认是双引号,除非它被转义了,比如:
11604, foo, "The ""second"" word is quoted.", bar
l escapse:如果输入文本中有这个字符,那么下一个字符就会被转义字符本身,即它不会被转义的字符不会被认为是特殊字符,比如:
11604, foo, The second\, word is followed by a comma., bar
l keepEmpty:指定是否空(0长度)域值是否应该被索引或是忽略。默认为false。
l overwirte:它是指是否有相同ID的文档是否应该覆盖另一个文档,ID是由Schema中指定的唯一键。它默认为true。如果你对确定你没有重复的ID,可以设置为false可以提高效率。
l split:它用于有多值的域的切分。指定多值间的分隔符。
l map:它可以将域值替换为另一个值,也可以移除某些域值。替换前和替换后的值用冒号分隔,你可以在MusicBrainz Track数据上用这一特性,你可将数值替换为一些更有意义的值。下面是一个例子:
-F keepEmpty=false -F f.t_r_attributes.map=0:
-F f.t_r_attributes.map=1:Album -F f.t_r_attributes.map=2:Single
这会使0被移除,因为它是无用的数据,几乎所有的Track都有这个值。我们将1映射为Album,2映射为Single。