悟空分词与mysql结合_悟空分词的搜索和排序源码分析之——索引
转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/
索引过程分析
下面我们来分析索引过程。
// 将文档加入索引
//
// 输入参数:
// docId标识文档编号,必须唯一
//data见DocumentIndexData注释
//
// 注意:
// 1. 这个函数是线程安全的,请尽可能并发调用以提高索引速度
// 2. 这个函数调用是非同步的,也就是说在函数返回时有可能文档还没有加入索引中,因此
// 如果立刻调用Search可能无法查询到这个文档。强制刷新索引请调用FlushIndex函数。
func (engine *Engine) IndexDocument(docId uint64, data types.DocumentIndexData) {
engine.internalIndexDocument(docId, data)
hash := murmur.Murmur3([]byte(fmt.Sprint("%d", docId))) % uint32(engine.initOptions.PersistentStorageShards)
if engine.initOptions.UsePersistentStorage {
engine.persistentStorageIndexDocumentChannels[hash]
}
}
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
docId: docId, hash: hash, data: data}
}
这里需要注意的是,docId参数需要调用者从外部传入,而不是在内部自己创建,这给搜索引擎的实现者更大的自由。 将文档交给分词器处理,然后根据murmur3计算的hash值模PersistentStorageShards,选择合适的shard写入持久化存储中。
索引过程分析:分词协程处理过程
分词器协程的逻辑代码在这里:segmenter_worker.go:func (engine *Engine) segmenterWorker()
分词器协程的逻辑是一个死循环,不停的从channel engine.segmenterChannel中读取数据,针对每一次读取的数据:
计算shard号
将文档分词
根据分词结果,构造indexerAddDocumentRequest 和 rankerAddDocRequest
将indexerAddDocumentRequest投递到channel engine.indexerAddDocumentChannels[shard]中
将rankerAddDocRequest投递到channel engine.rankerAddDocChannels[shard]中
补充一句:这里shard号的计算过程如下:
// 从文本hash得到要分配到的shard
func (engine *Engine) getShard(hash uint32) int {
return int(hash - hash/uint32(engine.initOptions.NumShards)*uint32(engine.initOptions.NumShards))
}
为什么不是直接取模呢?
索引过程分析:索引器协程处理过程
首先介绍一下倒排索引表,这是搜索引擎的核心数据结构。
// 索引器
type Indexer struct {
// 从搜索键到文档列表的反向索引
// 加了读写锁以保证读写安全
tableLock struct {
sync.RWMutex
table map[string]*KeywordIndices
docs map[uint64]bool
}
initOptions types.IndexerInitOptions
initialized bool
// 这实际上是总文档数的一个近似
numDocuments uint64
// 所有被索引文本的总关键词数
totalTokenLength float32
// 每个文档的关键词长度
docTokenLengths map[uint64]float32
}
// 反向索引表的一行,收集了一个搜索键出现的所有文档,按照DocId从小到大排序。
type KeywordIndices struct {
// 下面的切片是否为空,取决于初始化时IndexType的值
docIds []uint64 // 全部类型都有
frequencies []float32 // IndexType == FrequenciesIndex
locations [][]int // IndexType == LocationsIndex
}
table map[string]*KeywordIndices这个是核心:一个关键词,对应一个KeywordIndices结构。该结构的docIds字段记录了所有包含这个关键词的文档id。 如果 IndexType == FrequenciesIndex ,则同时记录这个关键词在该文档中出现次数。 如果 IndexType == LocationsIndex ,则同时记录这个关键词在该文档中出现的所有位置的起始偏移。
下面是索引的主函数代码:
func (engine *Engine) indexerAddDocumentWorker(shard int) {
for {
request :=
engine.indexers[shard].AddDocument(request.document)
atomic.AddUint64(&engine.numTokenIndexAdded,
uint64(len(request.document.Keywords)))
atomic.AddUint64(&engine.numDocumentsIndexed, 1)
}
}
其主要逻辑又封装在func (indexer *Indexer) AddDocument(document *types.DocumentIndex)函数中实现。其逻辑如下:
将倒排索引表加锁
更新文档关键词的长度加在一起的总和
查找关键词在倒排索引表中是否存在
如果不存在,则直接加入到table map[string]*KeywordIndices中
如果存在KeywordIndices,则使用二分查找该关键词对应的docId是否已经在KeywordIndices.docIds中存在。分两种情况: 1) docId存在,则更新原有的数据结构。 2) docId不存在,则插入到KeywordIndices.docIds数组中,同时保持升序排列。
更新索引过的文章总数
索引过程分析:排序器协程处理过程
在新索引文档的过程,排序器的主逻辑如下:
func (engine *Engine) rankerAddDocWorker(shard int) {
for {
request :=
engine.rankers[shard].AddDoc(request.docId, request.fields)
}
}
进而调用下面的函数
// 给某个文档添加评分字段
func (ranker *Ranker) AddDoc(docId uint64, fields interface{}) {
if ranker.initialized == false {
log.Fatal("排序器尚未初始化")
}
ranker.lock.Lock()
ranker.lock.fields[docId] = fields
ranker.lock.docs[docId] = true
ranker.lock.Unlock()
}
上述函数非常简单,只是将应用层自定义的数据加入到ranker中。
至此索引过程就完成了。简单来讲就是下面两个过程:
将文档分词,得到一堆关键词
将 关键词->docId 的对应关系加入到全局的map中(实际上是分了多个shard)
悟空分词与mysql结合_悟空分词的搜索和排序源码分析之——索引相关推荐
- 悟空分词与mysql结合_悟空分词的搜索和排序源码分析之——搜索
转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/ 搜索过程分析 下面我们来分析一下搜索的过程.首先构造一个SearchReq ...
- 悟空分词的搜索和排序源码分析之——搜索
转自:http://blog.codeg.cn/2016/02/02/wukong-source-code-reading/ 搜索过程分析 下面我们来分析一下搜索的过程.首先构造一个SearchReq ...
- springboot 事务_原创002 | 搭上SpringBoot事务源码分析专车
前言 如果这是你第二次看到师长,说明你在觊觎我的美色! 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 专车介绍 该趟专车是开往Spring Boot事务源码分析的专车 专车问题 为什么 ...
- 悟空分词与mysql结合_中文分词与关键词提取实践小结
本文基于题库查重需求实现过程及<NLP自然语言处理原理与实践>学习过程总结得出.定有不足之处,恳请指出. 基本概念 评价指标 一般,中文分词从Precision.Recall.F-scor ...
- mysql朗读_“绘爱”诗词朗读小程序源码(thinkphp+mysql+小程序端)
两年前,写了个这个"ThinkPHP开发微信语音小程序",时隔2年,小程序框架发展的也是日新月异.前段时间因为开发app,偶然接触到了dcloud的uni-app框架,真的是爽的不 ...
- java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动
在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...
- java ee是什么_死磕 java集合之HashSet源码分析
问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...
- hashmap修改对应key的值_死磕 java集合之HashMap源码分析
简介 HashMap采用key/value存储结构,每个key对应唯一的value,查询和修改的速度都很快,能达到O(1)的平均时间复杂度.它是非线程安全的,且不保证元素存储的顺序: 继承体系 Has ...
- 结巴分词关键词相似度_结巴分词5--关键词抽取
作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 简介 关键词抽取就是从文本里面把跟这篇文档意义最相关的一些词抽 ...
最新文章
- 在Leangoo中,如何快速切换项目内看板?
- Python操作Firefox
- 人脸识别翼闸使用规范_人行通道闸如何搭配人脸识别使用
- FPGA基础之逻辑单元(LE or LC)的基本结构
- mac上安装MySQL
- html5 svg组态图,绘制SVG内容到Canvas的HTML5应用
- LeetCode 84. 柱状图中最大的矩形(Largest Rectangle in Histogram)
- c语言找到串口,再次熟悉串口
- SQL调优日记--并行等待的原理和问题排查
- 华三 h3c super vlan配置
- java JSONObject/JSONArray详解
- ON1 Resize 2022 for Mac(图片大小修改工具)
- VS中Release模式下生成去掉生成pdb文件
- 一些易被忽视且难度较高的Web前端面试题汇总
- 苦难是屈辱,还是财富?
- 自动写字成图5款有趣实用的AIGC工具分享
- 阿里推出阿里云网盘APP,下载速度远超百度网盘
- The longest zig-zag subsequence
- Spring IOC和DI 的学习资料(附带大师英文文章)
- vue-router 快速返回上一页