1、線上問題
——問題來自:死磕Elasticsearch 知識星球微信群
這個問題涉及到業務細節,至今沒有定論。不過,該問題引發了我的思考。
2、我的一點思考
我們使用 Elasticsearch 到底用來做什麼?
除了 Elasticsearch 早已不是10年前因“菜單”而火出技術圈的搜尋引擎元件,它早已不是“單兵作戰”,而是 ELKB 形成的 Elastic Stack “行走江湖”。
但,至少技術選型涉及到大資料的檢索幾乎無一例外 Elasticsearch 都是“首發陣容”。
2.1 關于全文檢索,使用者更關注什麼?
關于全文搜尋,《這就是搜尋引擎》張俊林博士從搜尋引擎的角度闡述了使用者的關注點,核心就是兩個。
- 其一:精準率;
- 其二:召回率。
通俗點講:
- 精準率是站在使用者角度,召回的資料貼合使用者的預期,越準确越好。
當然,大資料時代的今天,單純的字、詞比對早已跟不上時代的步伐,基于使用者行為的推薦(如:抖音、網易雲音樂)往往更吊起使用者的胃口。
- 而召回率是滿足檢索條件的語句都盡可能的召回,到底要什麼,讓使用者在結果中二次再做選擇。
這兩種都有應用場景,無所謂誰對誰錯。
提到 Elasticsearch 精準召回資料,先不談“精準”,先說一下召回。
如下圖所示,可以分兩部分看:資料的寫入過程、資料檢索過程。
資料寫入過程要比圖中複雜,我們着重關注建立反向索引的過程,因為後面我們要基于反向索引做全文檢索。
2.2 資料寫入過程
寫入的文本如下:
基于 ik_smart 分詞後,反向索引的中的分詞詞典如下所示:
2.3 資料檢索過程
Elasticsearch 提到檢索,這其實是一個大的概念,不信你看下面的腦圖。
Elasticsearch 檢索從大的角度可以看成黑盒,類似:Google、baidu 搜尋框。使用者搜尋框輸入内容,檢索召回資料。
但是,技術人員的眼裡看搜尋,更關注用哪種類型搜尋。這涉及到檢索分類。
Elasticsearch 檢索大緻可以分為:全文檢索、精準比對檢索、多表關聯檢索、組合檢索、不常用檢索。
精準比對檢索回答的是“是和否、存在或不存在”問題?比如:小明的手機号為:“13566668888”,多一位、少一位、錯一位都不能被召回!完全滿足檢索條件就召回,不滿足檢索條件就不要召回。不存在中間情況。
而全文檢索回答的是“相關度”的問題?如文章開頭提到的“手表”、“手表帶”、“表帶”就有相關度,哪些資料該召回?誰優先被召回(也就是誰排在前面)。
match_phrase 和 match 等實作檢索的機制是不一樣的,“profile:true" API 能幫我們更精細的看到底層的實作。
簡單點說:match_phrase 走的是短語檢索比對,而 match 走的是多字段拆解後的 term query 的 bool 語句組合體。
2.4 如何了解精準?
其實這個沒有普适的标準,不同的業務系統是不一樣的。
建議,結合業務需求、産品經理和技術經理、項目經理、核心技術人員共同敲定。
滿足使用者要求的“精準”才算是精準。
比如文章開頭提到的“手表”,其實有多種了解?
- 其一:隻有“手表”兩個字,沒有任何其他,這種叫精準。
- 其二:分詞詞典裡有“手表”,就要召回,這種也叫精準。
- 其三:隻要文本裡有就要召回,這也是某種意義的精準。比如:“南京市長江大橋”,搜尋“江大橋”也要求召回。
等等......不一而足。
有了上面的思考,我們嘗試解讀一下開篇的問題。
3、Elasticsearch 8.X 更精準檢索實作
如下示例,拿資料說話!
3.1 字詞混合索引檢索方案
- 詞索引——使用 ik_smart 分詞。
- 字索引——使用 standard 标準分詞。
保留了 keyword 類型,便于方案二的精準字元比對。
DELETE test-20220928
PUT test-20220928
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_smart",
"fields": {
"standard": {
"type": "text",
"analyzer": "standard"
},
"keyword": {
"type": "keyword"
}
}
}
}
}
}
POST test-20220928/_bulk
{"index":{"_id":1}}
{"title":"手表帶真好看"}
{"index":{"_id":2}}
{"title":"手表最近賣的不好,咋整"}
{"index":{"_id":3}}
{"title":"卡西歐手表不錯哦"}
{"index":{"_id":4}}
{"title":"手表"}
先看“手表帶真好看”這個文檔 ik_smart 的分詞結果。
其他幾個文檔{“2”,“3”,“4”} 都包含手表的分詞,大家可以自己驗證,篇幅原因,沒有截圖。
如下檢索是 bool 組合混合體。
對于:must 條件要求單字相連的多字(可以了解為短語,但不見得是有意義的短語,如:江大橋)必須滿足,用 短語 match_phrase 進行檢索。
對于:should 條件滿足 ik_smart 分詞存在結果,則召回資料,且極大的提升評分權重。
POST test-20220928/_search
{
"query": {
"bool": {
"should": [
{
"match_phrase": {
"title": {
"query": "手表",
"boost": 50
}
}
}
],
"must": [
{
"match": {
"title.standard": "手表"
}
}
]
}
}
}
明顯看出來:包含手表要排在前面。
3.2 自定義評分實作精準檢索
使用前提:針對是 keyword 類型。
大家記住:sort 排序、aggregation 聚合、script 腳本都隻能針對 keyword 類型,text 類型都是不支援的,除非開啟“fielddata”(必要性非常小,使用場景也小,不建議開啟)。
如下腳本的含義,如果字段精準比對,沒有多餘字元,則評分極高,設定為1000;如果字段以給定關鍵詞開頭,則評分高,設定為500;如果屬于包含關系,則評分也較高,設定為100;如果沒有包含,那評分為10。
POST test-20220928/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"source": "if(doc['title.keyword'].value == params.keyword) { return 1000; } else if(doc['title.keyword'].value.startsWith(params.keyword)){ return 500; } else if(doc['title.keyword'].value.contains(params.keyword)) { return 100; } else { return 10;}",
"params": {
"keyword": "手表"
}
}
}
}
}
相當于我們人工幹預了評分,基于字段精準比對的情況實作了評分的區分。這樣,最先召回的結果資料就是我們最期望的精準比對結果了。
4、小結
針對企業級實戰問題,引發了思考,并根據思考嘗試做了解答。
當然,這道業務題目會有具體的細節業務場景,還需要進一步溝通交流。
- 分詞(中文分詞器、預設分詞器)
- 組合分詞(fields)
- 組合檢索
- 排序(評分)+ 全文檢索+召回
- 自定義評分(自己定義的規則來進行資料的評分,進而将評分高的優先傳回,排在前面進行傳回!)