Elasticsearch作為分布式搜索引擎可以說(shuō)應(yīng)用非常廣了,可以用于站內(nèi)搜索,日志查詢(xún)等功能。去年在工作中第一次接觸到了Elasticsearch, 在此總結(jié)一下自己學(xué)到的東西。
Elasticsearch 安裝
對(duì)于初學(xué)者來(lái)說(shuō)Elasticsearch的安裝建議采用docker的方式。雖然網(wǎng)上有各種教程關(guān)于如何安裝。但是這種方式的安裝是最方便快捷的了。這里推薦極客時(shí)間 提供的docker-compose, 里面既包含了Elasticsearch還有Kibana和Cerabro, 可以一鍵安裝到位了。
啟動(dòng)docker之后訪問(wèn)Kibana 地址為http://localhost:5601, 導(dǎo)入Kibana默認(rèn)提供的三種數(shù)據(jù), 然后就可以在Kibana的開(kāi)發(fā)者工具中練習(xí)Elasticsearch搜索和聚合的語(yǔ)法了。
搜索
搜索算分
在介紹搜索 DSL (Domain Specific Language) 之前先介紹一下Elasticsearch的搜索算分規(guī)則。
在ES5之前默認(rèn)的相關(guān)性算分采用TF-IDF,現(xiàn)在采用BM25。
- TF-IDF
- TF(Term Frequency): 檢索詞在一篇文檔中出現(xiàn)的頻率。 檢索詞出現(xiàn)的次數(shù)除以文檔的總字?jǐn)?shù)。
- IDF (Inverse Document Frequency): 計(jì)算方式為 log(全部文檔數(shù)/檢索詞出現(xiàn)過(guò)的文檔總數(shù))
TF-IDF計(jì)算公式:
TF(檢索詞1)* IDF(區(qū)塊鏈) + TF(檢索詞2)* IDF(檢索詞2)....
本質(zhì)就是加權(quán)求和
-
BM25
BM25的計(jì)算公式如下, 這里不做詳細(xì)介紹。
image.png
Term(詞項(xiàng)查詢(xún))
如果采用如下方式進(jìn)行查詢(xún)會(huì)發(fā)現(xiàn)返回結(jié)果為空,這是因?yàn)镋lasticsearch 在建立索引的時(shí)候會(huì)默認(rèn)對(duì)customer_first_name
字段進(jìn)行分詞, 分詞之后Mary變成了mary因此無(wú)法完全匹配到。
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"term": {
"customer_first_name": {
"value": "Mary"
}
}
}
}
如果改成如下語(yǔ)句就能完全匹配到了
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"term": {
"customer_first_name.keyword": {
"value": "Mary"
}
}
}
}
這是如下圖所示, text類(lèi)型字段ES會(huì)默認(rèn)創(chuàng)建一個(gè)keyword字段,通過(guò)這個(gè)字段去查詢(xún)就能?chē)?yán)格匹配到了。
Query (全文本查詢(xún))
Term查詢(xún)并不會(huì)去做分詞處理, 基于全文本的查詢(xún)會(huì)。 基于全文本的查找包括:Match Query / Match Phrase Query / Query String Query。查詢(xún)的時(shí)候會(huì)對(duì)輸入的查詢(xún)進(jìn)行分詞,每個(gè)詞逐個(gè)進(jìn)行底層查詢(xún),最后將結(jié)果進(jìn)行合并。并且為每個(gè)文檔生成一個(gè)算分。
下面例子中會(huì)先對(duì)“Low Spherecords”進(jìn)行分詞,比如結(jié)果是“l(fā)ow” 和“spherecords”, 然后再分別對(duì)這兩個(gè)單詞進(jìn)行底層搜索。
POST /kibana_sample_data_ecommerce/_search
{
"query": {
"match": {
"manufacturer":{
"query": "Low Spherecords"
}
}
}
}
Structured Search (結(jié)構(gòu)化搜索)
結(jié)構(gòu)化搜索針對(duì)的是日期,布爾類(lèi)型和數(shù)字這些類(lèi)型。對(duì)于文本來(lái)說(shuō),可能是博客標(biāo)簽,電商網(wǎng)站商品的UPCs碼或者其他標(biāo)識(shí)。
以日期格式為例可以通過(guò)range
進(jìn)行范圍查找
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"range": {
"order_date": {
"gte": "now-4y"
}
}
}
}
Bool Query (復(fù)合查詢(xún))
bool 查詢(xún)是一個(gè)或者多個(gè)子查詢(xún)的組合。總有有must
,should
,must_not
和filter
四種查詢(xún)子句。其中前面兩種影響算分屬于Query Context,后面兩個(gè)不影響算分,屬于Filter Context。
下面是一個(gè)bool 查詢(xún)的例子
GET /kibana_sample_data_ecommerce/_search
{
"query": {
"bool": {
"must": {
"term": {
"day_of_week" : "Monday"
}
},
"must_not": [
{
"range": {
"taxful_total_price": {
"lte": 90
}
}
}
],
"filter": {
"term": {
"currency": "EUR"
}
},
"should": [
{
"terms": {
"sku" : ["ZO0549605496", "ZO0299602996"]
}
}
]
}
}
}
子查詢(xún)可以任意順序出現(xiàn),同時(shí)可以嵌套多個(gè)查詢(xún),如果在bool查詢(xún)中沒(méi)有must條件,should中必須至少滿(mǎn)足一條查詢(xún)。
單字符串多字段查詢(xún)
- Dis Max Query
Dis max query 可以解決的問(wèn)題。如下有個(gè)例子,分別插入兩個(gè)文檔。
PUT /blogs/_doc/1
{
"title": "Quick brown rabbits",
"body": "Brown rabbits are commonly seen."
}
PUT /blogs/_doc/2
{
"title": "Keeping pets healthy",
"body": "My quick brown fox eats rabbits on a regular basis."
}
用如下兩個(gè)語(yǔ)法去查詢(xún),采用第一種語(yǔ)法文檔1排在文檔2的前面,采用第二種語(yǔ)法結(jié)果正好相反。
POST /blogs/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Brown fox" }},
{ "match": { "body": "Brown fox" }}
]
}
}
}
POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick fox" }},
{ "match": { "body": "Quick fox" }}
]
}
}
}
原因是因?yàn)榈谝环Nshould語(yǔ)法在算分的過(guò)程中會(huì)考慮整體語(yǔ)句匹配的總數(shù)。上述例子的中title和body字段是相互競(jìng)爭(zhēng)的, 不應(yīng)將分?jǐn)?shù)簡(jiǎn)單的疊加,而是找到單個(gè)最佳匹配字段的評(píng)分。Disjunction Max Query 是將任何與任一查詢(xún)匹配的文檔作為結(jié)果返回。采用字段上最匹配的評(píng)分返回
當(dāng)然第二種語(yǔ)法如果沒(méi)有加上tie_breaker參數(shù)就可能出現(xiàn)超預(yù)期的效果。比如查詢(xún)“Quick pets”的時(shí)候,因?yàn)閮蓚€(gè)文檔中的字段匹配分?jǐn)?shù)的最高都是一樣的所以,文檔1又出現(xiàn)在了文檔2的前面。可以通過(guò)如下加上tie_breaker參數(shù)解決。加上后,其他匹配語(yǔ)句的評(píng)分會(huì)與tie_breaker相乘 ,然后在與最佳匹配的語(yǔ)句求和。
POST blogs/_search
{
"query": {
"dis_max": {
"queries": [
{ "match": { "title": "Quick pets" }},
{ "match": { "body": "Quick pets" }}
],
"tie_breaker": 0.7
}
}
}
- Multi-Match
Multi-Match提供了best_fields
,most_fields
,cross_fields
三種查詢(xún)類(lèi)型來(lái)應(yīng)對(duì)不同的對(duì)字段查詢(xún)場(chǎng)景。
Multi-Match基本語(yǔ)法如下
GET /_search
{
"query": {
"multi_match" : {
"query": "this is a test",
"fields": [ "subject", "message" ]
}
}
}
下面是most_fields
的例子,這個(gè)例子中, title字段使用english分詞器, 然后插入兩個(gè)文檔。
PUT /titles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "english"
}
}
}
}
兩篇文檔
POST titles/_bulk
{ "index": { "_id": 1 }}
{ "title": "My dog barks" }
{ "index": { "_id": 2 }}
{ "title": "I see a lot of barking dogs on the road " }
使用下面的語(yǔ)法查詢(xún)會(huì)發(fā)現(xiàn)文檔1排在前面與期望不符,這是因?yàn)閑nglish分詞器會(huì)把詞性給抹掉掉了, barking 變成了bark , dogs變成了dog,而文檔1語(yǔ)句更短所以排在了前面。
GET titles/_search
{
"query": {
"match": {
"title": "barking dogs"
}
}
}
解決方法是修改titles的設(shè)置,增加子字段并添加standard分詞。在查詢(xún)的時(shí)候使用most_fields
類(lèi)型進(jìn)行搜索。
PUT /titles
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "english",
"fields": {"std": {"type": "text","analyzer": "standard"}}
}
}
GET /titles/_search
{
"query": {
"multi_match": {
"query": "barking dogs",
"type": "most_fields",
"fields": [ "title", "title.std" ]
}
}
}
GET /_search
{
"query": {
"multi_match" : {
"query": "Will Smith",
"type": "best_fields",
"fields": [ "first_name", "last_name" ],
"operator": "and"
}
}
}
上面采用best_fields
并不適合做跨字段的搜索。因?yàn)樗膱?zhí)行邏輯如下,是采用對(duì)每個(gè)field做operator的匹配。
(+first_name:will +first_name:smith)
| (+last_name:will +last_name:smith)
所有的term必須在一個(gè)field中都匹配到。
而cross_field
可用于跨字段搜索。
GET /_search
{
"query": {
"multi_match" : {
"query": "Will Smith",
"type": "cross_fields",
"fields": [ "first_name", "last_name" ],
"operator": "and"
}
}
}
它的執(zhí)行邏輯如下
+(first_name:will last_name:will)
+(first_name:smith last_name:smith)
所有的term都至少在一個(gè)field中匹配到。
聚合
Aggregation作為Search的一部分語(yǔ)法如下:
Metric Aggregation
Metric Aggregation可以用來(lái)做一些單值或者多值分析。單值分析比如min, max avg, sum , cardinality。 多值分析比如stats, extended stats, percentile, top hits。 下面是單值分析的例子:
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"max_tax_total_price": {
"max": {
"field": "taxful_total_price"
}
}
}
}
Bucket Aggregation
Bucket aggregation 是按照一定規(guī)則把文檔分配到不同的桶中,達(dá)到分類(lèi)的目的。
Terms Aggregation需要打開(kāi)fieldata。keyword默認(rèn)支持, text類(lèi)型需要在mapping中打開(kāi)然后才會(huì)按照分詞之后的結(jié)果進(jìn)行分類(lèi)。
如下這個(gè)例子中通過(guò)打開(kāi)category的fieldata從而實(shí)現(xiàn)針對(duì)category做聚合。
PUT kibana_sample_data_ecommerce/_mapping
{
"properties" : {
"category":{
"type": "text",
"fielddata": true
}
}
}
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"category": {
"terms": {
"field": "category"
}
}
}
}
下面是嵌套聚合的例子,先根據(jù)星期進(jìn)行分類(lèi),然后再根據(jù)total_quantity進(jìn)行降序排列取前三個(gè)。
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "day_of_week"
},
"aggs":{
"total_quantity":{
"top_hits": {
"size": 3,
"sort": [{"total_quantity":"desc"}]
}
}
}
}
}
}
Pipeline Aggregation
Pipeline就是在一次聚合分析的基礎(chǔ)之上再做一次聚合分析。比如下面的語(yǔ)法就是找出平均total_quantity最少的那個(gè)星期。buckets_path
指定聚合路徑,然后再去做一次min_bucket
的計(jì)算。
GET /kibana_sample_data_ecommerce/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "day_of_week"
},
"aggs":{
"avg_total_quantity":{
"avg": {
"field": "total_quantity"
}
}
}
},
"min_quantity":{
"min_bucket":{
"buckets_path":"categories>avg_total_quantity"
}
}
}
}
根據(jù)Pipeline的分析結(jié)果輸出到原結(jié)果中的位置,可將Pipeline分為兩大類(lèi):
- Sibling (結(jié)果和現(xiàn)有分析結(jié)果同級(jí))
- Max, min, Avg & Sum Bucket
- Stats, Extended Status Bucket
- Percentiles Buckets
- Parent (結(jié)果內(nèi)嵌到現(xiàn)有的聚合分析結(jié)果中)
- Derivative
- Cumultive Sum
- Moving Function (滑動(dòng)窗口)
當(dāng)數(shù)據(jù)分散在不同primary shards上的時(shí)候,會(huì)出現(xiàn)聚合不準(zhǔn)確的情況。可以通過(guò)添加show_term_doc_count_error
對(duì)結(jié)果進(jìn)行分析,同時(shí)通過(guò)增加shard_size的大小來(lái)提高精準(zhǔn)度。
GET my_flights/_search
{
"size": 0,
"aggs": {
"weather": {
"terms": {
"field":"OriginWeather",
"size":1,
"shard_size":1,
"show_term_doc_count_error":true
}
}
}
}
以上就是我對(duì)Elasticsearch搜索和聚合查詢(xún)的總結(jié),會(huì)有些遺漏的知識(shí)點(diǎn),具體可以通過(guò)ES的官網(wǎng)查閱。