悟空(wukong)搜索引擎源代码阅读(待续)

来源:互联网 发布:bae怎么域名备案 编辑:程序博客网 时间:2024/05/21 16:59

悟空(wukong)搜索引擎源代码阅读

最近为了了解搜索引擎的一些知识,阅读了wukong搜索的一些源码,在这里记录一下。项目地址在这里

整个的处理流程如下:

wukong-framework

在项目中主要有一下几个目录:

目录名 作用 core data docs engines examples testdata Types utils

示例代码如下:

package mainimport (    "github.com/huichen/wukong/engine"    "github.com/huichen/wukong/types"    "log")var (// searcher是协程安全的    searcher = engine.Engine{})func main() {    // 初始化    searcher.Init(types.EngineInitOptions{        SegmenterDictionaries: "./data/dictionary.txt"})    defer searcher.Close()    // 将文档加入索引    searcher.IndexDocument(0, types.DocumentIndexData{Content: "此次百度收购将成中国互联网最大并购"})    searcher.IndexDocument(1, types.DocumentIndexData{Content: "百度宣布拟全资收购91无线业务"})    searcher.IndexDocument(2, types.DocumentIndexData{Content: "百度是中国最大的搜索引擎"})    // 等待索引刷新完毕    searcher.FlushIndex()    // 搜索输出格式见types.SearchResponse结构体    res := searcher.Search(types.SearchRequest{Text:"百度中国"})    log.Printf("num=%d ", res.NumDocs)    for _, d := range res.Docs {        log.Printf("docId=%d", d.DocId)        log.Print("\tscore:", d.Scores)        log.Print("\tTokenLocations:", d.TokenLocations)        log.Print("\tTokenSnippetLocations:", d.TokenSnippetLocations)    }}

可以看到整个处理流程为:

  • [ ] 建立一个searcher
  • [ ] 进行初始化
  • [ ] 加入文档进行索引
  • [ ] 搜索

下面按照该流程,结合代码目录里的代码介绍各个的作用。

初始化工作

初始化在engines中的engine.go中进行,包括:

初始化通道

索引器、排序器、分词器(启动NumShards个)初始化:

// 初始化索引器和排序器    for shard := 0; shard < options.NumShards; shard++ {        engine.indexers = append(engine.indexers, core.Indexer{})        engine.indexers[shard].Init(*options.IndexerInitOptions)        engine.rankers = append(engine.rankers, core.Ranker{})        engine.rankers[shard].Init()    }// 载入分词器词典               engine.segmenter.LoadDictionary(options.SegmenterDictionaries)// 初始化停用词engine.stopTokens.Init(options.StopTokenFile)

索引器通道、排序器通道、分词器通道初始化:

分词器通道
/ 初始化分词器通道    engine.segmenterChannel = make(    chan segmenterRequest, options.NumSegmenterThreads)

启动分词器通道,传送的是segmenterRequest,位于engine.segementer_worker.go中。

索引器通道
// 初始化索引器通道    engine.indexerAddDocumentChannels = make(        []chan indexerAddDocumentRequest, options.NumShards)    engine.indexerLookupChannels = make(        []chan indexerLookupRequest, options.NumShards)    for shard := 0; shard < options.NumShards; shard++ {        engine.indexerAddDocumentChannels[shard] = make(            chan indexerAddDocumentRequest,            options.IndexerBufferLength)        engine.indexerLookupChannels[shard] = make(            chan indexerLookupRequest,            options.IndexerBufferLength)    }

启动了索引器的两个通道序列:indexerAddDocumentChannels、indexerLookupChannels

indexerAddDocumentChannels通道中传送的是indexerAddDocumentRequest用来发送增加索引文档的请求,indexerAddDocumentRequest在engine.indexer_worker.go里面定义。

indexerLookupChannels通道中传送的是indexerLookupRequest用来查找文档,也是在engine.indexer_worker.go中进行定义。

排序器通道
    // 初始化排序器通道    engine.rankerAddScoringFieldsChannels = make(        []chan rankerAddScoringFieldsRequest, options.NumShards)    engine.rankerRankChannels = make(        []chan rankerRankRequest, options.NumShards)    engine.rankerRemoveScoringFieldsChannels = make(        []chan rankerRemoveScoringFieldsRequest, options.NumShards)    for shard := 0; shard < options.NumShards; shard++ {        engine.rankerAddScoringFieldsChannels[shard] = make(            chan rankerAddScoringFieldsRequest,            options.RankerBufferLength)        engine.rankerRankChannels[shard] = make(            chan rankerRankRequest,            options.RankerBufferLength)        engine.rankerRemoveScoringFieldsChannels[shard] = make(            chan rankerRemoveScoringFieldsRequest,            options.RankerBufferLength)    }

启动了排序器的三个通道序列:rankerAddScoringFieldsChannels、rankerRankChannels、rankerRemoveScoringFieldsChannels

rankerAddScoringFieldsChannels用来传送rankerAddScoringFieldsRequest请求,该请求位于engine.ranker_worker.go中

rankerRankChannels用来传送rankerRankRequest请求,该请求位于engine.ranker_worker.go中

rankerRemoveScoringFieldsChannels用来传送rankerRemoveScoringFieldsRequest请求,该请求位于engine.ranker_worker.go中

然后就是启动持久化索引通道。

启动

// 启动分词器    for iThread := 0; iThread < options.NumSegmenterThreads; iThread++ {        go engine.segmenterWorker()    }    // 启动索引器和排序器    for shard := 0; shard < options.NumShards; shard++ {        go engine.indexerAddDocumentWorker(shard)        go engine.rankerAddScoringFieldsWorker(shard)        go engine.rankerRemoveScoringFieldsWorker(shard)        for i := 0; i < options.NumIndexerThreadsPerShard; i++ {            go engine.indexerLookupWorker(shard)        }        for i := 0; i < options.NumRankerThreadsPerShard; i++ {            go engine.rankerRankWorker(shard)        }    }

将初始化的一些通道进行启动。

加入文档索引

添加索引文档

在engine.engine.go中IndexDocument进行文档的索引。

IndexDocument主要调用internalIndexDocument,

/ 将文档加入索引//// 输入参数://  docId   标识文档编号,必须唯一//  data    见DocumentIndexData注释//// 注意://  1. 这个函数是线程安全的,请尽可能并发调用以提高索引速度//  2. 这个函数调用是非同步的,也就是说在函数返回时有可能文档还没有加入索引中,因此//  如果立刻调用Search可能无法查询到这个文档。强制刷新索引请调用FlushIndex函数。func (engine *Engine) IndexDocument(docId uint64, data types.DocumentIndexData) {    engine.internalIndexDocument(docId, data)}func (engine *Engine) internalIndexDocument(docId uint64, data types.DocumentIndexData) {    if !engine.initialized {        log.Fatal("必须先初始化引擎")    }    atomic.AddUint64(&engine.numIndexingRequests, 1)    hash := murmur.Murmur3([]byte(fmt.Sprint("%d%s", docId, data.Content)))    engine.segmenterChannel <- segmenterRequest{        docId: docId, hash: hash, data: data}}

将文档加入到分词器通道中。

删除文档

// 将文档从索引中删除// 输入参数:// docId    标识文档编号,必须唯一// 注意:这个函数仅从排序器中删除文档的自定义评分字段,索引器不会发生变化。所以你的自定义评分字段必须能够区别评分字段为nil的情况,并将其从排序结果中删除。
for shard := 0; shard < engine.initOptions.NumShards; shard++ {        engine.rankerRemoveScoringFieldsChannels[shard] <- rankerRemoveScoringFieldsRequest{docId: docId}    }

删除文档中,实际上只是删除了排序器中文档的评分字段

等待索引完毕

// 阻塞等待直到所有索引添加完毕

func (engine *Engine) FlushIndex()

搜索

搜索函数的定义如下:

func (engine *Engine) Search(request types.SearchRequest) (output types.SearchResponse)

输入类型是types.searchRequest类型,该类型主要包括以下字段:

    // 搜索的短语(必须是UTF-8格式),会被分词    // 当值为空字符串时关键词会从下面的Tokens读入    Text string    // 关键词(必须是UTF-8格式),当Text不为空时优先使用Text    // 通常你不需要自己指定关键词,除非你运行自己的分词程序    Tokens []string    // 文档标签(必须是UTF-8格式),标签不存在文档文本中,但也属于搜索键的一种    Labels []string    // 当不为空时,仅从这些文档中搜索    DocIds []uint64    // 排序选项    RankOptions *RankOptions    // 超时,单位毫秒(千分之一秒)。此值小于等于零时不设超时。    // 搜索超时的情况下仍有可能返回部分排序结果。    Timeout int

这些字段至少需要定义一个tokens

定义排序准则

在函数内部,首先需要定义排序的准则:

type RankOptions struct {    // 文档的评分规则,值为nil时使用Engine初始化时设定的规则    ScoringCriteria ScoringCriteria    // 默认情况下(ReverseOrder=false)按照分数从大到小排序,否则从小到大排序    ReverseOrder bool    // 从第几条结果开始输出    OutputOffset int    // 最大输出的搜索结果数,为0时无限制    MaxOutputs int}
    var rankOptions types.RankOptions    if request.RankOptions == nil {        rankOptions = *engine.initOptions.DefaultRankOptions    } else {        rankOptions = *request.RankOptions    }    if rankOptions.ScoringCriteria == nil {        rankOptions.ScoringCriteria = engine.initOptions.DefaultRankOptions.ScoringCriteria    }

排序的准则如果在request中定义了,就用其中的;要么由引擎的初始化选项的默认排序准则定义。

收集关键词

如果输入了文档,就对文档进行分词,并去掉其中的停用词;

如果没有,就用输入数据定义的关键词;

生成查找请求,发送给索引器,然后从排序器中取数据

排序,输出

0 0
原创粉丝点击