1.Search Engine
- 目前主流的開(kāi)源搜索引擎主要有兩個(gè)沪摄,一個(gè)是基于Java的Apache Lucene躯嫉,另一個(gè)是基于C++的Sphinx。在建立索引所需時(shí)間方面杨拐,Sphinx只需Lucene時(shí)間的50%左右祈餐,但是索引文件Sphinx比Lucene要大一倍,即Sphinx采用的是空間換時(shí)間的策略哄陶。在全文檢索速度方面帆阳,二者相差不大。全文檢索精確度方面屋吨,Lucene要優(yōu)于Sphinx蜒谤。在加入中文分詞引擎的難易程度上,Lucene要優(yōu)于Sphinx至扰。Lucene的API接口設(shè)計(jì)的比較通用鳍徽,輸入輸出結(jié)構(gòu)都很像數(shù)據(jù)庫(kù)的表、記錄敢课、字段阶祭,所以很多傳統(tǒng)的應(yīng)用的文件绷杜、數(shù)據(jù)庫(kù)等都可以比較方便的映射到Lucene的存儲(chǔ)結(jié)構(gòu)/接口中。因此濒募,選擇Lucene作為全文搜索引擎是比較好的選擇接剩。
2.Search based on Lucene
- Lucene只是一個(gè)框架,要充分利用它的功能萨咳,需要很高學(xué)習(xí)成本,很少直接直接使用Lucene疫稿,Solr和ElasticSearch都是基于Lucene的搜索服務(wù)器培他。ElasticSearch相比于solr擁有一些重要特征:
- 1.導(dǎo)入性能更好,查詢性能與solr持平遗座,因?yàn)閟olr在建索引時(shí)會(huì)產(chǎn)生io的阻塞舀凛,造成搜索性能的下降,但ElasticSearch不會(huì)途蒋,它是先把索引的內(nèi)容保存到內(nèi)存之中猛遍,當(dāng)內(nèi)存不夠時(shí)再把索引持久化到硬盤(pán)中,同時(shí)它還有一個(gè)隊(duì)列号坡,是在系統(tǒng)空閑時(shí)自動(dòng)把索引寫(xiě)到硬盤(pán)中
- 2.ElasticSearch 完全支持Apache Lucene的接近實(shí)時(shí)的搜索懊烤。
- 3.支持更多的客戶端庫(kù),如Java宽堆,Python腌紧,Javascript
- 4.自包含集群,無(wú)需zookeeper即可自動(dòng)構(gòu)建集群
- 5.支持多種插件:例如ElasticSearch-head插件畜隶、ik分詞插件
- 6.支持多個(gè)存儲(chǔ)方式壁肋,ElasticSearch的數(shù)據(jù)文件可以存儲(chǔ)在本地文件系統(tǒng)、Hadoop的HDFS和亞馬孫的S3
3.ES Vs Relational DB
- Relational DB--->庫(kù)(Databases)--->表(Tables)--->行(Rows)--->列(columns)
- ES--->索引(Indices)--->類型(Types)--->文檔(Documents)--->字段(Fields)
4.ElasticSearch Install
- 版本說(shuō)明
Java環(huán)境:JDK 1.8.0
Elasticsearch:2.3.4
OS環(huán)境:windows 7(為了開(kāi)發(fā)調(diào)試方便) - 下載解壓安裝包
下載地址:https://www.elastic.co/downloads/elasticsearch
下載合適版本的安裝包籽慢,當(dāng)前最新的版本是5.0.1浸遗,Elasticsearch版本更新太快,直接從2.4.0到了5.0箱亿,由于5.x版本安裝Elasticsearch-head插件過(guò)于復(fù)雜跛锌,選用的2.x版本即可
解壓后的目錄如下
├─bin
├─config
├─lib
└─modules
├─lang-expression
├─lang-groovy
└─reindex
單機(jī)Elasticsearch不需要修改過(guò)多的配置,就能直接使用届惋,如果需要修改察净,修改config目錄下Elasticsearch.yml文件即可,例如數(shù)據(jù)文件目錄盼樟、日志文件目錄氢卡、Elasticsearch服務(wù)器的ip和端口號(hào)以及JVM堆內(nèi)存大小
- 發(fā)布成服務(wù)
講ElasticSearch發(fā)布為服務(wù),方便在后臺(tái)運(yùn)行和開(kāi)機(jī)啟動(dòng)
在elasticsearch-2.3.4\bin下執(zhí)行
service.bat install ElasticSearch - 啟動(dòng)后檢查
啟動(dòng)ElasticSearch服務(wù)后晨缴,訪問(wèn)http://localhost:9200/译秦,提示服務(wù)是否就緒
{
"name": "D'Ken",
"cluster_name": "elasticsearch",
"version": {
"number": "2.3.4",
"build_hash": "e455fd0c13dceca8dbbdbb1665d068ae55dabe3f",
"build_timestamp": "2016-06-30T11:24:31Z",
"build_snapshot": false,
"lucene_version": "5.5.0"
},
"tagline": "You Know, for Search"
}
安裝ElasticSearch-head
elasticsearch-head是集群管理工具、數(shù)據(jù)可視化、增刪改查工具
在bin目錄執(zhí)行
plugin install mobz/elasticsearch-head
訪問(wèn)http://localhost:9200/_plugin/head/安裝ik分詞插件
方式1:按照https://github.com/medcl/elasticsearch-analysis-ik 指導(dǎo)文檔安裝
方式2:直接使用ik.zip包
5.Basic Operator of ElasticSearch
- 新建文檔
curl -XPUT http://localhost:9200/blog/article/1 -d '{"titile": "New version of ElasticSearch released!", "content":"Version 1.0 released today!", "tags" : ["announce", "elasticsearch"," release"]}'
http://localhost:9200/blog/article/1筑悴,blog是index们拙,article是indextype,1代表文檔的id阁吝,在同一個(gè)index砚婆、同一個(gè)indextype下是唯一的。也可以不指定文檔id突勇,通過(guò)post方法新建文檔装盯,生成的文檔id是隨機(jī)的。如下所示:
curl -XPOST http://localhost:9200/blog/article -d '{"titile": "New version of ElasticSearch released!", "content":"Version 1.0 released today!", "tags" : ["announce", "elasticsearch","release"]}'
- 檢索文檔
已知文檔id條件甲馋,檢索文檔
curl -XGET http://localhost:9200/blog/article/1
做全文搜索時(shí)埂奈,當(dāng)type中的字段比較多,但是我們只需要某些字段時(shí)定躏,可以選擇結(jié)果集要返回的字段
curl -XGET http://localhost:9200/blog/article/_search –d
‘{
"fields": [
"title",
"author"
],
"query": {
"match_all": {}
}
}’
- 更新文檔
更新文檔在ElasticSearch屬于比較消耗的操作账磺,需要將之前的建立的索引刪掉,重建新的索引痊远,ElasticSearch局部更新操作是通過(guò)Groovy腳步實(shí)現(xiàn)更新的垮抗,在更新之前,需要在elasticsearch.yml中添加
script.inline: on
script.indexed: on
script.engine.groovy.inline.aggs: on
重啟ElasticSearch
例如碧聪,需要根據(jù)用戶的點(diǎn)擊次數(shù)借宵,更新某篇文章的瀏覽量
POST /blog /article/1/_update
{
"script" : "ctx._source.read_count+=1"
} - 刪除文檔
刪除文檔命令
curl -XDELETE http://localhost:9200/blog/article/1
6.Devlopment Process
- 引入maven依賴
在pom文件添加對(duì)應(yīng)版本的依賴包
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>2.3.4</version>
</dependency>
- 創(chuàng)建mapping
mapping是什么?為了能夠把日期字段處理成日期,把數(shù)字字段處理成數(shù)字矾削,把字符串字段處理成全文本(Full-text)或精確的字符串值壤玫,Elasticsearch需要知道每個(gè)字段里面都包含了什么類型。這些類型和字段的信息存儲(chǔ)(包含)在映射(mapping)中 - 為什么要?jiǎng)?chuàng)建mapping
雖然Elasticsearch能更夠根據(jù)數(shù)據(jù)類型創(chuàng)建自動(dòng)創(chuàng)建mapping哼凯,但是自動(dòng)創(chuàng)建mapping很可能不符合要求欲间,例如我們想對(duì)指定的字段指定分詞器
查看mapping命令
curl -XGET http://localhost:9200/blog/article/_mapping/
或者通過(guò)head插件查看
結(jié)果如何
{
"blog": {
"mappings": {
"article": {
"_ttl": {
"enabled": false
},
"properties": {
"author": {
"type": "string",
"index": "not_analyzed"
},
"content": {
"type": "string",
" analyzer ": "ik "
},
"publish_time": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"read_count": {
"type": "integer"
},
"title": {
"type": "string",
"analyzer": "ik"
}
}
}
}
}
}
主要需要指定字段type類型,對(duì)于string類型断部,需要指定是否需要分詞猎贴,以及分詞器的類型
public class BlogMapping {
public static XContentBuilder getMapping(){
XContentBuilder mapping = null;
try {
mapping = jsonBuilder()
.startObject()
//開(kāi)啟倒計(jì)時(shí)功能
.startObject("_ttl")
.field("enabled",false)
.endObject()
.startObject("properties")
.startObject("title")
.field("type","string")
.field("analyzer", "ik")
.endObject()
.startObject("content")
.field("type","string")
.field("index","not_analyzed")
.endObject()
.startObject("author")
.field("type","string")
.field("index","not_analyzed")
.endObject()
.startObject("publish_time")
.field("type","date")
.field("index","not_analyzed")
.endObject()
.startObject("read_count")
.field("type","integer")
.field("index","not_analyzed")
.endObject()
.endObject()
.endObject();
} catch (IOException e) {
e.printStackTrace();
}
return mapping;
}
}
- 分析分詞結(jié)果
查看分詞結(jié)果
curl -XGET 'localhost:9200/_analyze' -d '
{
"analyzer": "ik",
"text": "百度張亞勤:ABC時(shí)代來(lái)了,迎戰(zhàn)云計(jì)算“馬拉松”"
}
命令行下官方推薦使用get方法蝴光,使用post方法也是可以的她渴,而且命令行環(huán)境下對(duì)漢字的支持不是很有友好,可以通過(guò)head插件或者其他restclient圖形界面工具查詢分詞結(jié)果蔑祟,通過(guò)圖形界面工具查詢務(wù)必使用post方法
- 分詞結(jié)果如下:
[百度趁耗,百,度疆虚,張苛败,亞满葛,勤,abc罢屈,時(shí)代嘀韧,來(lái)了,迎戰(zhàn)缠捌,戰(zhàn)云锄贷,計(jì)算,馬拉松曼月,馬谊却,拉,松]
分詞結(jié)果直接決定了十嘿,我們應(yīng)該采用精確匹配還是全文檢索,以及我們搜索的關(guān)鍵詞能否命中 - 創(chuàng)建DSL語(yǔ)句
Elasticsearch檢索文件是通過(guò)DSL語(yǔ)句岳锁,檢索文檔分為兩種分式精確查詢和全文檢索绩衷,在Elasticsearch里面分別對(duì)應(yīng)term query和match query
term query:
在給定的字段里查詢?cè)~或者詞組;
提供的查詢?cè)~是不分詞的(not analyzed)激率,即只有完全包含才算匹配咳燕;
支持boost屬性,boost可以提高field和document的相關(guān)性乒躺;
match query:
與term query不同招盲,match query的查詢?cè)~是被分詞處理的(analyzed),即首先分詞嘉冒,然后構(gòu)造相應(yīng)的查詢曹货,所以應(yīng)該確保查詢的分詞器和索引的分詞器是一致的;
與terms query相似讳推,提供的查詢?cè)~之間默認(rèn)是or的關(guān)系顶籽,可以通過(guò)operator屬性指定;
match query有兩種形式银觅,一種是簡(jiǎn)單形式礼饱,一種是bool形式;
希望通過(guò)[百度究驴,張亞勤镊绪,云計(jì)算,馬拉松]中任意一個(gè)搜索詞都能夠搜到上面的內(nèi)容洒忧,即百度張亞勤:ABC時(shí)代來(lái)了蝴韭,迎戰(zhàn)云計(jì)算“馬拉松”
如果使用term query
GET http://localhost:9200/blog/article/_search
{
"query": {
"term": {
"title": "百度"
}
}
}
當(dāng)title的值為“百度”或者“馬拉松”時(shí)可以檢索到內(nèi)容,當(dāng)title為“張亞勤或者云計(jì)算時(shí)熙侍,無(wú)法檢索到文檔万皿,因?yàn)榉衷~結(jié)果是[百度摧找,百,度牢硅,張蹬耘,亞,勤减余,abc综苔,時(shí)代,來(lái)了位岔,迎戰(zhàn)如筛,戰(zhàn)云,計(jì)算抒抬,馬拉松杨刨,馬,拉擦剑,松]妖胀,無(wú)法匹配“張亞勤”和“云計(jì)算”
如果使用match query檢索
{
"query": {
"match": {
"title": {
"query": "張亞勤",
"operator": "and",
"minimum_should_match": "3"
}
}
}
}
使用match query檢索,ik分詞器會(huì)把張亞勤分[張惠勒,亞赚抡,勤],會(huì)把三個(gè)元素依次和[百度,百纠屋,度涂臣,張,亞售担,勤赁遗,abc,時(shí)代族铆,來(lái)了吼和,迎戰(zhàn),戰(zhàn)云骑素,計(jì)算炫乓,馬拉松,馬献丑,拉末捣,松]里面的數(shù)據(jù)比對(duì),operator=and,表示每個(gè)分詞比較結(jié)果是與的關(guān)系创橄,minimum_should_match=3箩做,表示至少三次匹配成功,此時(shí)就能檢索到內(nèi)容
multi_match
如果我們希望兩個(gè)字段進(jìn)行匹配妥畏,其中一個(gè)字段有這個(gè)文檔就滿足的話邦邦,使用multi_match安吁,用戶輸入一個(gè)搜索詞,我們很少根據(jù)有一個(gè)字段進(jìn)行索引燃辖,通常是根據(jù)某幾個(gè)字段進(jìn)行索引鬼店,例如我們想根據(jù)title或者content字段搜包含“云計(jì)算”的內(nèi)容,那么相應(yīng)DSL語(yǔ)句:
{
"query": {
"multi_match": {
"query" : "云計(jì)算",
"fields" : ["title", "content"]
}
}
}
- 理解評(píng)分機(jī)制
通過(guò)DSL語(yǔ)句只是查詢到我們需要的結(jié)果黔龟,但是怎么對(duì)查詢到的結(jié)果進(jìn)行排序妇智,就需要了解ElasticSearch的評(píng)分機(jī)制,ElasticSearch默認(rèn)是安裝查詢文檔的評(píng)分排序的氏身。
詳細(xì)查看評(píng)分的命令
POST http://localhost:9200/blog/article/_search?explain
{
"query": {
"match": {
"title": {
"query": "羅一笑",
"operator": "and",
"minimum_should_match": "3"
}
}
}
}
搜索詞:羅一笑
查詢時(shí)將搜索詞分為4個(gè)分詞(term)巍棱,羅,一笑蛋欣,一航徙,笑
每個(gè)詞有一個(gè)評(píng)分
羅:0.042223614
一笑:0.11945401
一:0.11945401
笑:0.11945401
根據(jù)羅一笑查詢到文檔的總評(píng)分為:0.40058565=0.042223614+0.11945401+0.11945401+0.11945401
對(duì)于每一個(gè)term的評(píng)分規(guī)則是:term評(píng)分= queryweight * fieldweight
一笑的評(píng)分:0.11945401 = 0.54607546 * 0.21875
查詢權(quán)重query weight = idf * queryNorm
{
"value": 0.54607546,
"description": "queryWeight, product of:",
"details": [
{
"value": 1,
"description": "idf(docFreq=1, maxDocs=2)",
"details": []
},
{
"value": 0.54607546,
"description": "queryNorm",
"details": []
}
]
}
域權(quán)重fieldweight= tf * idf * fieldNorm
{
"value": 0.21875,
"description": "fieldWeight in 0, product of:",
"details": [
{
"value": 1,
"description": "tf(freq=1.0), with freq of:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
}
]
},
{
"value": 1,
"description": "idf(docFreq=1, maxDocs=2)",
"details": []
},
{
"value": 0.21875,
"description": "fieldNorm(doc=0)",
"details": []
}
]
}
主要有個(gè)兩個(gè)概念Term Frequency和Inverse document frequency
Term Frequency:某單個(gè)關(guān)鍵詞(term) 在某文檔的某字段中出現(xiàn)的頻率次數(shù), 顯然, 出現(xiàn)頻率越高意味著該文檔與搜索的相關(guān)度也越高
具體計(jì)算公式是 tf(q in d) = sqrt(termFreq)
Inverse document frequency:某個(gè)關(guān)鍵詞(term) 在索引(單個(gè)分片)之中出現(xiàn)的頻次. 出現(xiàn)頻次越高, 這個(gè)詞的相關(guān)度越低. 相對(duì)的, 當(dāng)某個(gè)關(guān)鍵詞(term)在一大票的文檔下面都有出現(xiàn), 那么這個(gè)詞在計(jì)算得分時(shí)候所占的比重就要比那些只在少部分文檔出現(xiàn)的詞所占的得分比重要低. 說(shuō)的那么長(zhǎng)一句話, 用人話來(lái)描述就是 “物以稀為貴”。
具體計(jì)算公式:
idf = 1 + ln(maxDocs/(docFreq + 1))
詳細(xì)計(jì)算公式請(qǐng)參考lucene源碼或者參考http://www.hankcs.com/program/java/lucene-scoring-algorithm-explained.html
lucene的評(píng)分機(jī)制過(guò)于復(fù)雜陷虎,我們可以根據(jù)上述公式派生出簡(jiǎn)單容易理解的規(guī)則
匹配的詞條越罕見(jiàn)到踏,文檔的得分越高
文檔的字段越小,文檔的得分越高
字段的加權(quán)越高泻红,文檔的得分越高