Elasticsearch入門(mén)(一)—— 搜索與聚合

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
  1. TF(Term Frequency): 檢索詞在一篇文檔中出現(xiàn)的頻率。 檢索詞出現(xiàn)的次數(shù)除以文檔的總字?jǐn)?shù)。
  2. 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)格匹配到了。

image.png

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_notfilter四種查詢(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)

  1. 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
        }
    }
}
  1. 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ǔ)法如下:


image.png

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)查閱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容