用Golang寫一個搜尋引擎(0x08)--- 索引的段

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

我覺得這個標題應該改改了,我寫下來其實是告訴大家怎麼寫一個搜尋引擎,並沒有涉及太多的Golang的東西,我覺得這樣也挺好,熟悉了原理,用什麼實現其實並不重要了,而且說說原理比說代碼更實在。

之前已經說了底層的資料結構了,包括倒排和正排索引。今天我們上一層,來說說索引的欄位和段。

欄位這個上一篇已經介紹過了,欄位的概念實際上是搜尋引擎索引中我們能看到的最底層的東西,也是對外暴露的最底層的概念,在欄位之下是倒排和正排索引,這兩項其實對使用者是封裝起來了,我們可以認為每個欄位對應一個正排和一個倒排,而實際上也確實是這樣的。

在欄位之上就是我們這一篇主要說的了,段這個概念並不是搜尋引擎特有的,也不是必須的,是我這個項目新增出來的,當然,也不是我原創,很多搜尋引擎的引擎系統都有這個概念。

所謂段,就是最基本的檢索系統,一個包含所有欄位,包含一部分連續的文檔集合,能夠進行完整的檢索,可以把它當成一個檢索系統最基本單位。

這麼說可能還是有點抽象,我們打個比方,在資料庫中,一行資料是最基本的單位,對應搜尋引擎中的是一個文檔,而表是所有文檔的集合,對應搜尋引擎中是一份索引,而段就是一部分表,它包含一部分文檔的內容,可以對這一部分文檔進行檢索,多個段合并起來就是一份完整的索引。

為什麼要有段這個概念呢?

  • 如果一個搜尋引擎的資料再建好索引以後並不變化,那麼完全沒有必要使用段,直接在建立全量索引的時候把資料都建好就行了

  • 如果有增量資料,並且增量資料是不斷進入系統的話,那麼段的概念就有必要了,新增的資料首先在記憶體中進行儲存,然後周期性的產生一個段,持久化到磁碟中提供檢索操作。

  • 段還有一個好處就是當系統是一個分布式的系統的時候,進行索引同步的時候,因為各個段持久化以後就不會變化了,只需要把段拷貝到各個機器,就可以提供檢索服務了,不需要在各個機器上重建索引。

  • 一個段損壞了,並不影響其他段的檢索,只需要從其他機器上將這個段拷貝過來就能正常檢索了,如果只有一個索引的話,一旦索引壞了,就無法提供檢索服務了,需要等把正確索引拷貝過來才行。

一個段都存一些什麼資訊呢?

一個段包含幾個檔案

  • indexname_{segementNumber}.meta 這裡是段的元資訊,包括段中欄位的名稱,類型,也包括段的文檔的起始和終止編號。

  • indexname_{segementNumber}.bt 這裡是段的倒排索引的字典檔案

  • indexname_{segementNumber}.idx 這裡是段的所有欄位的倒排檔案

  • indexname_{segementNumber}.pfl 這裡是段的所有數字正排檔案的資料,同時也包含字串類型資料的位置資訊

  • indexname_{segementNumber}.dtl 這裡是段的字串類型資料的詳情資料

上面的indexname是這個索引的名稱,相當於資料庫中的表名,segmentNumber是段編號,這個編號是系統產生的。

多個段合在一起就是一個完整的索引,檢索的時候實際上是每個段單獨檢索,然後把資料合併起來就是最後的結果集了。

段的構建

下面我們一個一個來說說這些個檔案,看看一堆正排和一堆倒排如何構成一個段的。

一個真正意義上的段的構建由以下幾個步驟來構建,我們以一個實際的例子來說明一下段的構建,比如我們現在索引結構是這樣,這個索引包括三個欄位,分別是姓名(字串),年齡(數字),自我介紹(帶分詞的字串),那麼構建段和索引的時候步驟是這樣的

1.前期準備

首先建立一個段需要先初始化一個段,在初始化段的時候我們實際上已經知道這個段包含哪些欄位,每個欄位的類型。

  • 初始化一個段資訊,包含段所包含的欄位資訊和類型,在這裡就是包含姓名(字串【正排和倒排】),年齡(數字【正排】),自我介紹(帶分詞的字串【正排和倒排】)。

  • 給段一個編號,比如1000。

  • 準備開始接收資料。

2.建立記憶體中的段

記憶體中的段是構建段的第一步,以上述的欄位資訊為例,我們會在記憶體中建立以下幾個資料結構,在這裡我都是使用語言自動的未經處理資料結構

  • 姓名需要建立倒排索引,所以建立一個map<string,list>,key是姓名,value是docid,姓名也要建立正排索引,所以建立一個StringArray[],儲存每條資料的姓名的詳情。

  • 年齡需要建立正排索引,所以建立一個IntegerArray[],儲存每條資料的年齡的詳情。

  • 自我介紹需要建立倒排索引,所以建立一個map<string,list>,key是自我介紹的分詞的term,value是docid,自我介紹也要建立正排索引,所以建立一個StringArray[],儲存每條資料的自我介紹的詳情。

當新增一條資料的時候{"name":"張三","age":18,"introduce":"我喜歡跑步"},首先我們給他一個docid【假如是0】,然後我們把資料分別存放到上面的5個資料結構中,如果再來一條資料{"name":"李四","age":28,"introduce":"我喜歡唱歌"},我們給他一個docid【假如是1】,那麼資料就變成了的樣子

3.將資料結構持久化到磁碟中

這樣,隨著資料的不停匯入,記憶體中的資料結構不斷變化,記憶體段的資料也越來越大,當達到一定閾值的時候(這部分策略以後會說,我把這部分策略放到了引擎層,由引擎來決定什麼時候進行段的持久化),我們將把資料持久化到磁碟中。

進行持久化的過程中

  • 如果是map的資料結構,我們將遍曆整個map,首先將value追加寫到.idx檔案中,然後把key建立B+樹,value是剛剛寫入的idx檔案的位移位置。

  • 如果是IntegerArray,我們遍曆整個數組,然後把資料寫入到pfl檔案中,每個資料佔用8個位元組。

  • 如果是StringArray,我們遍曆整個資料,首先把value追加寫入到dtl檔案中,然後把檔案位移量寫入到pfl檔案中

完成上面的三個步驟,我們的持久化工作就完成了,完成以後資料結構就變成下面的樣子了,大家可以自己腦子裡實現一遍。

4.段構建完成後

段構建完成後,這個段就算完全持久化磁碟中了,不會再變更了,相當於提交到索引系統了,可以進行檢索了。這時候,我們再建立一個段,接著接收新的文檔資料,然後繼續把後續的段持久化到磁碟中。

當檢索的時候,依次檢索每個段,然後將結果集合并起來返回給前端。

段的合并

段建立好了以後,可能需要對段進行合併作業,段的合并方式也很多,最簡單的就是建立一個段,然後遍曆之前的所有資料,從建立立一個段即可,這比較適合於資料量少的情況,因為建立一個段是在記憶體中的,如果之前的資料太多的話,記憶體會撐不住。

還有一種方式是分別將倒排,正排依次合并,這種方式不耗費記憶體,但是比較耗費磁碟的IO,兩種方式大家可以根據自己的業務情境進行選擇,第一種的方法和之前段的構建是一樣的,這裡我們說說第二種方式。

合并倒排檔案

我們使用的B+樹對倒排索引的字典檔案進行儲存,B+樹天然帶排序,那麼合并段的時候實際上就是合并多個B+樹,我們只要使用歸併排序的方式就能合并多個B+樹了。歸併排序不清楚的可以自己去查查,每個B+樹的Key就是待歸併的元素,一邊掃描B+樹一邊構建一個新的B+樹,然後把倒排檔案合并起來形成一個新的idx檔案,倒排檔案就合并完了。

合并正排檔案

合并正排檔案更加簡單,只需要按照欄位依次遍曆每個段的正排檔案,然後一邊遍曆一邊就形成了一個新的正排檔案,遍曆完正排檔案也就合并完了。

合并的方法在FalconIndex/segment/segment.goMergeSegments中有詳細代碼,大家可以參考一下這種最簡單的合并方式。

段的策略

段的策略比較自由,一般也不建議固化到索引中。一般有以下幾種策略可供選擇,具體需要根據自己的商務邏輯來選擇一個合適的段的持久化策略。

  • 如果你的系統是一個一旦建立了索引就不怎麼變化的系統,那麼在做全量索引的時候建立一個段就行了,全量索引構建完了,然後把段持久化到磁碟就行了,如果全量索引量很大,怕記憶體扛不住,那麼可以每10萬條建立一個段,當全量索引完成了以後再將所有的段合并成一個段就行了,段的合并後面會說,合并段基本不佔用什麼記憶體,可以隨時合并,如果有增量資料,每隔一段時間序列化一下段,然後再每隔一段時間將所有非全量資料的段合并一下,那麼系統中就基本上只有一個全量的段和一個增量的段,檢索起來還是非常快的。

  • 如果你的系統是一個即時變化比較大的系統,比如日誌系統,那麼全量索引實際就沒什麼意義了,由於日誌系統的檢索其實即時性要求沒有那麼高,那麼段的策略可以是每新增10萬條資料持久化一個段,沒到10個段將所有段合并成一個段。或者按照時間戳記來合并段,方便剔除老的資料。

  • 如果你的系統是一個即時性要求很高的系統,那麼可以按照時間(比如10秒)持久化一次段,每當系統閒置時候將小的段合并成一個大的段。

總之,段的策略比較自由,完全由引擎層來實現,根據自己的業務情境來選擇重寫一個段合并的策略都是可以的。

段是索引的一部分,也是一個微型的索引,下面一篇我們將會介紹索引層了,索引層介紹玩以後搜尋引擎的資料層就完全結束了,上面就是各種引擎的策略了,有了索引層以後,其實對上你要變成一個搜尋引擎還是要變成一個資料庫,或者變成一個KVDB的資料庫都是可以的,反正基礎的東西不會有太多變化。

好了,如果你想看之前的文章,可以關注我的公眾號哈:)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.