遇到的問題
通過前面的學(xué)習(xí)响巢,我們已經(jīng)可以使用elasticsearch來進(jìn)行數(shù)據(jù)的搜索了缆八,但此時我們發(fā)現(xiàn)了一個問題吱七,這個問題如果沒有解決好就很影響我們后續(xù)的使用,那么該問題是什么呢指郁?我們來看一下:
上面的截圖是我搜索“在”關(guān)鍵字出來的結(jié)果,按照正常情況下拷呆,我們是不是不應(yīng)該搜索“在”也出來結(jié)果呢闲坎?因?yàn)槲覀冏龅氖撬阉鳎皇悄:樵儾绺热皇撬阉鞯脑捬窍襁@種沒有意義的關(guān)鍵字就不應(yīng)該搜索出來才對的,還有像類似“在”项秉、“是”绣溜、“了”等等這些單獨(dú)存在不能構(gòu)成一個中文詞義的字符也可以搜索了,這顯然是一種搜索效果不好的方式娄蔼。還有一個問題怖喻,我們看看下面的截圖:
上面這個截圖是我搜索“中國人”這個關(guān)鍵詞出來的結(jié)果,然后我們通過高亮匹配的內(nèi)容發(fā)現(xiàn)岁诉,它是“中”锚沸、“國”、“人”拆成3個關(guān)鍵字去匹配內(nèi)容了涕癣,而我們想要的是它匹配“中國人”哗蜈,“中國”,“人”這樣的搜索效果,但現(xiàn)在的情況卻是每個中文字符都能搜索恬叹,這樣就不太智能了候生。
那從表面上看,出現(xiàn)以上兩個問題的原因貌似是因?yàn)閑lasticsearch對中文的分詞不太智能绽昼,它現(xiàn)在的分詞只是簡單的安照每個中文字符來分唯鸭,那如果我們想讓elasticsearch的搜索能更智能一點(diǎn),分析出我們搜索的關(guān)鍵字是否有含義硅确,然后只搜這些有含義的詞語目溉,比如“中國人”,“中國”菱农,“人”缭付,這樣的話我們要做什么操作才能讓elasticsearch支持呢?這個就是我們接下來要探討的問題了循未。
其實(shí)這個問題對elasticsearch來說是很好解決的陷猫,我們只需要換一個分詞算法就可以了,但是為了讓大家對elasticsearch的搜索過程和底層的分詞原理有一個比較深刻的了解的妖,我們不忙著切換其他分詞算法绣檬,而是先來看看全文檢索的一些基礎(chǔ)知識,不然的話大家也僅僅只停留在會用的階段嫂粟,都不知道它底層做了什么事情娇未。
數(shù)據(jù)類型
在詳細(xì)了解全文搜索前,我們先來看看平時我們開發(fā)一個應(yīng)用會遇到什么樣的數(shù)據(jù)星虹,這些數(shù)據(jù)是屬于什么類型的零抬,通過對這些數(shù)據(jù)的了解,我們接下來才能知道全文搜索宽涌,搜索的是什么樣的數(shù)據(jù)平夜。
在開發(fā)過程中通常都會接觸到3種數(shù)據(jù)類型:結(jié)構(gòu)化數(shù)據(jù)、半結(jié)構(gòu)化數(shù)據(jù)和非結(jié)構(gòu)化數(shù)據(jù)卸亮,這3種數(shù)據(jù)有什么特點(diǎn)呢褥芒?
1、結(jié)構(gòu)化數(shù)據(jù):是一種二維結(jié)構(gòu)的數(shù)據(jù)嫡良,也就是說它的數(shù)據(jù)看上去是一個橫向和縱向組成的锰扶,橫向數(shù)據(jù)每一行都代表著一個實(shí)體的信息,縱向的列是固定的寝受,并需要事先確定下來坷牛,是用來描述該實(shí)體的屬性,所以每一行數(shù)據(jù)對應(yīng)的屬性都是一樣的很澄。結(jié)構(gòu)化數(shù)據(jù)可以使用關(guān)系形數(shù)據(jù)庫來儲存京闰,比如我們很熟悉的mysql颜及,oracle等。如下圖所示的數(shù)據(jù):
2蹂楣、半結(jié)構(gòu)化數(shù)據(jù):這種數(shù)據(jù)類型其實(shí)也是結(jié)構(gòu)化的數(shù)據(jù)俏站,但是不能使用關(guān)系型數(shù)據(jù)庫來存儲和描述數(shù)據(jù),因?yàn)樗總€實(shí)體的屬性可以不一樣痊土,比如我們描述一個人的實(shí)體信息肄扎,其中一個人可以不描述年齡,一個人可以描述年齡赁酝,而存儲在關(guān)系型數(shù)據(jù)庫中的數(shù)據(jù)每個實(shí)體信息的屬性都是需要一樣的犯祠,但是它的擴(kuò)展性很好,也比純結(jié)構(gòu)化數(shù)據(jù)靈活酌呆。我們經(jīng)常使用的json衡载、xml、yml就是半結(jié)構(gòu)化數(shù)據(jù)隙袁。如下圖所示的數(shù)據(jù):
3痰娱、非結(jié)構(gòu)化數(shù)據(jù):跟結(jié)構(gòu)化數(shù)據(jù)相對應(yīng)的一種數(shù)據(jù),他沒有固定的結(jié)構(gòu)菩收,也就說我們不需要為該數(shù)據(jù)事先定義縱向的屬性列梨睁。比如各種文檔、圖片坛梁、視頻/音頻等而姐。而接下來我們要講的全文搜索腊凶,就是搜索這種非結(jié)構(gòu)化的數(shù)據(jù)划咐。
創(chuàng)建索引過程
通過剛剛我們對數(shù)據(jù)類型的講解,我們知道钧萍,全文搜索是搜索類似文檔這種非結(jié)構(gòu)化數(shù)據(jù)褐缠,并且它們的數(shù)據(jù)量都會比較大,那這樣的話风瘦,如果我們按照傳統(tǒng)的思維來對這些文檔搜索的話队魏,那肯定是搜索的速度會很慢,因?yàn)樗獙@些文檔逐字逐字的掃描万搔,就好比我們看一本書胡桨,如果這本書沒有目錄的話,那么我們要找一個內(nèi)容瞬雹,就要在茫茫字海中尋找昧谊,那這樣的話,我們是否可以把這些內(nèi)容歸納成一個一個章節(jié)酗捌,這些章節(jié)有對應(yīng)的頁碼呢诬,并統(tǒng)一放到一個地方涌哲,我們想要找某個內(nèi)容的時候,先找這個內(nèi)容對應(yīng)的章節(jié)尚镰,再找到該章節(jié)對應(yīng)的頁面阀圾,這樣的話就極大的提高了我們尋找內(nèi)容的速度了,而這些歸納出來的章節(jié)就是我們所說的目錄狗唉。
那別人在做全文搜索的時候初烘,是否也能按照類似書的目錄這種思想來來設(shè)計(jì)呢?答案是肯定的敞曹,但是大家要知道账月,這是一種思想,具體的實(shí)現(xiàn)細(xì)節(jié)有很多澳迫,我們來看看全文搜索的一種很常用的實(shí)現(xiàn)局齿,具體過程如下:
1、給文檔分詞
首先橄登,搜索引擎會有個分詞組件抓歼,我們新建的文檔會傳給分詞組件,然后分詞組件根據(jù)它特定的分詞算法拢锹,把文檔拆分成一個一個的詞元谣妻,如果以elasticsearch默認(rèn)的分詞算法來舉例,那么如果我們創(chuàng)建以下這個文檔:“我是一個中國人卒稳,張姓在中國是大姓蹋半。”
充坑,首先傳到分詞組件减江,然后得到以下新的數(shù)據(jù):
我
是
一
個
中
國
人
張
姓
在
中
國
是
大
姓
這些就是分詞組件拿到文檔后拆分出來的詞元集合。
2捻爷、將得到的詞元創(chuàng)建索引
通過分詞組件把文檔拆成詞元后辈灼,搜索引擎會把這些詞元傳給索引組件,索引組件會做以下事情:
(1)利用得到的詞創(chuàng)建一個字典也榄。
索引組件首先會使用剛剛新建文檔得到的詞元放到字典中巡莹,如下圖所示:
該字典會有對應(yīng)的文檔ID
(2)對字典排序
對這些詞進(jìn)行排序,這里的排序算法就以中文拼音首字母順序?yàn)槔?br>
(3)合并相同的詞
排好序后甜紫,會合并相同的詞:
Frequency為詞頻率降宅,表示此文檔中包含了幾個此詞。
(4)生成索引文件
最后對排序并合并好的字典生成索引文件囚霸,該索引文件是一個倒排表腰根,鏈表狀的數(shù)據(jù)結(jié)構(gòu),每個詞都會標(biāo)記總共有多少文檔包含此詞邮辽,并且會關(guān)聯(lián)著該詞所在的文檔id唠雕,文檔id又會包含著此文檔中包含了幾個此詞贸营。
Document Frequency 即為文檔頻次,表示總共有多少文檔包含此詞岩睁。
如果此時我們再新增一個文檔钞脂,那么該索引文件會發(fā)生什么變化呢?我們來看一下:添加
“中國是全球人口最多的國家”
捕儒。此時冰啃,搜索引擎又開始執(zhí)行創(chuàng)建索引的步驟,經(jīng)過1-4的步驟后刘莹,索引文件變成如下這樣:
那么索引引擎在新增一個文檔的時候就要做這些事情了阎毅,最后生成出這些倒排表結(jié)構(gòu)的索引文件,每個詞都會關(guān)聯(lián)著所有包含該詞的文檔id点弯,找到指定的詞扇调,就相當(dāng)于找到該詞對應(yīng)的文檔了。
但是我們通過上述的這些步驟發(fā)現(xiàn)抢肛,添加一個文檔要做的事情還挺多的狼钮,那這樣豈不是降低了我們系統(tǒng)的性能?如果單單從這方面來看捡絮,確實(shí)是熬芜,但是大家要知道,一般來說福稳,我們錄入一個需要做全文索引的數(shù)據(jù)涎拉,一般都是采用異步的方式去給該數(shù)據(jù)創(chuàng)建索引的,也就是說我們會發(fā)一個消息告訴專門創(chuàng)建索引的服務(wù)的圆,并把相關(guān)的數(shù)據(jù)放到消息體鼓拧,整個發(fā)送消息、獲取消息和創(chuàng)建索引的過程都是異步的略板。業(yè)務(wù)系統(tǒng)本身只是發(fā)了個消息毁枯,然后就接著做下面的業(yè)務(wù)慈缔,真正耗性能的創(chuàng)建索引過程由搜索服務(wù)器處理叮称,而搜素服務(wù)器要做集群太簡單了,所以這點(diǎn)性能的消耗并不是什么問題藐鹤。
最重要的是瓤檐,有了這個索引文件,我們要搜索就變得很簡單了娱节,我們不需要在這些文檔中掃描我們要找的內(nèi)容了挠蛉,而是查找索引文件就可以了。那接下來肄满,我們來看看我們在輸入一個關(guān)鍵字后谴古,是如何在索引文件查找內(nèi)容的质涛。
搜索過程
從我們輸入要搜索的關(guān)鍵字到返回搜索結(jié)果這個過程一般要經(jīng)過這幾個步驟:
1、給搜索關(guān)鍵字分詞
首先還是使用分詞組件給搜索關(guān)鍵字分詞掰担,比如用戶輸入“舌尖上的中國”
汇陆,那么經(jīng)過分詞組件拆出來的詞就有:
舌
尖
上
的
中
國
2、搜索索引
接著搜索引擎拿著這些拆出來的詞去索引文件找對應(yīng)的詞带饱,由于索引文件是有順序的毡代,一旦找到就停止向下找,然后獲取該詞對應(yīng)的整個文檔鏈表勺疼,如下圖:
搜索出來的結(jié)果有“的”教寂,“中”,“國”执庐,并且后面帶著該詞所在的文檔id鏈表酪耕。“的”有一個id為2的文檔轨淌,“中”和“國”分別都有id為1和id為2這兩個文檔因妇。但是由于
“舌尖上”
在索引文件中沒有找到,所以也就沒有搜索結(jié)果了猿诸。
3婚被、鏈表合并
對包含“的”,“中”梳虽,“國”的鏈表進(jìn)行合并操作址芯,得到包含這些詞的所有文檔id,所以最終就得到id為1和2這兩個文檔了窜觉。最后進(jìn)行搜索結(jié)果相關(guān)度排序谷炸,使用特定的相關(guān)度排序算法,根據(jù)詞在文檔出現(xiàn)的頻率和我們提交的排序條件等計(jì)算禀挫,最終把這些文檔id排序旬陡,排序好后默認(rèn)獲取前10條文檔id,如果用戶有提交查詢數(shù)量语婴,那么就獲取用戶提交的數(shù)量描孟,最后通過文檔id查詢出整個文檔返回給用戶。
后記
那么這就是elasticsearch為我們提供搜索服務(wù)時砰左,它要做的事情了匿醒,了解了這些大致的過程有利于我們更好的使用搜索引擎,并擴(kuò)展一些基礎(chǔ)知識缠导。那么現(xiàn)在我們回到最開始說的遇到的問題上廉羔,我們說elasticsearch不夠智能,它只是簡單的按照一個一個字符去搜索文檔的內(nèi)容僻造,導(dǎo)致沒有語義的字符也搜索出結(jié)果了憋他。那這個問題孩饼,我們通過上面的全文搜索過程可以知道,問題是出在分詞這一塊竹挡,也就是說分詞的時候它是一個一個字符串去分的捣辆,最終導(dǎo)致索引文件保存的是文檔中所有詞。
那這個問題如何解決呢此迅?elasticsearch是否提供其他的分詞算法汽畴,讓它支持中文語義來分詞,這樣的話耸序,保存在索引文件中的詞就不再是一個一個的字符忍些,而是有意義的詞語,比如“我是一個中國人坎怪,張姓在中國是大姓罢坝。”
這個文檔搅窿,就變成了這樣:
是一個
中國
中國人
人
張姓
那我們下一章就來了解一下elasticsearch的分詞組件嘁酿,看看它給我們提供了哪些分詞組件,和這些分詞組件的特點(diǎn)是怎么樣的男应,從而來確定elasticsearch是否內(nèi)置一個更智能的中文分詞方式闹司。