Solr采用Lucene搜索库为核心,提供全文索引和搜索开源企业平台,提供REST的HTTP/XML和JSON的API,若是你是Solr新手,那么就和我一块儿来入门吧!本教程以solr4.8做为测试环境,jdk版本须要1.7及以上版本。 java
本文假设你对Java有初中级以上水平,所以再也不介绍Java相关环境的配置。下载解压缩solr,在example目录有start.jar文件,启动: python
1
|
java -jar start.jar
|
浏览器访问:http://localhost:8983/solr/,你看到的就是solr的管理界面 mysql
服务启动后,目前你看到的界面没有任何数据,你能够经过POSTing命令向Solr中添加(更新)文档,删除文档,在exampledocs目录包含一些示例文件,运行命令: git
1
|
java -jar post.jar solr.xml monitor.xml
|
上面的命令是向solr添加了两份文档,打开这两个文件看看里面是什么内容,solr.xml里面的内容是: github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<add>
<doc>
<field name="id">SOLR1000</field>
<field name="name">Solr, the Enterprise Search Server</field>
<field name="manu">Apache Software Foundation</field>
<field name="cat">software</field>
<field name="cat">search</field>
<field name="features">Advanced Full-Text Search Capabilities using Lucene</field>
<field name="features">OptimizedforHigh Volume Web Traffic</field>
<field name="features">Standards Based Open Interfaces - XML and HTTP</field>
<field name="features">Comprehensive HTML Administration Interfaces</field>
<field name="features">Scalability - Efficient Replication to other Solr Search Servers</field>
<field name="features">Flexible and Adaptable with XML configuration and Schema</field>
<field name="features">Good unicode support: héllo (hello with an accent over the e)</field>
<field name="price">0</field>
<field name="popularity">10</field>
<field name="inStock">true</field>
<field name="incubationdate_dt">2006-01-17T00:00:00.000Z</field>
</doc>
</add>
|
表示向索引中添加一个文档,文档就是用来搜索的数据源,如今就能够经过管理界面搜索关键字”solr”,具体步骤是:
web
点击页面下的Execute Query按钮后右侧就会显示查询结果,这个结果就是刚才导入进去的solr.xml的json格式的展现结果。solr支持丰富的查询语法,好比:如今想搜索字段name里面的关键字”Search”就能够用语法name:search,固然若是你搜索name:xxx就没有返回结果了,由于文档中没有这样的内容。 算法
导入数据到Solr的方式也是多种多样的: sql
若是同一份文档solr.xml重复导入会出现什么状况呢?实际上solr会根据文档的字段id来惟一标识文档,若是导入的文档的id已经存在solr中,那么这份文档就被最新导入的同id的文档自动替换。你能够本身尝试试验一下,观察替换先后管理界面的几个参数:Num Docs,Max Doc,Deleted Docs的变化。 mongodb
经过id删除指定的文档,或者经过一个查询来删除匹配的文档 数据库
1
2
|
java -Ddata=args -jar post.jar"<delete><id>SOLR1000</id></delete>"
java -Ddata=args -jar post.jar"<delete><query>name:DDR</query></delete>"
|
此时solr.xml文档从索引中删除了,再次搜”solr”时再也不返回结果。固然solr也有数据库中的事务,执行删除命令的时候事务自动提交了,文档就会当即从索引中删除。你也能够把commit设置为false,手动提交事务。
1
|
java -Ddata=args -Dcommit=false-jar post.jar"<delete><id>3007WFP</id></delete>"
|
执行完上面的命令时文档并无真正删除,仍是能够继续搜索相关结果,最后能够经过命令:
1
|
java -jar post.jar -
|
提交事务,文档就完全删除了。如今把刚刚删除的文件从新导入Solr中来,继续咱们的学习。
删除全部数据:
1
|
http://localhost:8983/solr/collection1/update?stream.body=<delete><query>*:*</query></delete>&commit=true
|
删除指定数据
1
|
http://localhost:8983/solr/collection1/update?stream.body=<delete><query>title:abc</query></delete>&commit=true
|
多条件删除
1
|
http://localhost:8983/solr/collection1/update?stream.body=<delete><query>title:abc AND name:zhang</query></delete>&commit=true
|
查询数据都是经过HTTP的GET请求获取的,搜索关键字用参数q指定,另外还能够指定不少可选的参数来控制信息的返回,例如:用fl指定返回的字段,好比f1=name,那么返回的数据就只包括name字段的内容
1
|
http://localhost:8983/solr/collection1/select?q=solr&fl=name&wt=json&indent=true
|
Solr提供排序的功能,经过参数sort来指定,它支持正序、倒序,或者多个字段排序
网页搜索中,为了突出搜索结果,可能会对匹配的关键字高亮出来,Solr提供了很好的支持,只要指定参数:
1
|
http://localhost:8983/solr/collection1/select?q=Search&wt=json&indent=true&hl=true&hl.fl=features
|
返回的内容中包含:
1
2
3
4
5
|
"highlighting":{
"SOLR1000":{
"features":["Advanced Full-Text <em>Search</em> Capabilities using Lucene"]
}
}
|
文本字段经过把文本分割成单词以及运用各类转换方法(如:小写转换、复数移除、词干提取)后被索引,schema.xml文件中定义了字段在索引中,这些字段将做用于其中.
默认状况下搜索”power-shot”是不能匹配”powershot”的,经过修改schema.xml文件(solr/example/solr/collection1/conf目录),把features和text字段替换成”text_en_splitting”类型,就能索引到了。
1
2
3
|
<field name="features"type="text_en_splitting"indexed="true"stored="true"multiValued="true"/>
...
<field name="text"type="text_en_splitting"indexed="true"stored="false"multiValued="true"/>
|
修改完后重启solr,而后从新导入文档
1
|
java -jar post.jar *.xml
|
如今就能够匹配了
场景:小时候咱们都使用过新华字典,妈妈叫你翻开第38页,找到“坑爹”所在的位置,此时你会怎么查呢?毫无疑问,你的眼睛会从38页的第一个字开始从头到尾地扫描,直到找到“坑爹”二字为止。这种搜索方法叫作顺序扫描法。对于少许的数据,使用顺序扫描是够用的。可是妈妈叫你查出坑爹的“坑”字在哪一页时,你要是从第一页的第一个字逐个的扫描下去,那你真的是被坑了。此时你就须要用到索引。索引记录了“坑”字在哪一页,你只需在索引中找到“坑”字,而后找到对应的页码,答案就出来了。由于在索引中查找“坑”字是很是快的,由于你知道它的偏旁,所以也就可迅速定位到这个字。
那么新华字典的目录(索引表)是怎么编写而成的呢?首先对于新华字典这本书来讲,除去目录后,这本书就是一堆没有结构的数据集。可是聪明的人类善于思考总结,发现每一个字都会对应到一个页码,好比“坑”字就在第38页,“爹”字在第90页。因而他们就从中提取这些信息,构形成一个有结构的数据。相似数据库中的表结构:
word page_no --------------- 坑 38 爹 90 ... ...
这样就造成了一个完整的目录(索引库),查找的时候就很是方便了。对于全文检索也是相似的原理,它能够归结为两个过程:1.索引建立(Indexing)2. 搜索索引(Search)。那么索引究竟是如何建立的呢?索引里面存放的又是什么东西呢?搜索的的时候又是如何去查找索引的呢?带着这一系列问题继续往下看。
Solr/Lucene采用的是一种反向索引,所谓反向索引:就是从关键字到文档的映射过程,保存这种映射这种信息的索引称为反向索引
字段串列表和文档编号链表二者构成了一个字典。如今想搜索”lucene”,那么索引直接告诉咱们,包含有”lucene”的文档有:2,3,10,35,92,而无需在整个文档库中逐个查找。若是是想搜既包含”lucene”又包含”solr”的文档,那么与之对应的两个倒排表去交集便可得到:三、十、3五、92。
假设有以下两个原始文档:
文档一:Students should be allowed to go out with their friends, but not allowed to drink beer.
文档二:My friend Jerry went to school to see his students but found them drunk which is not allowed.
建立过程大概分为以下步骤:
一:把原始文档交给分词组件(Tokenizer)
分词组件(Tokenizer)会作如下几件事情(这个过程称为:Tokenize),处理获得的结果是词汇单元(Token)
"Students","allowed","go","their","friends","allowed","drink","beer","My","friend","Jerry","went","school","see","his","students","found","them","drunk","allowed"
二:词汇单元(Token)传给语言处理组件(Linguistic Processor)
语言处理组件(linguistic processor)主要是对获得的词元(Token)作一些语言相关的处理。对于英语,语言处理组件(Linguistic Processor)通常作如下几点:
语言处理组件(linguistic processor)处理获得的结果称为词(Term),例子中通过语言处理后获得的词(Term)以下:
"student","allow","go","their","friend","allow","drink","beer","my","friend","jerry","go","school","see","his","student","find","them","drink","allow"。
通过语言处理后,搜索drive时drove也能被搜索出来。Stemming 和 lemmatization的异同:
三:获得的词(Term)传递给索引组件(Indexer)
Term Document ID student 1 allow 1 go 1 their 1 friend 1 allow 1 drink 1 beer 1 my 2 friend 2 jerry 2 go 2 school 2 see 2 his 2 student 2 find 2 them 2 drink 2 allow 2
Term Document ID allow 1 allow 1 allow 2 beer 1 drink 1 drink 2 find 2 friend 1 friend 2 go 1 go 2 his 2 jerry 2 my 2 school 2 see 2 student 1 student 2 their 1 them 2
对词(Term) “allow”来说,总共有两篇文档包含此词(Term),词(Term)后面的文档链表总共有两个,第一个表示包含”allow”的第一篇文档,即1号文档,此文档中,”allow”出现了2次,第二个表示包含”allow”的第二个文档,是2号文档,此文档中,”allow”出现了1次
至此索引建立完成,搜索”drive”时,”driving”,”drove”,”driven”也可以被搜到。由于在索引中,”driving”,”drove”,”driven”都会通过语言处理而变成”drive”,在搜索时,若是您输入”driving”,输入的查询语句一样通过分词组件和语言处理组件处理的步骤,变为查询”drive”,从而能够搜索到想要的文档。
搜索”microsoft job”,用户的目的是但愿在微软找一份工做,若是搜出来的结果是:”Microsoft does a good job at software industry…”,这就与用户的指望偏离太远了。如何进行合理有效的搜索,搜索出用户最想要得结果呢?搜索主要有以下步骤:
一:对查询内容进行词法分析、语法分析、语言处理
二:搜索索引,获得符合语法树的文档集合
三:根据查询语句与文档的相关性,对结果进行排序
咱们把查询语句也看做是一个文档,对文档与文档之间的相关性(relevance)进行打分(scoring),分数高比较越相关,排名就越靠前。固然还能够人工影响打分,好比百度搜索,就不必定彻底按照相关性来排名的。
如何评判文档之间的相关性?一个文档由多个(或者一个)词(Term)组成,好比:”solr”, “toturial”,不一样的词可能重要性不同,好比solr就比toturial重要,若是一个文档出现了10次toturial,但只出现了一次solr,而另外一文档solr出现了4次,toturial出现一次,那么后者颇有可能就是咱们想要的搜的结果。这就引伸出权重(Term weight)的概念。
权重表示该词在文档中的重要程度,越重要的词固然权重越高,所以在计算文档相关性时影响力就更大。经过词之间的权重获得文档相关性的过程叫作空间向量模型算法(Vector Space Model)
影响一个词在文档中的重要性主要有两个方面:
文档中词的权重看做一个向量
Document = {term1, term2, …… ,term N} Document Vector = {weight1, weight2, …… ,weight N}
把欲要查询的语句看做一个简单的文档,也用向量表示:
Query = {term1, term 2, …… , term N} Query Vector = {weight1, weight2, …… , weight N}
把搜索出的文档向量及查询向量放入N维度的空间中,每一个词表示一维:
夹角越小,表示越类似,相关性越大
Document是Solr索引(动词,indexing)和搜索的最基本单元,它相似于关系数据库表中的一条记录,能够包含一个或多个字段(Field),每一个字段包含一个name和文本值。字段在被索引的同时能够存储在索引中,搜索时就能返回该字段的值,一般文档都应该包含一个能惟一表示该文档的id字段。例如:
1
2
3
4
5
6
7
8
|
<doc>
<field name="id">company123</field>
<field name="companycity">Atlanta</field>
<field name="companystate">Georgia</field>
<field name="companyname">Code Monkeys R Us, LLC</field>
<field name="companydescription">we write lots of code</field>
<field name="lastmodified">2013-06-01T15:26:37Z</field>
</doc>
|
Solr中的Schema相似于关系数据库中的表结构,它以schema.xml的文本形式存在在conf目录下,在添加文当到索引中时须要指定Schema,Schema文件主要包含三部分:字段(Field)、字段类型(FieldType)、惟一键(uniqueKey)
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<schema name="example"version="1.5">
<field name="id"type="string"indexed="true"stored="true"required="true"multiValued="false"/>
<field name="title"type="text_general"indexed="true"stored="true"multiValued="true"/>
<uniqueKey>id</uniqueKey>
<fieldType name="string"class="solr.StrField"sortMissingLast="true"/>
<fieldType name="text_general"class="solr.TextField"positionIncrementGap="100">
<analyzer type="index">
<tokenizerclass="solr.StandardTokenizerFactory"/>
<filterclass="solr.StopFilterFactory"ignoreCase="true"words="stopwords.txt"/>
<!-- inthisexample, we will only use synonyms at query time
<filterclass="solr.SynonymFilterFactory"synonyms="index_synonyms.txt"ignoreCase="true"expand="false"/>
-->
<filterclass="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizerclass="solr.StandardTokenizerFactory"/>
<filterclass="solr.StopFilterFactory"ignoreCase="true"words="stopwords.txt"/>
<filterclass="solr.SynonymFilterFactory"synonyms="synonyms.txt"ignoreCase="true"expand="true"/>
<filterclass="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
</schema>
|
在Solr中,字段(Field)是构成Document的基本单元。对应于数据库表中的某一列。字段是包括了名称,类型以及对字段对应的值如何处理的一种元数据。好比:
<field name="name" type="text_general" indexed="true" stored="true"/>
Solr中每一个字段都有一个对应的字段类型,好比:float、long、double、date、text,Solr提供了丰富字段类型,同时,咱们还能够自定义适合本身的数据类型,例如:
1
2
3
4
5
6
7
8
9
10
|
<!-- Ik 分词器 -->
<fieldType name="text_cn_stopword"class="solr.TextField">
<analyzer type="index">
<tokenizerclass="org.wltea.analyzer.lucene.IKAnalyzerSolrFactory"useSmart="false"/>
</analyzer>
<analyzer type="query">
<tokenizerclass="org.wltea.analyzer.lucene.IKAnalyzerSolrFactory"useSmart="true"/>
</analyzer>
</fieldType>
<!-- Ik 分词器 -->
|
若是把Schema定义为Solr的Model的话,那么Solrconfig就是Solr的Configuration,它定义Solr若是处理索引、高亮、搜索等不少请求,同时还指定了缓存策略,用的比较多的元素包括:
1
2
3
4
5
6
|
<!--
Used to specify an alternate directory to hold all index data
other than thedefault./data under the Solr home.
If replication is in use,thisshould match the replication configuration.
-->
<dataDir>${solr.data.dir:./solr/data}</dataDir>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<filterCache
class="solr.FastLRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- queryResultCache caches results of searches - ordered lists of
document ids (DocList) based on a query, a sort, and the range
of documents requested. -->
<queryResultCache
class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- documentCache caches Lucene Document objects (the stored fieldsforeach document).
Since Lucene internal document ids aretransient,thiscache will not be autowarmed. -->
<documentCache
class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
|
1
2
3
4
5
6
7
8
9
|
<!-- A request handler that returns indented JSON bydefault-->
<requestHandler name="/query"class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="wt">json</str>
<str name="indent">true</str>
<str name="df">text</str>
</lst>
</requestHandler>
|
每一个请求处理器包括一系列可配置的搜索参数,例如:wt,indent,df等等。
1
2
3
4
5
6
|
<requestHandlername="/dataimport"
class="org.apache.solr.handler.dataimport.DataImportHandler">
<lstname="defaults">
<strname="config">data-config.xml</str>
</lst>
</requestHandler>
|
1
|
<lib dir="../../../dist/" regex="solr-dataimporthandler-\d.*\.jar"/>
|
1
|
<lib dir="../../../dist/" regex="solr-cell-\d.*\.jar" />
|
<?xmlversion="1.0"encoding="UTF-8"?>
<dataConfig>
<dataSourcetype="JdbcDataSource"
driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/django_blog"
user="root"
password=""/>
<documentname="blog">
<entityname="blog_blog"pk="id"
query="select id,title,content from blog_blog"
deltaImportQuery="select id,title,content from blog_blog where ID='${dataimporter.delta.id}'"
deltaQuery="select id from blog_blog where add_time > '${dataimporter.last_index_time}'"
deletedPkQuery="select id from blog_blog where id=0">
<fieldcolumn="id"name="id"/>
<fieldcolumn="title"name="title"/>
<fieldcolumn="content"name="content"/>
</entity>
</document>
</dataConfig>
|
<!-- mysql -->
<fieldname="id"type="string"indexed="true"stored="true"required="true"/>
<fieldname="title"type="text_cn"indexed="true"stored="true"termVectors="true"termPositions="true"termOffsets="true"/>
<fieldname="content"type="text_cn"indexed="true"stored="true"termVectors="true"termPositions="true"termOffsets="true"/>
<!-- mysql -->
|
参考:
<code>git clone https://github.com/10gen-labs/mongo-connector.git cd mongo-connector #安装前修改mongo_connector/constants.py的变量:设置DEFAULT_COMMIT_INTERVAL = 0 python setup.py install </code>
默认是不会自动提交了,这里设置成自动提交,不然mongodb数据库更新,索引这边无法同时更新,或者在命令行中能够指定是否自动提交,不过我如今还没发现。
<?xmlversion="1.0"encoding="UTF-8"?>
<schemaname="example"version="1.5">
<fieldname="_version_"type="long"indexed="true"stored="true"/>
<fieldname="_id"type="string"indexed="true"stored="true"required="true"multiValued="false"/>
<fieldname="body"type="string"indexed="true"stored="true"/>
<fieldname="title"type="string"indexed="true"stored="true"multiValued="true"/>
<fieldname="text"type="text_general"indexed="true"stored="false"multiValued="true"/>
<uniqueKey>_id</uniqueKey>
<defaultSearchField>title</defaultSearchField>
<solrQueryParserdefaultOperator="OR"/>
<fieldTypename="string"class="solr.StrField"sortMissingLast="true"/>
<fieldTypename="long"class="solr.TrieLongField"precisionStep="0"positionIncrementGap="0"/>
<fieldTypename="text_general"class="solr.TextField"positionIncrementGap="100">
<analyzertype="index">
<tokenizerclass="solr.StandardTokenizerFactory"/>
<filterclass="solr.StopFilterFactory"ignoreCase="true"words="stopwords.txt"/>
<filterclass="solr.LowerCaseFilterFactory"/>
</analyzer>
<analyzertype="query">
<tokenizerclass="solr.StandardTokenizerFactory"/>
<filterclass="solr.StopFilterFactory"ignoreCase="true"words="stopwords.txt"/>
<filterclass="solr.SynonymFilterFactory"synonyms="synonyms.txt"ignoreCase="true"expand="true"/>
<filterclass="solr.LowerCaseFilterFactory"/>
</analyzer>
</fieldType>
</schema>
|
<code>mongod --replSet myDevReplSet --smallfiles </code>
初始化:rs.initiate()
<code>E:\Users\liuzhijun\workspace\mongo-connector\mongo_connector\doc_managers>mongo-connector -m localhost:27017 -t http://localhost:8983/solr/collection2 -n s_soccer.person -u id -d ./solr_doc_manager.py </code>
注意:mongodb一般使用_id做为uniquekey,而Solrmore使用id做为uniquekey,若是不作处理,索引文件时将会失败,有两种方式来处理这个问题:
<code><uniqueKey>id<uniqueKey> </code>
替换成
<code><uniqueKey>_id</uniqueKey> </code>
同时还要定义一个_id的字段:
<code><field name="_id" type="string" indexed="true" stored="true" /> </code>
<code>2014-06-18 12:30:36,648 - ERROR - OplogThread: Last entry no longer in oplog cannot recover! Collection(Database(MongoClient('localhost', 27017), u'local'), u'oplog.rs') </code>
清空E:\Users\liuzhijun\workspace\mongo-connector\mongo_connector\doc_managers\config.txt中的内容,须要删除索引目录下的文件从新启动