wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储
searcher.IndexDocument(0, types.DocumentIndexData{Content: "此次百度收购将成中国互联网最大并购"})
engine.go中的源码实现:
// 将文档加入索引 // // 输入参数: // docId 标识文档编号,必须唯一 // data 见DocumentIndexData注释 // // 注意: // 1. 这个函数是线程安全的,请尽可能并发调用以提高索引速度 // 2. 这个函数调用是非同步的,也就是说在函数返回时有可能文档还没有加入索引中,因此 // 如果立刻调用Search可能无法查询到这个文档。强制刷新索引请调用FlushIndex函数。 func (engine *Engine) IndexDocument(docId uint64, data types.DocumentIndexData) {if !engine.initialized {log.Fatal("必须先初始化引擎")}atomic.AddUint64(&engine.numIndexingRequests, 1)shard := int(murmur.Murmur3([]byte(fmt.Sprint("%d", docId))) % uint32(engine.initOptions.NumShards))engine.segmenterChannel <- segmenterRequest{docId: docId, shard: shard, data: data} }
而其中:
engine.segmenterChannel <- segmenterRequest{docId: docId, shard: shard, data: data}
将请求发送给segmenterChannel,其定义:
// 建立分词器使用的通信通道segmenterChannel chan segmenterRequest
而接受请求处理的代码在segmenter_worker.go里:
func (engine *Engine) segmenterWorker() {for {request := <-engine.segmenterChannel //关键tokensMap := make(map[string][]int)numTokens := 0if !engine.initOptions.NotUsingSegmenter && request.data.Content != "" {// 当文档正文不为空时,优先从内容分词中得到关键词segments := engine.segmenter.Segment([]byte(request.data.Content))for _, segment := range segments {token := segment.Token().Text()if !engine.stopTokens.IsStopToken(token) {tokensMap[token] = append(tokensMap[token], segment.Start())}}numTokens = len(segments)} else {// 否则载入用户输入的关键词for _, t := range request.data.Tokens {if !engine.stopTokens.IsStopToken(t.Text) {tokensMap[t.Text] = t.Locations}}numTokens = len(request.data.Tokens)}// 加入非分词的文档标签for _, label := range request.data.Labels {if !engine.initOptions.NotUsingSegmenter {if !engine.stopTokens.IsStopToken(label) {tokensMap[label] = []int{}}} else {tokensMap[label] = []int{}}}indexerRequest := indexerAddDocumentRequest{document: &types.DocumentIndex{DocId: request.docId,TokenLength: float32(numTokens),Keywords: make([]types.KeywordIndex, len(tokensMap)),},}iTokens := 0for k, v := range tokensMap {indexerRequest.document.Keywords[iTokens] = types.KeywordIndex{Text: k,// 非分词标注的词频设置为0,不参与tf-idf计算 Frequency: float32(len(v)),Starts: v}iTokens++}var dealDocInfoChan = make(chan bool, 1)indexerRequest.dealDocInfoChan = dealDocInfoChanengine.indexerAddDocumentChannels[request.shard] <- indexerRequestrankerRequest := rankerAddDocRequest{docId: request.docId,fields: request.data.Fields,dealDocInfoChan: dealDocInfoChan,}engine.rankerAddDocChannels[request.shard] <- rankerRequest} }
上面代码的作用就是在统计词频和单词位置(注意:tag也是作为搜索的单词,不过其词频是0,而无法参与tf-idf计算),并封装为indexerRequest,发送给engine.indexerAddDocumentChannels[request.shard]
------------------------------------------------
补充一点,上述代码之所以得以执行是因为在:
searcher = engine.Engine{}
// 初始化
searcher.Init(types.EngineInitOptions{SegmenterDictionaries: "../data/dictionary.txt"})
searcher的初始化代码里有这么一段:
// 启动分词器for iThread := 0; iThread < options.NumSegmenterThreads; iThread++ {go engine.segmenterWorker()}
------------------------------------------------
接收indexerRequest的代码在index_worker.go里:
func (engine *Engine) indexerAddDocumentWorker(shard int) {for {request := <-engine.indexerAddDocumentChannels[shard] //关键addInvertedIndex := engine.indexers[shard].AddDocument(request.document, request.dealDocInfoChan)// saveif engine.initOptions.UsePersistentStorage {for k, v := range addInvertedIndex {engine.persistentStorageIndexDocumentChannels[shard] <- persistentStorageIndexDocumentRequest{typ: "index",keyword: k,keywordIndices: v,}}}atomic.AddUint64(&engine.numTokenIndexAdded,uint64(len(request.document.Keywords)))atomic.AddUint64(&engine.numDocumentsIndexed, 1)} }
-----------------------------------------------
而上述函数之所以得以执行,还是因为在searcher的初始化函数里有这么一句:
// 启动索引器和排序器for shard := 0; shard < options.NumShards; shard++ {go engine.indexerAddDocumentWorker(shard) //关键go engine.indexerRemoveDocWorker(shard)go engine.rankerAddDocWorker(shard)go engine.rankerRemoveDocWorker(shard)for i := 0; i < options.NumIndexerThreadsPerShard; i++ {go engine.indexerLookupWorker(shard)}for i := 0; i < options.NumRankerThreadsPerShard; i++ {go engine.rankerRankWorker(shard)}}
------------------------------------------------
其中,engine.indexers[shard].AddDocument(request.document, request.dealDocInfoChan)的核心代码在indexer.go里:
// 向反向索引表中加入一个文档 func (indexer *Indexer) AddDocument(document *types.DocumentIndex, dealDocInfoChan chan<- bool) (addInvertedIndex map[string]*types.KeywordIndices) {if indexer.initialized == false {log.Fatal("索引器尚未初始化")}indexer.InvertedIndexShard.Lock()defer indexer.InvertedIndexShard.Unlock()// 更新文档总数及关键词总长度 indexer.DocInfosShard.Lock()if _, found := indexer.DocInfosShard.DocInfos[document.DocId]; !found {indexer.DocInfosShard.DocInfos[document.DocId] = new(types.DocInfo)indexer.DocInfosShard.NumDocuments++}if document.TokenLength != 0 {originalLength := indexer.DocInfosShard.DocInfos[document.DocId].TokenLengthsindexer.DocInfosShard.DocInfos[document.DocId].TokenLengths = float32(document.TokenLength)indexer.InvertedIndexShard.TotalTokenLength += document.TokenLength - originalLength}indexer.DocInfosShard.Unlock()close(dealDocInfoChan)// docIdIsNew := truefoundKeyword := falseaddInvertedIndex = make(map[string]*types.KeywordIndices)for _, keyword := range document.Keywords {addInvertedIndex[keyword.Text], foundKeyword = indexer.InvertedIndexShard.InvertedIndex[keyword.Text]if !foundKeyword {addInvertedIndex[keyword.Text] = new(types.KeywordIndices)}indices := addInvertedIndex[keyword.Text]if !foundKeyword {// 如果没找到该搜索键则加入switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations = [][]int{keyword.Starts}case types.FrequenciesIndex:indices.Frequencies = []float32{keyword.Frequency}}indices.DocIds = []uint64{document.DocId}indexer.InvertedIndexShard.InvertedIndex[keyword.Text] = indicescontinue}// 查找应该插入的位置position, found := indexer.searchIndex(indices, 0, indexer.getIndexLength(indices)-1, document.DocId)if found {// docIdIsNew = false// 覆盖已有的索引项switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations[position] = keyword.Startscase types.FrequenciesIndex:indices.Frequencies[position] = keyword.Frequency}continue}// 当索引不存在时,插入新索引项switch indexer.initOptions.IndexType {case types.LocationsIndex:indices.Locations = append(indices.Locations, []int{})copy(indices.Locations[position+1:], indices.Locations[position:])indices.Locations[position] = keyword.Startscase types.FrequenciesIndex:indices.Frequencies = append(indices.Frequencies, float32(0))copy(indices.Frequencies[position+1:], indices.Frequencies[position:])indices.Frequencies[position] = keyword.Frequency}indices.DocIds = append(indices.DocIds, 0)copy(indices.DocIds[position+1:], indices.DocIds[position:])indices.DocIds[position] = document.DocId}return }
查找docID是否存在于倒排列表的时候是二分:
// 二分法查找indices中某文档的索引项 // 第一个返回参数为找到的位置或需要插入的位置 // 第二个返回参数标明是否找到 func (indexer *Indexer) searchIndex( indices *types.KeywordIndices, start int, end int, docId uint64) (int, bool) {// 特殊情况if indexer.getIndexLength(indices) == start {return start, false}if docId < indexer.getDocId(indices, start) {return start, false} else if docId == indexer.getDocId(indices, start) {return start, true}if docId > indexer.getDocId(indices, end) {return end + 1, false} else if docId == indexer.getDocId(indices, end) {return end, true}// 二分var middle intfor end-start > 1 {middle = (start + end) / 2 if docId == indexer.getDocId(indices, middle) {return middle, true} else if docId > indexer.getDocId(indices, middle) {start = middle } else {end = middle}}return end, false }
TODO,待分析:indexer里索引的细节,以及评分相关的逻辑:
rankerRequest := rankerAddDocRequest{docId: request.docId,fields: request.data.Fields,dealDocInfoChan: dealDocInfoChan,}engine.rankerAddDocChannels[request.shard] <- rankerRequest
转载于:https://www.cnblogs.com/bonelee/p/6528556.html
wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储相关推荐
- wukong引擎源码分析之索引——part 2 持久化 直接set(key,docID数组)在kv存储里...
前面说过,接收indexerRequest的代码在index_worker.go里: func (engine *Engine) indexerAddDocumentWorker(shard int) ...
- wukong引擎源码分析之索引——part 3 文档评分 无非就是将docid对应的fields信息存储起来,为搜索结果rank评分用...
之前的文章分析过,接受索引请求处理的代码在segmenter_worker.go里: func (engine *Engine) segmenterWorker() {for {request := ...
- wukong引擎源码分析之搜索——docid有序的数组里二分归并求交集,如果用跳表的话,在插入索引时会更快...
searcher.Search(types.SearchRequest{Text: "百度中国"}) // 查找满足搜索条件的文档,此函数线程安全 func (engine *En ...
- 虚幻引擎源码分析(5)
虚幻引擎源码分析(5)
- bleve搜索引擎源码分析之索引——mapping和lucene一样,也有_all
例子: package mainimport ("fmt""github.com/blevesearch/bleve" )func main() {// ope ...
- 白鹭php源码,egret 2D引擎源码分析(二) 创建播放器
本帖最后由 fightingcat 于 2016-7-16 00:26 编辑 上一篇讲到了引擎的入口runEgret为每一个播放器标签(就是index.html中看到的那个 之前web.WebPlay ...
- bleve搜索引擎源码分析之索引——mapping真复杂啊
接下来看看下面index部分的源码实现: data := struct {Name stringDes string}{Name: "hello world this is bone&quo ...
- 悟空分词与mysql结合_悟空分词的搜索和排序源码分析之——索引
转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/ 索引过程分析 下面我们来分析索引过程. // 将文档加入索引 // // ...
- 以太坊共识引擎源码分析
这一篇分析以太坊的共识引擎,先看一下各组件之间的关系: Engine接口定义了共识引擎需要实现的所有函数,实际上按功能可以划分为2类: 区块验证类:以Verify开头,当收到新区块时,需要先验证区块的 ...
最新文章
- Linux网络属性配置相关命令
- Synchronize读脏
- Fiori elements执行过程解析:When click go in table list, odata service is sent
- 一根网线有这么多“花样”,你知道吗?
- python创建和控制的实体称为_Python语法基础
- 通过DOS命令nslookup查域名DNS服务器
- 白帽黑客眼中的网络安全 挡黑客财路曾收恐吓信
- MySQL建表(那些字段必须)命令详解
- POJ 2457 BFS
- android 选择银行类型,『自定义View实战』—— 银行种类选择器
- 18.docker top
- 机器学习(周志华) 第七章贝叶斯分类器
- IDEA自定义带JavaDoc的getter/setter模板
- JavaScript级联链表
- 毕业设计周报(第六周)
- python使用turtle库绘制一个红色五角星_使用turtle库绘制红色五角星图形
- Sybase数据库自动备份的实现
- 排名:百度小程序 微信 + 支付宝 + 百度 + 头条 商城源码-拓客营销
- SAP 发票金额容差与供应商容差
- R语言入门教程知识 第一章 R语言
热门文章
- 【Linux网络编程笔记】TCP短连接产生大量TIME_WAIT导致无法对外建立新TCP连接的原因及解决方法—实践篇
- 在Linux下怎样让top命令启动之后就按内存使用排序(或CPU使用排序)?
- ubuntu05.04 linux2.6.10 内核安装
- c语言邻接表的构建_c语言数据结构--图的邻接矩阵和邻接表操作的基本操作
- linux搜索有哪些文件夹,Linux常见几个查找命令
- java 主线程_Java中的主线程 - Break易站
- 原生mysql的批量更新及性能测试
- 字节Java高级岗:javaio流面试题
- 深度学习生态圈【详解深度学习工具Keras】
- 【Java Web前端开发】Response笔记