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, )
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 :=
if !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 :=
for 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, ) indexerRequest.dealDocInfoChan = dealDocInfoChan
engine.indexerAddDocumentChannels[request.shard] <- indexerRequest rankerRequest := 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 := ; 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)
// save
if 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, )
}
}

-----------------------------------------------

而上述函数之所以得以执行,还是因为在searcher的初始化函数里有这么一句:

        // 启动索引器和排序器
for shard := ; shard < options.NumShards; shard++ {
go engine.indexerAddDocumentWorker(shard) //关键
go engine.indexerRemoveDocWorker(shard)
go engine.rankerAddDocWorker(shard)
go engine.rankerRemoveDocWorker(shard) for i := ; i < options.NumIndexerThreadsPerShard; i++ {
go engine.indexerLookupWorker(shard)
}
for i := ; 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 != {
originalLength := indexer.DocInfosShard.DocInfos[document.DocId].TokenLengths
indexer.DocInfosShard.DocInfos[document.DocId].TokenLengths = float32(document.TokenLength)
indexer.InvertedIndexShard.TotalTokenLength += document.TokenLength - originalLength
}
indexer.DocInfosShard.Unlock()
close(dealDocInfoChan) // docIdIsNew := true
foundKeyword := false
addInvertedIndex = 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] = indices
continue
} // 查找应该插入的位置
position, found := indexer.searchIndex(
indices, , indexer.getIndexLength(indices)-, document.DocId)
if found {
// docIdIsNew = false // 覆盖已有的索引项
switch indexer.initOptions.IndexType {
case types.LocationsIndex:
indices.Locations[position] = keyword.Starts
case 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+:], indices.Locations[position:])
indices.Locations[position] = keyword.Starts
case types.FrequenciesIndex:
indices.Frequencies = append(indices.Frequencies, float32())
copy(indices.Frequencies[position+:], indices.Frequencies[position:])
indices.Frequencies[position] = keyword.Frequency
}
indices.DocIds = append(indices.DocIds, )
copy(indices.DocIds[position+:], 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 + , false
} else if docId == indexer.getDocId(indices, end) {
return end, true
} // 二分
var middle int
for end-start > {
middle = (start + end) /
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

wukong引擎源码分析之索引——part 1 倒排列表本质是有序数组存储的更多相关文章

  1. wukong引擎源码分析之索引——part 2 持久化 直接set(key,docID数组)在kv存储里

    前面说过,接收indexerRequest的代码在index_worker.go里: func (engine *Engine) indexerAddDocumentWorker(shard int) ...

  2. wukong引擎源码分析之索引——part 3 文档评分 无非就是将docid对应的fields信息存储起来,为搜索结果rank评分用

    之前的文章分析过,接受索引请求处理的代码在segmenter_worker.go里: func (engine *Engine) segmenterWorker() { for { request : ...

  3. wukong引擎源码分析之搜索——docid有序的数组里二分归并求交集,如果用跳表的话,在插入索引时会更快

    searcher.Search(types.SearchRequest{Text: "百度中国"}) // 查找满足搜索条件的文档,此函数线程安全 func (engine *En ...

  4. Spark源码分析 – 汇总索引

    http://jerryshao.me/categories.html#architecture-ref http://blog.csdn.net/pelick/article/details/172 ...

  5. bleve搜索引擎源码分析之索引——mapping和lucene一样,也有_all

    例子: package main import ( "fmt" "github.com/blevesearch/bleve" ) func main() { / ...

  6. bleve搜索引擎源码分析之索引——mapping真复杂啊

    接下来看看下面index部分的源码实现: data := struct { Name string Des string }{ Name: "hello world this is bone ...

  7. 转:Irrlicht 0.1引擎源码分析与研究(一)

    目录(?)[-] 主要技术特性 引擎概览 Irrlicht的窗口管理   Irrlicht引擎主要是由一个名叫Nikolaus Gebhardt奥地利人所设计,是sourceforge上的一个开源项目 ...

  8. lua源码分析 伪索引

    Lua 提供了一个 注册表, 这是一个预定义出来的表, 可以用来保存任何 C 代码想保存的 Lua 值. 这个表可以用有效伪索引 LUA_REGISTRYINDEX 来定位. 任何 C 库都可以在这张 ...

  9. Java集合系列:-----------03ArrayList源码分析

    上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...

随机推荐

  1. 使用Reveal 调试iOS应用程序

    Itty Bitty Apps发布了一款实用工具——Reveal,它能够在运行时调试和修改iOS应用程序.Reveal能连接到应用程序,并允许开发者编辑各种用户界面参数,这反过来会立即反应在程序的UI ...

  2. 一张图搞清楚PMBOK所有过程的使用

      很多参加PMP培训的学员大概都会有一个感受,上课时似乎每个知识点都听懂了,大的知识框架也弄明白了,但是所有这些串起来在实践中怎么用呀!说的再直接一点,在考试的时候这些过程和活动是以怎样的逻辑来应用 ...

  3. gdb源码安装过程中的问题:no termcap library found

    gdb使用源码安装的时候遇到错误:no termcap library found ./configure -->  make --> make install 解决办法,下载termca ...

  4. Java中的Copy-on-Write容器 & ConcurrentHashMap & HashTable比较

    参考这篇文章:Link 从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet ...

  5. 【转】Spring框架深入理解

    参考这篇文章: http://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/ Spring内部分为Beans, Context 和 ...

  6. docker下用keepalived+Haproxy实现高可用负载均衡集群

    启动keepalived后宿主机无法ping通用keepalived,报错: [root@localhost ~]# ping 172.18.0.15 PING () bytes of data. F ...

  7. iOS常用网络库收集

    一 ASIHttpRequest二 AFNetworking 三 AFDownloadRequestOperationA progressive download operation for AFNe ...

  8. C#语言 函数

  9. 【转载】TCP的三次握手(建立连接)和四次挥手(关闭连接)

    建立连接: 理解:窗口和滑动窗口TCP的流量控制 TCP使用窗口机制进行流量控制 什么是窗口? 连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端 接收方发送的确认信息中包 ...

  10. hive cli 启动缓慢问题

    hive-0.13.1启动缓慢的原因 发现时间主要消耗在以下3个地方: 1. hadoopjar的时候要把相关的jar包上传到hdfs中(这里大概消耗5s,hive0.11一样,这个地方不太好优化) ...