• 悟空分词的搜索和排序源码分析之——搜索


    转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/

    搜索过程分析

    下面我们来分析一下搜索的过程。首先构造一个SearchRequest对象。一般情况下只需提供SearchRequest.Text即可。

    type SearchRequest struct {
    	// 搜索的短语(必须是UTF-8格式),会被分词
    	// 当值为空字符串时关键词会从下面的Tokens读入
    	Text string
    
    	// 关键词(必须是UTF-8格式),当Text不为空时优先使用Text
    	// 通常你不需要自己指定关键词,除非你运行自己的分词程序
    	Tokens []string
    
    	// 文档标签(必须是UTF-8格式),标签不存在文档文本中,但也属于搜索键的一种
    	Labels []string
    
    	// 当不为nil时,仅从这些DocIds包含的键中搜索(忽略值)
    	DocIds map[uint64]bool
    
    	// 排序选项
    	RankOptions *RankOptions
    
    	// 超时,单位毫秒(千分之一秒)。此值小于等于零时不设超时。
    	// 搜索超时的情况下仍有可能返回部分排序结果。
    	Timeout int
    
    	// 设为true时仅统计搜索到的文档个数,不返回具体的文档
    	CountDocsOnly bool
    
    	// 不排序,对于可在引擎外部(比如客户端)排序情况适用
    	// 对返回文档很多的情况打开此选项可以有效节省时间
    	Orderless bool
    }
    

    从本文一开始那段示例代码的搜索语句读起:searcher.Search(types.SearchRequest{Text:"百度中国"})。进入到 Search 函数内部,其逻辑如下:

    设置一些搜索选项

    例如排序选项RankOptions, 分数计算条件ScoringCriteria等等

    将搜索词进行分词

    	// 收集关键词
    	tokens := []string{}
    	if request.Text != "" {
    		querySegments := engine.segmenter.Segment([]byte(request.Text))
    		for _, s := range querySegments {
    			token := s.Token().Text()
    			if !engine.stopTokens.IsStopToken(token) {
    				tokens = append(tokens, s.Token().Text())
    			}
    		}
    	} else {
    		for _, t := range request.Tokens {
    			tokens = append(tokens, t)
    		}
    	}
    
    

    这里的”百度中国”会分词得到两个词:百度 和中国

    向索引器发送查找请求

    	// 建立排序器返回的通信通道
    	rankerReturnChannel := make(
    		chan rankerReturnRequest, engine.initOptions.NumShards)
    
    	// 生成查找请求
    	lookupRequest := indexerLookupRequest{
    		countDocsOnly:       request.CountDocsOnly,
    		tokens:              tokens,
    		labels:              request.Labels,
    		docIds:              request.DocIds,
    		options:             rankOptions,
    		rankerReturnChannel: rankerReturnChannel,
    		orderless:           request.Orderless,
    	}
    
    	// 向索引器发送查找请求
    	for shard := 0; shard < engine.initOptions.NumShards; shard++ {
    		engine.indexerLookupChannels[shard] <- lookupRequest
    	}
    

    这里是否可以进行优化? 1) 只向特定的shard分发请求,避免无谓的indexer查找过程。2)rankerReturnChannel是否不用每次都创建新的?

    读取索引器的返回结果然后排序

    上面已经建立了结果的返回通道rankerReturnChannel,直接从个channel中读取返回数据,并加入到数组rankOutput中。 注意,如果设置了超时,就在超时之前能读取多少就读多少。 然后调用排序算法进行排序。排序算法直接调用Golang自带的sort包的排序算法。

    下面我们深入到索引器,看看索引器是如何进行搜索的。其核心代码在这里func (engine *Engine) indexerLookupWorker(shard int),它的主逻辑是一个死循环,不断的从engine.indexerLookupChannels[shard]读取搜索请求。

    针对每一个搜索请求,会将请求分发到索引器去,调用func (indexer *Indexer) Lookup(tokens []string, labels []string, docIds map[uint64]bool, countDocsOnly bool) (docs []types.IndexedDocument, numDocs int)方法。其主要逻辑如下:

    1. 将分词和标签合并在一起进行搜索
    2. 合并搜索到的docId,并进行初步排序,将docId大的排在前面(实际上是认为docId越大,时间越近,时效性越好)
    3. 然后进行排序,BM25算法
    4. 最后返回数据
  • 相关阅读:
    反射模块与模块之间的通信
    WCF传输协议
    IIs7 报错
    MVC3 ActionResult 返回类型
    三条数据 判断其中最大与最小
    dos批处理命令详解
    十拿九稳过倒桩之(倒桩技巧)
    九项路考(1)铁饼神功
    山鸽子
    九项路考(2)
  • 原文地址:https://www.cnblogs.com/bonelee/p/6341334.html
Copyright © 2020-2023  润新知