1. 程式人生 > >Elasticsearch(六)瞭解全文搜尋

Elasticsearch(六)瞭解全文搜尋

遇到的問題

通過前面的學習,我們已經可以使用elasticsearch來進行資料的搜尋了,但此時我們發現了一個問題,這個問題如果沒有解決好就很影響我們後續的使用,那麼該問題是什麼呢?我們來看一下:
搜尋“在”關鍵字.png
上面的截圖是我搜索“在”關鍵字出來的結果,按照正常情況下,我們是不是不應該搜尋“在”也出來結果呢?因為我們做的是搜尋,不是模糊查詢,既然是搜尋的話,那像這種沒有意義的關鍵字就不應該搜尋出來才對的,還有像類似“在”、“是”、“了”等等這些單獨存在不能構成一箇中文詞義的字元也可以搜尋了,這顯然是一種搜尋效果不好的方式。還有一個問題,我們看看下面的截圖:
搜尋“中國人”關鍵字

上面這個截圖是我搜索“中國人”這個關鍵詞出來的結果,然後我們通過高亮匹配的內容發現,它是“中”、“國”、“人”拆成3個關鍵字去匹配內容了,而我們想要的是它匹配“中國人”,“中國”,“人”這樣的搜尋效果,但現在的情況卻是每個中文字元都能搜尋,這樣就不太智慧了。
那從表面上看,出現以上兩個問題的原因貌似是因為elasticsearch對中文的分詞不太智慧,它現在的分詞只是簡單的安照每個中文字元來分,那如果我們想讓elasticsearch的搜尋能更智慧一點,分析出我們搜尋的關鍵字是否有含義,然後只搜這些有含義的詞語,比如“中國人”,“中國”,“人”,這樣的話我們要做什麼操作才能讓elasticsearch支援呢?這個就是我們接下來要探討的問題了。
其實這個問題對elasticsearch來說是很好解決的,我們只需要換一個分詞演算法就可以了,但是為了讓大家對elasticsearch的搜尋過程和底層的分詞原理有一個比較深刻的瞭解,我們不忙著切換其他分詞演算法,而是先來看看全文檢索的一些基礎知識,不然的話大家也僅僅只停留在會用的階段,都不知道它底層做了什麼事情。

資料型別

在詳細瞭解全文搜尋前,我們先來看看平時我們開發一個應用會遇到什麼樣的資料,這些資料是屬於什麼型別的,通過對這些資料的瞭解,我們接下來才能知道全文搜尋,搜尋的是什麼樣的資料。
在開發過程中通常都會接觸到3種資料型別:結構化資料、半結構化資料和非結構化資料,這3種資料有什麼特點呢?
1、結構化資料:是一種二維結構的資料,也就是說它的資料看上去是一個橫向和縱向組成的,橫向資料每一行都代表著一個實體的資訊,縱向的列是固定的,並需要事先確定下來,是用來描述該實體的屬性,所以每一行資料對應的屬性都是一樣的。結構化資料可以使用關係形資料庫來儲存,比如我們很熟悉的mysql,oracle等。如下圖所示的資料:
結構化資料

2、半結構化資料:這種資料型別其實也是結構化的資料,但是不能使用關係型資料庫來儲存和描述資料,因為它每個實體的屬性可以不一樣,比如我們描述一個人的實體資訊,其中一個人可以不描述年齡,一個人可以描述年齡,而儲存在關係型資料庫中的資料每個實體資訊的屬性都是需要一樣的,但是它的擴充套件性很好,也比純結構化資料靈活。我們經常使用的json、xml、yml就是半結構化資料。如下圖所示的資料:
半結構化資料

3、非結構化資料:跟結構化資料相對應的一種資料,他沒有固定的結構,也就說我們不需要為該資料事先定義縱向的屬性列。比如各種文件、圖片、視訊/音訊等。而接下來我們要講的全文搜尋,就是搜尋這種非結構化的資料。

建立索引過程

通過剛剛我們對資料型別的講解,我們知道,全文搜尋是搜尋類似文件這種非結構化資料,並且它們的資料量都會比較大,那這樣的話,如果我們按照傳統的思維來對這些文件搜尋的話,那肯定是搜尋的速度會很慢,因為它要對這些文件逐字逐字的掃描,就好比我們看一本書,如果這本書沒有目錄的話,那麼我們要找一個內容,就要在茫茫字海中尋找,那這樣的話,我們是否可以把這些內容歸納成一個一個章節,這些章節有對應的頁碼,並統一放到一個地方,我們想要找某個內容的時候,先找這個內容對應的章節,再找到該章節對應的頁面,這樣的話就極大的提高了我們尋找內容的速度了,而這些歸納出來的章節就是我們所說的目錄。
那別人在做全文搜尋的時候,是否也能按照類似書的目錄這種思想來來設計呢?答案是肯定的,但是大家要知道,這是一種思想,具體的實現細節有很多,我們來看看全文搜尋的一種很常用的實現,具體過程如下:

1、給文件分詞

首先,搜尋引擎會有個分片語件,我們新建的文件會傳給分片語件,然後分片語件根據它特定的分詞演算法,把文件拆分成一個一個的詞元,如果以elasticsearch預設的分詞演算法來舉例,那麼如果我們建立以下這個文件:“我是一箇中國人,張姓在中國是大姓。”,首先傳到分片語件,然後得到以下新的資料:

我
是
一
個
中
國
人
張
姓
在
中
國
是
大
姓

這些就是分片語件拿到文件後拆分出來的詞元集合。

2、將得到的詞元建立索引

通過分片語件把文件拆成詞元后,搜尋引擎會把這些詞元傳給索引元件,索引元件會做以下事情:
(1)利用得到的詞建立一個字典。
索引元件首先會使用剛剛新建文件得到的詞元放到字典中,如下圖所示:
字典
該字典會有對應的文件ID

(2)對字典排序
對這些詞進行排序,這裡的排序演算法就以中文拼音首字母順序為例:
排序.png

(3)合併相同的詞
排好序後,會合並相同的詞:
合併
Frequency為詞頻率,表示此文件中包含了幾個此詞。

(4)生成索引檔案
最後對排序併合並好的字典生成索引檔案,該索引檔案是一個倒排表,連結串列狀的資料結構,每個詞都會標記總共有多少文件包含此詞,並且會關聯著該詞所在的文件id,文件id又會包含著此文件中包含了幾個此詞。
Document Frequency 即為文件頻次,表示總共有多少文件包含此詞。
索引檔案
如果此時我們再新增一個文件,那麼該索引檔案會發生什麼變化呢?我們來看一下:新增“中國是全球人口最多的國家”
此時,搜尋引擎又開始執行建立索引的步驟,經過1-4的步驟後,索引檔案變成如下這樣:
更新後的索引檔案
那麼索引引擎在新增一個文件的時候就要做這些事情了,最後生成出這些倒排表結構的索引檔案,每個詞都會關聯著所有包含該詞的文件id,找到指定的詞,就相當於找到該詞對應的文件了。
但是我們通過上述的這些步驟發現,新增一個文件要做的事情還挺多的,那這樣豈不是降低了我們系統的效能?如果單單從這方面來看,確實是,但是大家要知道,一般來說,我們錄入一個需要做全文索引的資料,一般都是採用非同步的方式去給該資料建立索引的,也就是說我們會發一個訊息告訴專門建立索引的服務,並把相關的資料放到訊息體,整個傳送訊息、獲取訊息和建立索引的過程都是非同步的。業務系統本身只是發了個訊息,然後就接著做下面的業務,真正耗效能的建立索引過程由搜尋伺服器處理,而搜素伺服器要做叢集太簡單了,所以這點效能的消耗並不是什麼問題。
最重要的是,有了這個索引檔案,我們要搜尋就變得很簡單了,我們不需要在這些文件中掃描我們要找的內容了,而是查詢索引檔案就可以了。那接下來,我們來看看我們在輸入一個關鍵字後,是如何在索引檔案查詢內容的。

搜尋過程

從我們輸入要搜尋的關鍵字到返回搜尋結果這個過程一般要經過這幾個步驟:

1、給搜尋關鍵字分詞

首先還是使用分片語件給搜尋關鍵字分詞,比如使用者輸入“舌尖上的中國”,那麼經過分片語件拆出來的詞就有:

舌
尖
上
的
中
國

2、搜尋索引

接著搜尋引擎拿著這些拆出來的詞去索引檔案找對應的詞,由於索引檔案是有順序的,一旦找到就停止向下找,然後獲取該詞對應的整個文件連結串列,如下圖:
搜尋結果
搜尋出來的結果有“的”,“中”,“國”,並且後面帶著該詞所在的文件id連結串列。“的”有一個id為2的文件,“中”和“國”分別都有id為1和id為2這兩個文件。但是由於“舌尖上”在索引檔案中沒有找到,所以也就沒有搜尋結果了。

3、連結串列合併

對包含“的”,“中”,“國”的連結串列進行合併操作,得到包含這些詞的所有文件id,所以最終就得到id為1和2這兩個文件了。最後進行搜尋結果相關度排序,使用特定的相關度排序演算法,根據詞在文件出現的頻率和我們提交的排序條件等計算,最終把這些文件id排序,排序好後預設獲取前10條文件id,如果使用者有提交查詢數量,那麼就獲取使用者提交的數量,最後通過文件id查詢出整個文件返回給使用者。

後記

那麼這就是elasticsearch為我們提供搜尋服務時,它要做的事情了,瞭解了這些大致的過程有利於我們更好的使用搜索引擎,並擴充套件一些基礎知識。那麼現在我們回到最開始說的遇到的問題上,我們說elasticsearch不夠智慧,它只是簡單的按照一個一個字元去搜索文件的內容,導致沒有語義的字元也搜尋出結果了。那這個問題,我們通過上面的全文搜尋過程可以知道,問題是出在分詞這一塊,也就是說分詞的時候它是一個一個字串去分的,最終導致索引檔案儲存的是文件中所有詞。
那這個問題如何解決呢?elasticsearch是否提供其他的分詞演算法,讓它支援中文語義來分詞,這樣的話,儲存在索引檔案中的詞就不再是一個一個的字元,而是有意義的詞語,比如“我是一箇中國人,張姓在中國是大姓。”這個文件,就變成了這樣:

是一個
中國
中國人
人
張姓

那我們下一章就來了解一下elasticsearch的分片語件,看看它給我們提供了哪些分片語件,和這些分片語件的特點是怎麼樣的,從而來確定elasticsearch是否內建一個更智慧的中文分詞方式。