gofound项目服务框架剖析(一) | 青训营笔记 您所在的位置:网站首页 go搜索引擎bleve gofound项目服务框架剖析(一) | 青训营笔记

gofound项目服务框架剖析(一) | 青训营笔记

2023-12-29 01:48| 来源: 网络整理| 查看: 265

这是我参与「第三届青训营-后端场」笔记创作活动的第4篇笔记

本系列笔记将对gofound项目的api源码进行分析,探究这个搜索引擎的架构。

0. 预备工作:配置vscode调试环境

为了方便后续对项目源码的跟踪和调试,首先学习利用vscode对go项目进行调试。这里主要参考

github.com/golang/vsco…

segmentfault.com/a/119000001…

首先尝试按下F5启动自动调试,这时vscode会提醒缺少golang调试器,按照vscode的提示安装golang调试器后利用vscode的“运行和调试”组件生成一个launch.json文件,在该文件中设置程序启动时的参数

点击vscode“运行和调试”UI上的运行箭头即可开始调试,下图展示了程序停止在main函数设置的断点处,UI中变量一栏显示了当前函数局部变量args的值,调用堆栈一栏显示了函数调用栈。

小结:为了方便后续对项目源码进行跟踪调试,首先配置好vscode的调试环境。

分析gofound主流程

以下是gofound的主函数,可以看到main函数做的事情就是根据参数args做一些初始化操作,得到一个提供分词服务和数据存储服务的container对象,之后调用initGin函数对Gin引擎进行初始化。

func main() { defer func() { if r := recover(); r != nil { fmt.Printf("panic: %s\n", r) } }() //解析参数 args := parseArgs() //线程数=cpu数 runtime.GOMAXPROCS(args.GOMAXPROCS) //初始化分词器,args.DictionaryPath的默认值是"./data/dictionary.txt" tokenizer := initTokenizer(args.DictionaryPath) container := initContainer(args, tokenizer) //初始化gin initGin(args, container) fmt.Printf("Done!") }

根据以上这个代码结构我们可以推断出该服务器通过Gin框架实现http通信,通过container提供检索服务,container又利用了tokenizer提供的分词服务。

分析searcher.Container提供的检索服务 Container结构体定义 type Container struct { Dir string //文件夹 engines map[string]*Engine //引擎 Debug bool //调试 Tokenizer *words.Tokenizer //分词器 Shard int //分片 }

上面是Container结构体的定义,显然engines是检索引擎,Engine结构体的定义如下:

type Engine struct { IndexPath string //索引文件存储目录 Option *Option //配置 invertedIndexStorages []*storage.LeveldbStorage //关键字和Id映射,倒排索引,key=id,value=[]words positiveIndexStorages []*storage.LeveldbStorage //ID和key映射,用于计算相关度,一个id 对应多个key,正排索引 docStorages []*storage.LeveldbStorage //文档仓 sync.Mutex //锁 sync.WaitGroup //等待 addDocumentWorkerChan []chan *model.IndexDoc //添加索引的通道 IsDebug bool //是否调试模式 Tokenizer *words.Tokenizer //分词器 DatabaseName string //数据库名 Shard int //分片数 } type Option struct { InvertedIndexName string //倒排索引 PositiveIndexName string //正排索引 DocIndexName string //文档存储 }

根据注释我们可以看出Engine使用了3组数据库分别是invertedIndexStorages,positiveIndexStorages和docStorages。(源代码注释中的关键字可以理解为利用分词器得到的关键词,每条索引可以有多个关键字,但是只能有一个id)

倒排索引(invertedIndexStorages)存储关键词和id数组之间的映射 正排索引(positiveIndexStorages)存储id和关键词之间的映射 文档仓(docStorages)存储id和(索引,关键词)之间的映射

下面以添加一条gofound官方给出的索引为例,说明上面三种存储的差异

{ "id": 88888, "text": "深圳北站", "document": { "title": "阿森松岛所445", "number": 223 } }

假设分词器为此索引生成的关键词为 “深圳”、“北站”

那么倒排索引(invertedIndexStorages)中会添加两个条目分别为:

{"深圳",[88888]}, {"北站",[88888]}

正排索引(positiveIndexStorages)中会添加一个条目为:

{88888,[“深圳”,“北站”]}

文档仓(docStorages)中会添加一个条目为:

{88888, { {"id": 88888, "text": "深圳北站","document": {"title": "阿森松岛所445","number": 223}}, [“深圳”,“北站”]}}

Container api分析

上文已经提到gofound通过Contain结构体中的Engine提供数据检索相关服务,下面对查询索引和添加索引对应两个api对应的源码进行分析

查询索引api

api定义:func (e *Engine) MultiSearch(request *model.SearchRequest) *model.SearchResult

请求、响应结构体定义:

// SearchRequest 搜索请求 type SearchRequest struct { Query string `json:"query,omitempty"` // 搜索关键词 Order string `json:"order,omitempty"` // 排序类型 Page int `json:"page,omitempty"` // 页码 Limit int `json:"limit,omitempty"` // 每页大小,最大1000,超过报错 Highlight *Highlight `json:"highlight,omitempty"` // 关键词高亮 } // SearchResult 搜索响应 type SearchResult struct { Time float64 `json:"time,omitempty"` //查询用时 Total int `json:"total"` //总数 PageCount int `json:"pageCount"` //总页数 Page int `json:"page,omitempty"` //页码 Limit int `json:"limit,omitempty"` //页大小 Documents []ResponseDoc `json:"documents,omitempty"` //文档 Words []string `json:"words,omitempty"` //搜索关键词 } type ResponseDoc struct { IndexDoc OriginalText string `json:"originalText,omitempty"` Score int `json:"score,omitempty"` //得分 Keys []string `json:"keys,omitempty"` }

主要逻辑流程:

调用分析器服务得到搜索关键词 多协程并行对关键词进行遍历 利用倒排索引找到每个关键词对应的索引id 计算每条索引id的得分,并按照得分从高到低排序 根据页号选出对应索引id的窗口,再根据id窗口去读取索引内容 增加/修改索引api

api定义:func (e *Engine) AddDocument(index *model.IndexDoc)

请求结构体定义:

// IndexDoc 索引实体 type IndexDoc struct { Id uint32 `json:"id,omitempty"` Text string `json:"text,omitempty"` Document map[string]interface{} `json:"document,omitempty"` }

主要逻辑流程:

调用分词器得到关键词 判断当前索引是否值得更新 如果当前id存在且内容与原来一致,则不值得更新 添加倒排索引到数据库,添加正排索引到数据库 Web api分析

上文已经介绍了container中的engine提供了索引查找和索引添加的服务,Web层Api可以直接调用这些服务实现,下面展示了Web层的query 和 addIndex两个api的实现

查询索引API func (a *Api) query(c *gin.Context) { var request = &model.SearchRequest{} err := c.BindJSON(&request) if err != nil { c.JSON(200, Error(err.Error())) return } //调用Container提供的搜索服务 r := a.Container.GetDataBase(c.Query("database")).MultiSearch(request) c.JSON(200, Success(r)) }

请求、响应结构体定义:

增加/修改索引API func (a *Api) addIndex(c *gin.Context) { document := &model.IndexDoc{} err := c.BindJSON(&document) if err != nil { c.JSON(200, Error(err.Error())) return } //调用Container提供的添加索引服务 //这些协程(生产者)实际上将索引缓存到了Container中chan缓存队列中 //Container内部的消费者协程(go e.DocumentWorkerExec(worker)) //负责将索引数据取出并保存到数据库 go a.Container.GetDataBase(c.Query("database")).IndexDocument(document) c.JSON(200, Success(nil)) }

请求、响应结构体定义:

响应结构体示例: { "state": true, "message": "success" } 总结

本文对gofound项目Web层和搜索引擎层核心API进行了分析,总结出gofound服务框架的基本轮廓:

暂时无法在文档外展示此内容

Web层利用Gin框架处理与客户端之间的数据传输,利用Container提供的服务实现索引的查询和添加 Container层中的Engine实现了查询索引和添加索引的具体逻辑,调用Tokenizer提供的分词服务,

调用LeveldbStorage层提供的与数据库交互的服务



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有