首頁 後端開發 Golang Bleve:如何建立一個快速的搜尋引擎?

Bleve:如何建立一個快速的搜尋引擎?

Jan 03, 2025 am 04:23 AM

Bleve: How to build a rocket-fast search engine?

Go/Golang 是我最喜歡的語言之一;我喜歡極簡主義和它的干淨,它的語法非常緊湊,並且努力讓事情變得簡單(我是KISS 原則的忠實粉絲)。

我最近面臨的主要挑戰之一是建立一個快速的搜尋引擎。當然還有 SOLR 和 ElasticSearch 等選項;兩者都工作得很好並且具有高度可擴展性,但是,我需要簡化搜索,使其更快、更容易部署,幾乎沒有依賴項。

我需要進行足夠的最佳化才能快速返回結果,以便可以對它們進行重新排名。雖然 C/Rust 可能很適合這個,但我更重視開發速度和生產力。我認為 Golang 是兩全其美的。

在本文中,我將透過一個簡單的範例來介紹如何使用 Go 建立自己的搜尋引擎,你會驚訝地發現:它並沒有您想像的那麼複雜。

Golang:Python 的增強版

我不知道為什麼,但 Golang 在某種程度上感覺像 Python。文法很容易掌握,也許是因為到處都沒有分號和括號,或是沒有醜陋的 try-catch 語句。也許這是很棒的 Go 格式化程序,我不知道。

無論如何,由於 Golang 產生一個獨立的二進位文件,因此部署到任何生產伺服器都非常容易。您只需“建置”並交換可執行檔即可。

這正是我所需要的。

你有布萊夫嗎?

不,這不是打字錯誤嗎? Bleve 是一個功能強大、易於使用且非常靈活的 Golang 搜尋庫。

身為 Go 開發人員,您通常會像躲避瘟疫一樣避免使用 3rd 方包;有時使用第三方軟體包是有意義的。 Bleve 速度快、設計精良,並提供足夠的價值來證明使用它的合理性。

此外,這就是我「Bleve」的原因:

  • 獨立,Golang 的一大優點是單一二進位文件,所以我想保持這種感覺,不需要外部資料庫或服務來儲存和查詢文件。 Bleve 與 Sqlite 類似,在記憶體中運行並寫入磁碟。

  • 易於擴充。由於它只是 Go 程式碼,因此我可以根據需要輕鬆調整庫或在我的程式碼庫中擴展它。

  • 快速:1000 萬個文件的搜尋結果只需 50-100 毫秒,其中包括過濾。

  • 分面:如果沒有一定程度的分面支持,您就無法建立現代搜尋引擎。 Bleve 完全支援常見的構面類型:例如範圍或簡單類別計數。

  • 快速索引:Bleve 比 SOLR 稍慢。 SOLR 可以在 30 分鐘內索引 1000 萬個文檔,而 Bleve 需要一個多小時,但一個小時左右仍然相當不錯,速度足以滿足我的需求。

  • 良好的品質結果。 Bleve 在關鍵字結果方面表現出色,而有些語意類型搜尋在 Bleve 中也非常有效。

  • 快速啟動:如果您需要重新啟動或部署更新,只需幾毫秒即可重新啟動 Bleve。在記憶體中重建索引時不會阻塞讀取,因此重新啟動後幾毫秒內即可搜尋索引,不會出現中斷。

設定索引?

在 Bleve 中,「索引」可以被視為資料庫表或集合(NoSQL)。與常規 SQL 表不同,您不需要指定每一列,基本上可以在大多數用例中使用預設架構。

要初始化 Bleve 索引,您可以執行下列操作:

mappings := bleve.NewIndexMapping()
index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil)
if err != nil {
    log.Fatal(err)
}
登入後複製
登入後複製

Bleve 支援幾種不同的索引類型,但經過多次擺弄後我發現「scorch」索引類型可以為您提供最佳效能。如果您不傳入最後 3 個參數,Bleve 將預設為 BoltDB。

新增文件

為 Bleve 新增文件非常簡單。您基本上可以在索引中儲存任何類型的結構:

type Book struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Genre string `json:"genre"`
}

b := Book{
    ID:    1234,
    Name:  "Some creative title",
    Genre: "Young Adult",
}
idStr := fmt.Sprintf("%d", b.ID)
// index(string, interface{})
index.index(idStr, b)
登入後複製
登入後複製

如果您要索引大量文檔,最好使用批次:

// You would also want to check if the batch exists already
// - so that you don't recreate it.
batch := index.NewBatch()
if batch.Size() >= 1000 {
    err := index.Batch(batch)
    if err != nil {
        // failed, try again or log etc...
    }
    batch = index.NewBatch()
} else {
    batch.index(idStr, b)
}
登入後複製
登入後複製

正如您所注意到的,使用「index.NewBatch」可以簡化諸如批次記錄並將其寫入索引之類的複雜任務,該任務會建立一個容器來暫時索引文件。

此後,您只需在循環時檢查大小,並在達到批量大小限制後刷新索引。

搜尋索引

Bleve 公開了多個不同的搜尋查詢解析器,您可以根據您的搜尋需求進行選擇。為了使本文簡短而有趣,我將使用標準查詢字串解析器。

searchParser := bleve.NewQueryStringQuery("chicken reciepe books")
maxPerPage := 50
ofsset := 0
searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false)
// By default bleve returns just the ID, here we specify
// - all the other fields we would like to return.
searchRequest.Fields = []string{"id", "name", "genre"}
searchResults, err := index.Search(searchResult)
登入後複製
登入後複製

只需這幾行,您現在就擁有了一個強大的搜尋引擎,可以以較低的記憶體和資源佔用提供良好的結果。

這是搜尋結果的 JSON 表示,「hits」將包含符合的文件:

{
    "status": {
        "total": 5,
        "failed": 0,
        "successful": 5
    },
    "request": {},
    "hits": [],
    "total_hits": 19749,
    "max_score": 2.221337297308545,
    "took": 99039137,
    "facets": null
}
登入後複製
登入後複製

刻面

如前所述,Bleve 提供開箱即用的全面分面支持,而無需在您的架構中進行設定。以《流派》一書為例,您可以執行以下操作:

//... build searchRequest -- see previous section.
// Add facets
genreFacet := bleve.NewFacetRequest("genre", 50)
searchRequest.AddFacet("genre", genreFacet)
searchResults, err := index.Search(searchResult)
登入後複製
登入後複製

我們只用 2 行程式碼擴充了之前的 searchRequest。 「NewFacetRequest」接受 2 個參數:

  • 欄位:索引中要分面的欄位(字串)。

  • 大小:要計數的條目數(整數)。因此,在我們的範例中,它只會計算前 50 個流派。

以上內容現在將填入我們搜尋結果中的「面向」。

接下來,我們只需將我們的方面添加到搜尋請求中即可。它接受“方面名稱”和實際方面。 「Facet name」是您將在我們的搜尋結果中找到此結果集的「鍵」。

進階查詢和過濾

雖然“QueryStringQuery”解析器可以為您帶來相當多的幫助;有時您需要更複雜的查詢,例如“一個必須匹配”,您希望將搜尋字詞與多個欄位進行匹配,並傳回結果,只要至少有一個字段匹配即可。

您可以使用「Disjunction」和「Conjunction」查詢類型來完成此操作。

  • 聯合查詢:基本上,它允許您將多個查詢連結在一起形成一個巨大的查詢。所有子查詢必須至少符合一個文件。

  • 析取查詢:這將允許您執行上面提到的「一個必須匹配」查詢。您可以傳入 x 數量的查詢,並設定必須符合至少一個文件的子查詢數量。

析取查詢範例:

mappings := bleve.NewIndexMapping()
index, err = bleve.NewUsing("/some/path/index.bleve", mappings, "scorch", "scorch", nil)
if err != nil {
    log.Fatal(err)
}
登入後複製
登入後複製

與我們之前使用「searchParser」的方式類似,我們現在可以將「析取查詢」傳遞到「searchRequest」的建構子中。

雖然不完全相同,但類似以下 SQL:

type Book struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Genre string `json:"genre"`
}

b := Book{
    ID:    1234,
    Name:  "Some creative title",
    Genre: "Young Adult",
}
idStr := fmt.Sprintf("%d", b.ID)
// index(string, interface{})
index.index(idStr, b)
登入後複製
登入後複製

您也可以透過設定「query.Fuzziness=[0 or 1 or 2]」來調整搜尋的模糊程度

連線查詢範例:

// You would also want to check if the batch exists already
// - so that you don't recreate it.
batch := index.NewBatch()
if batch.Size() >= 1000 {
    err := index.Batch(batch)
    if err != nil {
        // failed, try again or log etc...
    }
    batch = index.NewBatch()
} else {
    batch.index(idStr, b)
}
登入後複製
登入後複製

您會注意到語法非常相似,您基本上可以互換使用“Conjunction”和“Disjunction”查詢。

這將類似於 SQL 中的以下內容:

searchParser := bleve.NewQueryStringQuery("chicken reciepe books")
maxPerPage := 50
ofsset := 0
searchRequest := bleve.NewSearchRequestOptions(searchParser, maxPerPage, offset, false)
// By default bleve returns just the ID, here we specify
// - all the other fields we would like to return.
searchRequest.Fields = []string{"id", "name", "genre"}
searchResults, err := index.Search(searchResult)
登入後複製
登入後複製

總結一下;當您希望所有子查詢符合至少一個文件時,請使用「聯合查詢」;當您希望符合至少一個子查詢但不一定符合所有子查詢時,請使用「析取查詢」。

分片

如果您遇到速度問題,Bleve 也可以將資料分佈在多個索引分片上,然後在一個請求中查詢這些分片,例如:

{
    "status": {
        "total": 5,
        "failed": 0,
        "successful": 5
    },
    "request": {},
    "hits": [],
    "total_hits": 19749,
    "max_score": 2.221337297308545,
    "took": 99039137,
    "facets": null
}
登入後複製
登入後複製

分片可能會變得相當複雜,但正如您在上面看到的,Bleve 消除了很多痛苦,因為它會自動「合併」所有索引並在它們之間進行搜索,然後在一個結果集中返回結果,就像您搜尋一樣單一索引。

我一直在使用分片來搜尋 100 個分片。整個搜尋過程平均只需 100-200 毫秒即可完成。

您可以如下建立分片:

//... build searchRequest -- see previous section.
// Add facets
genreFacet := bleve.NewFacetRequest("genre", 50)
searchRequest.AddFacet("genre", genreFacet)
searchResults, err := index.Search(searchResult)
登入後複製
登入後複製

只要確保為每個文檔建立唯一的 ID,或採用某種可預測的方式新增和更新文檔,而不會弄亂索引。

執行此操作的簡單方法是將包含分片名稱的前綴儲存在來源資料庫中或從您取得文件的任何位置。這樣,每次您嘗試插入或更新時,您都會尋找“前綴”,它會告訴您在哪個分片上呼叫“.index”。

說到更新,只需呼叫「index.index(idstr, struct)」即可更新現有文件。

結論

僅使用上面的這種基本搜尋技術並將其置於 GIN 或標準 Go HTTP 伺服器後面,您就可以建立非常強大的搜尋 API 並服務數百萬個請求,而無需推出複雜的基礎設施。

但有一點需要注意;但是,Bleve 不支援複製,因為您可以將其包裝在 API 中。只需有一個 cron 作業,從您的來源讀取數據,然後使用 goroutine 將更新「爆炸」到您的所有 Bleve 伺服器。

或者,您可以將寫入磁碟鎖定幾秒鐘,然後將資料「rsync」到從屬索引,儘管我不建議這樣做,因為您可能還需要每次重新啟動 go 二進位檔案.

以上是Bleve:如何建立一個快速的搜尋引擎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1663
14
CakePHP 教程
1420
52
Laravel 教程
1315
25
PHP教程
1266
29
C# 教程
1239
24
Golang的目的:建立高效且可擴展的系統 Golang的目的:建立高效且可擴展的系統 Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

Golang和C:並發與原始速度 Golang和C:並發與原始速度 Apr 21, 2025 am 12:16 AM

Golang在並發性上優於C ,而C 在原始速度上優於Golang。 1)Golang通過goroutine和channel實現高效並發,適合處理大量並發任務。 2)C 通過編譯器優化和標準庫,提供接近硬件的高性能,適合需要極致優化的應用。

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

Golang的影響:速度,效率和簡單性 Golang的影響:速度,效率和簡單性 Apr 14, 2025 am 12:11 AM

goimpactsdevelopmentpositationality throughspeed,效率和模擬性。 1)速度:gocompilesquicklyandrunseff,IdealforlargeProjects.2)效率:效率:ITScomprehenSevestAndardArdardArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdArdEcceSteral Depentencies,增強的Depleflovelmentimency.3)簡單性。

Golang vs. Python:主要差異和相似之處 Golang vs. Python:主要差異和相似之處 Apr 17, 2025 am 12:15 AM

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。Golang以其并发模型和高效性能著称,Python则以简洁语法和丰富库生态系统著称。

Golang和C:性能的權衡 Golang和C:性能的權衡 Apr 17, 2025 am 12:18 AM

Golang和C 在性能上的差異主要體現在內存管理、編譯優化和運行時效率等方面。 1)Golang的垃圾回收機制方便但可能影響性能,2)C 的手動內存管理和編譯器優化在遞歸計算中表現更為高效。

表演競賽:Golang vs.C 表演競賽:Golang vs.C Apr 16, 2025 am 12:07 AM

Golang和C 在性能競賽中的表現各有優勢:1)Golang適合高並發和快速開發,2)C 提供更高性能和細粒度控制。選擇應基於項目需求和團隊技術棧。

C和Golang:表演至關重要時 C和Golang:表演至關重要時 Apr 13, 2025 am 12:11 AM

C 更適合需要直接控制硬件資源和高性能優化的場景,而Golang更適合需要快速開發和高並發處理的場景。 1.C 的優勢在於其接近硬件的特性和高度的優化能力,適合遊戲開發等高性能需求。 2.Golang的優勢在於其簡潔的語法和天然的並發支持,適合高並發服務開發。

See all articles