分佈式搜索引擎,你真的懂嗎?

一、分佈式搜索引擎簡介

(一) 概念

分佈式搜索引擎通過在多臺服務器上分配索引和搜索負載, 實現索引和搜索吞吐能力的橫向擴展。

主要特徵:

  • 索引和搜索負載分佈在多臺服務器

  • 支持大規模數據和訪問量

(二) 與集中式搜索引擎區別

集中式搜索引擎在單個節點上完成全部工作, 硬件資源限制其擴展能力。

分佈式搜索引擎通過分佈式計算技術, 實現可橫向擴展的大規模搜索引擎。

(三) 優勢

  • 處理更多文檔, 支持更大訪問量

  • 更高的查詢吞吐量

  • 更好的容錯性

  • 更易於擴展

二、分佈式搜索引擎架構

典型分佈式搜索引擎由以下組件組成:

(一) 索引器

文檔分片存儲和倒排索引構建。支持增量索引。

(二) 查詢引擎

接收查詢請求, 創建查詢計劃, 聚合並返回結果。

(三)DocServer

文檔服務器。存儲原始文檔, 併爲查詢引擎提供文檔獲取。

(四) 協調節點

協調索引器和查詢引擎節點, 包括文檔分配、擴縮容等。

三、分佈式索引技術

分佈式搜索引擎的核心是將索引分佈到多臺服務器上, 關鍵技術有:

(一) 倒排索引

倒排索引通過詞到文檔列表的映射, 支持快速查詢。

type Index struct {
    Fields []string
    DocMap map[string][]int //詞到文檔列表映射
}
func IndexDoc(doc *Document) {
    index := &Index{}
    //分詞 
    tokens := Tokenize(doc.Content)  
    //構建倒排索引
    for _, token := range tokens {
        index.DocMap[token] = append(index.DocMap[token], doc.ID)  
    }
}
func SearchQuery(query) {
    //解析查詢詞
    tokens := Tokenize(query)   
    //交集算法獲取共現文檔
    //返回文檔id列表
    return IntersecForTokens(index.DocMap, tokens)
}

(二) 中文分詞

中文搜索需要用分詞算法切分句子。

//基於詞典的中文分詞
import "github.com/go-ego/gse"
func Tokenize(text string) []string  {
    segmenter := gse.New("dict.txt")
    // 分詞
    tokens := segmenter.Segment([]byte(text))
    return tokens
}

(三) 文檔分配

將文檔分佈到不同索引器服務器。常用策略:

  • Hash 取模: docId % N

  • 一致性 Hash: 動態調整, 平衡負載

  • 主詞項散列: 根據主詞項 Hash 值分配

//一致性hash分配
import "github.com/cch123/elastics/hashring"
var nodes = []string{"server1","server2"...}
ring := hashring.New(nodes)
func DistributeDoc(doc *Document) {
    //根據id選擇節點
    node = ring.GetNode(doc.id)  
    //索引文檔
    indexDoc(doc, node)  
}

四、查詢優化

(一) 緩存

熱點詞頻查詢可緩存在內存, 避免搜索計算。

import "github.com/patrickmn/go-cache"
type QueryCache struct {
  cache *cache.Cache //詞到結果緩存
}
//獲取查詢結果  
func (c *QueryCache)Search(query) {
    if c.cache.Has(query) {
        return cache.Get(query) 
    }
    //緩存未命中,搜索索引
    result = indexSearch(query) 
    c.cache.Set(query, result)
    return result
}

(二) 查詢重寫

相關查詢可重寫爲通用詞, 擴大命中。

func QueryRewrite(query string) string {
    rewriteRules :=map[string]string {  
        "寶馬" : "汽車",
        "招商銀行" : "銀行"
    }
    if rewrite, ok := rules[query]; ok {
        return rewrite
    }
    return query
}

(三) 相關性排序

匹配詞頻 / 位置和文檔權重相結合, 按相關度排序。

type RankedResult struct {
    DocId int  
    Score float32 //相關度  
}
func Rank(results []int, tokens []string) {
    scoredList := make([]RankedResult,0) 
    for _,docId := range results {
        score = 0
        if frequency[docId][token] > 0 {
            score ++
        } 
        //詞出現在標題權重更高 
        if postion[docId][token] == "title" {
            score += 5
        }
        //文檔自身權重分
        score += docWeight[docId]
        scored := RankedResult(docId, score)
        scoredList = append(scoredList, scored)
    } 
    //按相關度降序排序並返回 
    sort.Sort(scoredList)
    return scoredList
}

五、Go 實現分佈式搜索引擎

Go 語言用於分佈式搜索引擎主要有:

(一) 索引器

  • 倒排索引構建

  • 文檔存儲

  • 定時批量創建新分割索引

(二) 查詢引擎

  • 通過索引器節點聚合查詢結果

  • 結果解析和排序

  • 提供查詢接口服務

(三)Coordinator

  • 文檔分配和路由

  • 負載監控

  • 添加刪除節點

(四)RPC 框架

indexing 和查詢服務通過 RPC 框架暴露, 典型選型:

  • gRPC: 定製化 RPC, 性能好

  • Go kit: 組件化 RPC, 易開發

  • Http REST: 易與其他語言集成

六、Go 實現分佈式搜索引擎案例

Go 生態存在多個成熟分佈式搜索引擎實現。

(一)Bleve

倒排索引爲核心, 支持文本和地理位置查詢。

  • 輕量級文檔存儲

  • 支持地理位置查詢

  • 倒排索引核心算法

// bleve 建立索引示例
import (
        "github.com/blevesearch/bleve"
        "github.com/blevesearch/bleve/index/upsidedown"
)
// 創建倒排索引實例
mapping := bleve.NewIndexMapping() 
index, _ := bleve.New("example.bleve",mapping)  
//索引文檔
data := []byte("Bleve builds indexes for text search.")
index.Index(1,data)
// Term查詢
query := bleve.NewTermQuery("Bleve")
request := bleve.NewSearchRequest(query)
result,_:= index.Search(request)

(二)Riot

基於 Lucene 的高可擴展 Go 搜索引擎, 特點:

  • 副本機制保證高可用

  • 文檔自動負載均衡

  • 索引 / 查詢分離

// riot 分佈索引文檔
import (  
    "github.com/go-ego/riot"
    "github.com/go-ego/riot/types"
)
var crawler = riot.New("zh")
crawler.Index(types.DocData{
    Id:   1, 
    Text: "文檔內容",
})
//搜索查詢
import (
    "github.com/go-ego/riot"
    "github.com/go-ego/riot/types"
)
var searcher = riot.New("zh")
searcher.Search(types.SearchRequest{
    Text: "關鍵詞", 
    RankOpts: &types.RankOpts{  
        OutputOffset: 5,
    },
})

(三)Go-ElasticSearch

Go 客戶端訪問 ElasticSearch 集羣。

// 連接 ElasticSearch
import (
    "github.com/olivere/elastic"
)
client, err := elastic.NewClient(elastic.SetURL("http://ip:port"))
// 索引文檔
client.Index().
    Index("twitter").
    Type("tweet").
    Id("1").
    BodyJson(tweet).
    Do(context.Background())
//搜索
searchResult, err := client.Search().
    Index("twitter").  
    Query(elastic.NewTermQuery("user", "kimchy")).  
    From(0).Size(10).
    Pretty(true).
    Do(context.Background())

七、總結

分佈式搜索引擎可以應對更大規模的數據和訪問。Go 語言正逐步成爲構建分佈式搜索引擎的理想選擇。

在未來,Go 語言將有機會進一步發揮效能, 助力搜索技術產業化進程。分佈式搜索引擎也將藉助雲原生技術,實現更大規模的應用。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ScUrCB5rgNd-DmNH_SKSEQ