用 Golang 寫一個搜尋引擎(0x09)— 資料增,刪,改

吳YH堅發表於2019-02-13

根據某位和我同姓的朋友的建議,後面的文章都會加上副標題,方便查閱。

今天的文章會比較短,很快就能看完。

按照步驟,說完段層以後,應該就開始涉及到索引層了,但我想說的是一個分散式的搜尋引擎,所以除了索引層以外,還有個分片層,這兩個概念是緊密聯絡在一起的,我怕說不好,所以在說索引層和分片層之前,我們先直觀的對索引有個瞭解,並且先熟悉一下索引層的一些特殊的資料結構以及一些常用演算法,讓大家對搜尋引擎有個整體瞭解以後再來說說索引層和分片層。

本篇只會涉及到資料的增,刪,改,因為是一個大的話題,涉及的東西有點多,可能需要單獨的一篇甚至兩篇來說,好了,不廢話,我們來說說今天的主題吧。

之前我們已經基本將搜尋引擎的底層資料結構介紹完畢了,那麼一個搜尋引擎最基本的功能也和資料庫一樣,為了使搜尋引擎跑起來,必須要有增,刪,改,查四個基本功能,下來我們一個一個來說。

在這之前,我們還需要一些準備工作,那麼還是用一個例子引出所有內容,假如我們有一份這樣的微博資料,每一條資料就是一個微博使用者釋出的一條微博,每條資料有三個欄位,分別是使用者暱稱,微博內容,釋出時間,具體資料長成這樣子:

{"nickname":"吳YH堅","content":"今天是個好日子","datetime":"2016-05-05 22:12:00"}

那麼首先,我們需要建立一個這樣的索引結構

欄位名稱 欄位型別
nickname string(全詞匹配) 倒排+正排
content string(分詞模式匹配) 倒排+正排
datetime datetime(時間型別) 正排
_id 主鍵(自動生成型主鍵) 倒排+正排

好了,把上面這個表的內容提交到搜尋引擎中,就建立好這個索引結構了,搜尋引擎會按照我們之前所寫的,建立好各個欄位,給每個欄位建立好倒排和正排

增這個其實就是全量索引構建增量索引構建了,我們之前一直再說的就是這個,這裡我們就不詳細說了。

不管是全量還是增量,增加資料的時候就是將一條一條上文中的原始資料匯入到搜尋引擎中,每增加一條資料會先在記憶體中增加資料,然後達到一定條件以後將一批資料寫入到段檔案中,並且達到一定條件以後會將各個段合併起來。

唯一要說的是主鍵這個欄位,由於在原始資料中是沒有_id這個東西的,所以在增加資料的時候搜尋引擎內部會給每條資料生成一個_id。

具體的_id的生成方法有很多,可以使用隨機數生成,也可以使用GUID的方法進行生成,只要保證_id的唯一性就行了,由於最後我們需要的是一個分散式的搜尋引擎,所以_id的生成方法還是有一些講究的,一般簡單的可以用隨機數-時間戳-本機IP(MAC)地址來生成,這樣產生相同的_id概率就已經幾乎沒有了,當然對於分散式系統還有其他辦法生成唯一_id,我們以後會介紹。

主鍵的倒排我沒有儲存在段中,而是直接使用了一個叫primary.pk的B+樹檔案進行儲存,這個B+樹的value就是主鍵對應的docid,因為主鍵是唯一的,所以只需要一個value值就行了,不需要倒排鏈。

如果資料中自帶唯一id,那麼更簡單,直接使用就行了。

一般搜尋引擎也提供無主鍵的資料檢索,這種索引就只有增加和檢索操作了,由於沒有主鍵,一般不提供刪除和修改操作。

刪除操作是搜尋引擎比較特殊的操作,因為我們知道,儲存到搜尋引擎中的資料有倒排檔案,想想倒排檔案的資料結構,要從中刪除一個文件的話,需要先找出這個文件的內容,然後找到所有包含這個文件的倒排鏈,然後把每個倒排鏈中的文件刪除掉,再把倒排鏈從新排列,寫入倒排檔案中,加上讀取詞典資料,少說也有6,7次IO。

很麻煩是不是,我們當然不會這麼做,這裡,我們用到了一個新的資料結構,bitmap,分分鐘解決問題。

bitmap是什麼?bitmap是一種非常高效和常用的資料結構,它通過一個bit陣列來儲存特定資料的一種資料結構;由於bit是資料的最小單位,所以這種資料結構往往是非常節省儲存空間,而一個bit要不是0,要不是1,所以bitmap只適合用來儲存ture/false的這種資料(擴充套件以後可以儲存更多資訊),比如下面就是一個32位的bitmap,它可以用來表示0到31這些數中,哪些是有效的(用0表示),哪些是無效的(用1表示),在這裡,0,4,14這三個數是無效的。這個資料結構很好理解吧。

0000 0000 0000 0000 0100 0000 0001 0001

而搜尋引擎中,docid是連續的,並且docid是有效的還是被刪除了正好對應ture和false,所以非常適合使用bitmap來描述某個文件是否被刪除。

bitmap的基本運算是位運算,所以速度也特別快,並且bitmap用一個bit來表示一個文件,所以儲存空間也非常少,一個16M的bitmap可以儲存134217728個文件是否被刪除的資訊。

所以,在搜尋引擎中刪除一個資料,我們按照以下幾步進行

  • 通過主鍵找到這個文件的docid
  • 直接將bitmap的第docid位設定為1
  • 結束

所以說是刪除,實際並沒有刪除,資料還在,在檢索的時候做點小動作就能讓人覺得被刪除了,什麼小動作說檢索的時候再說吧。

有了增和刪,改的話就簡單了,先刪再增就是改了。

改的話還有個小技巧,為了儘量的少增加資料量,當我們修改的時候如果只修改了正排欄位,並且修改前後正排欄位的長度沒有發生變化的話,我們可以直接在原始的記錄上覆蓋,而不用刪除再增加記錄,當然,這麼做的話,修改的時候需要比對所有欄位來判斷使用哪種更新模式,不如先刪後增來得直接,而且在分散式的環境下,這樣不太利於同步資料。

說完了增,刪,改,特別特別簡單吧,但我們發現搜尋引擎如果一直不停的修改刪除資料的話,而實際上並沒有真正的刪除資料,資料會越來越多並不會減少,所以搜尋引擎每隔一段時間都要重做一下全量索引,把無用的資料真正的清除掉,不然冗餘的資料越來越多,會影響查詢的效率。

後面一篇我們再來詳細說說,涉及到的演算法部分稍微多一點,一篇可能說不完。


如果想看其他文章,歡迎關注我的公眾號,主要聊聊搜尋引擎,推薦廣告演算法以及其他瞎扯的東西 :)

用 Golang 寫一個搜尋引擎(0x09)— 資料增,刪,改

相關文章