初次接觸 Elasticsearch 的同學(xué)經(jīng)常會遇到分詞相關(guān)的難題豹缀,比如如下這些場景:
- 為什么命名有包含搜索關(guān)鍵詞的文檔坟漱,但結(jié)果里面就沒有相關(guān)文檔呢鼠次?
- 我存進去的文檔到底被分成哪些詞(term)了?
- 我得自定義分詞規(guī)則,但感覺好麻煩呢腥寇,無從下手
如果你遇到過類似的問題成翩,希望本文可以解決你的疑惑。
1. 上手
讓我們從一個實例出發(fā)赦役,如下創(chuàng)建一個文檔:
PUT test/doc/1
{
"msg":"Eating an apple a day keeps doctor away"
}
然后我們做一個查詢麻敌,我們試圖通過搜索 eat
這個關(guān)鍵詞來搜索這個文檔
POST test/_search
{
"query":{
"match":{
"msg":"eat"
}
}
}
ES的返回結(jié)果為0。這不太對啊掂摔,我們用最基本的字符串查找也應(yīng)該能匹配到上面新建的文檔才對笆醺帷!
各位不要急棒呛,我們先來看看什么是分詞聂示。
2. 分詞
搜索引擎的核心是倒排索引(這里不展開講),而倒排索引的基礎(chǔ)就是分詞簇秒。所謂分詞可以簡單理解為將一個完整的句子切割為一個個單詞的過程鱼喉。在 es 中單詞對應(yīng)英文為 term
。我們簡單看個例子:
ES 的倒排索引即是根據(jù)分詞后的單詞創(chuàng)建趋观,即 我
扛禽、愛
、北京
皱坛、天安門
這4個單詞编曼。這也意味著你在搜索的時候也只能搜索這4個單詞才能命中該文檔。
實際上 ES 的分詞不僅僅發(fā)生在文檔創(chuàng)建的時候剩辟,也發(fā)生在搜索的時候掐场,如下圖所示:
讀時分詞
發(fā)生在用戶查詢時,ES 會即時地對用戶輸入的關(guān)鍵詞進行分詞贩猎,分詞結(jié)果只存在內(nèi)存中熊户,當(dāng)查詢結(jié)束時,分詞結(jié)果也會隨即消失吭服。而寫時分詞
發(fā)生在文檔寫入時嚷堡,ES 會對文檔進行分詞后,將結(jié)果存入倒排索引艇棕,該部分最終會以文件的形式存儲于磁盤上蝌戒,不會因查詢結(jié)束或者 ES 重啟而丟失。
ES 中處理分詞的部分被稱作分詞器沼琉,英文是Analyzer
北苟,它決定了分詞的規(guī)則。ES 自帶了很多默認的分詞器刺桃,比如Standard
粹淋、 Keyword
吸祟、Whitespace
等等,默認是 Standard
桃移。當(dāng)我們在讀時或者寫時分詞時可以指定要使用的分詞器屋匕。
3. 寫時分詞結(jié)果
回到上手階段,我們來看下寫入的文檔最終分詞結(jié)果是什么借杰。通過如下 api 可以查看:
POST test/_analyze
{
"field": "msg",
"text": "Eating an apple a day keeps doctor away"
}
其中 test
為索引名过吻,_analyze
為查看分詞結(jié)果的 endpoint
,請求體中 field
為要查看的字段名蔗衡,text
為具體值纤虽。該 api 的作用就是請告訴我在 test 索引使用 msg 字段存儲一段文本時,es 會如何分詞绞惦。
返回結(jié)果如下:
{
"tokens": [
{
"token": "eating",
"start_offset": 0,
"end_offset": 6,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "an",
"start_offset": 7,
"end_offset": 9,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "apple",
"start_offset": 10,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "a",
"start_offset": 16,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "day",
"start_offset": 18,
"end_offset": 21,
"type": "<ALPHANUM>",
"position": 4
},
{
"token": "keeps",
"start_offset": 22,
"end_offset": 27,
"type": "<ALPHANUM>",
"position": 5
},
{
"token": "doctor",
"start_offset": 28,
"end_offset": 34,
"type": "<ALPHANUM>",
"position": 6
},
{
"token": "away",
"start_offset": 35,
"end_offset": 39,
"type": "<ALPHANUM>",
"position": 7
}
]
}
返回結(jié)果中的每一個 token
即為分詞后的每一個單詞逼纸,我們可以看到這里是沒有 eat
這個單詞的,這也解釋了在上手中我們搜索 eat
沒有結(jié)果的情況济蝉。如果你去搜索 eating
杰刽,會有結(jié)果返回。
寫時分詞器需要在 mapping 中指定王滤,而且一經(jīng)指定就不能再修改贺嫂,若要修改必須新建索引。如下所示我們新建一個名為ms_english
的字段雁乡,指定其分詞器為 english
:
PUT test/_mapping/doc
{
"properties": {
"msg_english":{
"type":"text",
"analyzer": "english"
}
}
}
4. 讀時分詞結(jié)果
由于讀時分詞器默認與寫時分詞器默認保持一致第喳,拿 上手 中的例子,你搜索 msg
字段踱稍,那么讀時分詞器為 Standard
曲饱,搜索 msg_english
時分詞器則為 english
。這種默認設(shè)定也是非常容易理解的珠月,讀寫采用一致的分詞器渔工,才能盡最大可能保證分詞的結(jié)果是可以匹配的。
然后 ES 允許讀時分詞器單獨設(shè)置桥温,如下所示:
POST test/_search
{
"query":{
"match":{
"msg":{
"query": "eating",
"analyzer": "english"
}
}
}
}
如上 analyzer
字段即可以自定義讀時分詞器,一般來講不需要特別指定讀時分詞器梁丘。
如果不單獨設(shè)置分詞器侵浸,那么讀時分詞器的驗證方法與寫時一致;如果是自定義分詞器氛谜,那么可以使用如下的 api 來自行驗證結(jié)果掏觉。
POST _analyze
{
"text":"eating",
"analyzer":"english"
}
返回結(jié)果如下:
{
"tokens": [
{
"token": "eat",
"start_offset": 0,
"end_offset": 6,
"type": "<ALPHANUM>",
"position": 0
}
]
}
由上可知 english
分詞器會將 eating
處理為 eat
,大家可以再測試下默認的 standard
分詞器值漫,它沒有做任何處理澳腹。
5. 解釋問題
現(xiàn)在我們再來看下 上手 中所遇問題的解決思路。
- 查看文檔寫時分詞結(jié)果
- 查看查詢關(guān)鍵詞的讀時分詞結(jié)果
- 匹對兩者是否有命中
我們簡單分析如下:
由上圖可以定位問題的原因了。
6. 解決需求
由于 eating
只是 eat
的一個變形酱塔,我們依然希望輸入 eat
時可以匹配包含 eating
的文檔沥邻,那么該如何解決呢?
答案很簡單羊娃,既然原因是在分詞結(jié)果不匹配唐全,那么我們就換一個分詞器唄~ 我們可以先試下 ES 自帶的 english
分詞器,如下:
# 增加字段 msg_english蕊玷,與 msg 做對比
PUT test/_mapping/doc
{
"properties": {
"msg_english":{
"type":"text",
"analyzer": "english"
}
}
}
# 寫入相同文檔
PUT test/doc/1
{
"msg":"Eating an apple a day keeps doctor away",
"msg_english":"Eating an apple a day keeps doctor away"
}
# 搜索 msg_english 字段
POST test/_search
{
"query": {
"match": {
"msg_english": "eat"
}
}
}
執(zhí)行上面的內(nèi)容邮利,我們會發(fā)現(xiàn)結(jié)果有內(nèi)容了,原因也很簡單垃帅,如下圖所示:
由上圖可見 english
分詞器會將 eating
分詞為 eat
延届,此時我們搜索 eat
或者 eating
肯定都可以匹配對應(yīng)的文檔了。至此贸诚,需求解決方庭。
7. 深入分析
最后我們來看下為什么english
分詞器可以解決我們遇到的問題。一個分詞器由三部分組成:char filter赦颇、tokenizer 和 token filter二鳄。各部分的作用我們這里就不展開了,我們來看下 standard
和english
分詞器的區(qū)別媒怯。
從上圖可以看出订讼,english
分詞器在 Token Filter 中和 Standard
不同,而發(fā)揮主要作用的就是 stemmer
扇苞,感興趣的同學(xué)可以自行去看起它的作用欺殿。
8. 自定義分詞
如果我們不使用 english
分詞器,自定義一個分詞器來實現(xiàn)上述需求也是完全可行的鳖敷,這里不詳細講解了脖苏,只給大家講一個快速驗證自定義分詞器效果的方法,如下:
POST _analyze
{
"char_filter": [],
"tokenizer": "standard",
"filter": [
"stop",
"lowercase",
"stemmer"
],
"text": "Eating an apple a day keeps doctor away"
}
通過上面的 api 你可以快速驗證自己要定制的分詞器定踱,當(dāng)達到自己需求后棍潘,再將這一部分配置加入索引的配置。
至此崖媚,我們再看開篇的三個問題亦歉,相信你已經(jīng)心里有答案了,趕緊上手去自行測試下吧畅哑!