悟空(wukong)搜索引擎源代码阅读(待续)
来源:互联网 发布:bae怎么域名备案 编辑:程序博客网 时间:2024/05/21 16:59
悟空(wukong)搜索引擎源代码阅读
最近为了了解搜索引擎的一些知识,阅读了wukong搜索的一些源码,在这里记录一下。项目地址在这里
整个的处理流程如下:
在项目中主要有一下几个目录:
示例代码如下:
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中定义了,就用其中的;要么由引擎的初始化选项的默认排序准则定义。
收集关键词
如果输入了文档,就对文档进行分词,并去掉其中的停用词;
如果没有,就用输入数据定义的关键词;
生成查找请求,发送给索引器,然后从排序器中取数据
排序,输出
- 悟空(wukong)搜索引擎源代码阅读(待续)
- Javac源代码阅读理解(未完待续)
- 源代码(未完待续)
- 阅读EventBus源码(笔记)待续
- OSG源代码部分剖析(待续)?????????????????????????????
- 搜索引擎源代码
- 《搜索引擎技术基础》---读书总结(未完待续)
- qlwm源代码阅读(一)
- 如何阅读源代码(1)
- Apache 源代码阅读(1)
- Apache 源代码阅读(2)
- python解析搜狗网页源代码:取搜索引擎返回的前page_num*10个链接的url(待续)
- hdu 2833 WuKong(最短路 + dp)
- HDU 2833 WuKong(floyd最短路)
- HDU 2833 WuKong(floyd+dp)
- HDU2833 WuKong(floyd + dp)经典
- hdu 2833 WuKong(floyd变形)
- swish-e搜索引擎, 源代码分析(6)
- WordCount代码解析
- linux安装coreseek
- classpath环境变量配置
- 安卓应用百度地图API(1)-keytool
- java File类
- 悟空(wukong)搜索引擎源代码阅读(待续)
- 关于unity的绝对路径与相对路径的使用
- Linux入门(12)——解决双系统下Ubuntu16.04不能访问Windows分区
- OA系统文档
- 动态生成和销毁物体
- Iterator和for循环对比
- 分享一些实用的Android Studio插件
- 银行卡号 格式,可删除
- typedef的作用