引言

Bleve是Golang实现的一个全文检索库,类似Lucene之于Java。在这里通过阅读其代码,来学习如何使用及定制检索功能。也是为了通过阅读代码,学习在具体环境下Golang的一些使用方式。代码的路径在github上https://github.com/blevesearch/bleve

1 新建索引

下面的代码摘自Bleve的"Hello World"示例。

  1. // open a new index
  2. mapping := bleve.NewIndexMapping()
  3. index, err := bleve.New("example.bleve", mapping)
  4. if err != nil {
  5. fmt.Println(err)
  6. return
  7. }

1.1和1.2两节是对上面逻辑的展开介绍,1.3节是在阅读代码中遇到的一些Golang特性的介绍,1.4节是当我们在使用bleve新建索引时可能会怎么做。

1.1 新建一个IndexMapping

下面这段代码是Bleve的"Hello World"示例的第一条语句,表示打开一个新索引。

  1. // open a new index
  2. mapping := bleve.NewIndexMapping()

这个函数的定义位于bleve目录下的mapping.go文件

  1. func NewIndexMapping() *mapping.IndexMappingImpl {
  2. return mapping.NewIndexMapping()
  3. }

可以看出它是一个封装函数,调用了mapping package的NewIndexMapping函数。该函数的定义位于bleve/mapping/index.go文件内,属于github.com/blevesearch/bleve/mapping包。

  1. // NewIndexMapping creates a new IndexMapping that will use all the default indexing rules
  2. func NewIndexMapping() *IndexMappingImpl {
  3. return &IndexMappingImpl{
  4. TypeMapping: make(map[string]*DocumentMapping),
  5. DefaultMapping: NewDocumentMapping(),
  6. TypeField: defaultTypeField,
  7. DefaultType: defaultType,
  8. DefaultAnalyzer: defaultAnalyzer,
  9. DefaultDateTimeParser: defaultDateTimeParser,
  10. DefaultField: defaultField,
  11. IndexDynamic: IndexDynamic,
  12. StoreDynamic: StoreDynamic,
  13. DocValuesDynamic: DocValuesDynamic,
  14. CustomAnalysis: newCustomAnalysis(),
  15. cache: registry.NewCache(),
  16. }
  17. }

建立了一个IndexMappingImpl结构并返回其指针,该IndexMappingImpl的所有成员均以默认方式初始化。接下来看一下IndexMappingImpl结构的定义,该结构同样属于github.com/blevesearch/bleve/mapping包,并位于bleve/mapping/index.go文件内。

  1. // An IndexMappingImpl controls how objects are placed
  2. // into an index.
  3. // First the type of the object is determined.
  4. // Once the type is know, the appropriate
  5. // DocumentMapping is selected by the type.
  6. // If no mapping was determined for that type,
  7. // a DefaultMapping will be used.
  8. type IndexMappingImpl struct {
  9. TypeMapping map[string]*DocumentMapping `json:"types,omitempty"`
  10. DefaultMapping *DocumentMapping `json:"default_mapping"`
  11. TypeField string `json:"type_field"`
  12. DefaultType string `json:"default_type"`
  13. DefaultAnalyzer string `json:"default_analyzer"`
  14. DefaultDateTimeParser string `json:"default_datetime_parser"`
  15. DefaultField string `json:"default_field"`
  16. StoreDynamic bool `json:"store_dynamic"`
  17. IndexDynamic bool `json:"index_dynamic"`
  18. DocValuesDynamic bool `json:"docvalues_dynamic,omitempty"`
  19. CustomAnalysis *customAnalysis `json:"analysis,omitempty"`
  20. cache *registry.Cache
  21. }

从注释可以看出,IndexMappingImpl结构是用来控制每一个对象应该被如何放入Index中,其各字段也是围绕这个目标展开的。

TypeMapping :一个map类型,Key是string类型,表示文档的类型。Value是DocumentMapping类型的指针,表示该类型文档对应的DocumentMapping。

DefaultMapping:一个DocumentMapping类型的指针。当文档的类型未知时,使用的默认DocumentMapping。

函数bleve.NewIndexMapping()仅仅返回了一个结构,这个结构用来控制所有文档应该被如何建立索引。

1.2 新建一个索引文件

  1. index, err := bleve.New("example.bleve", mapping)

示例的第二条语句,将IndexMappingImpl结构与一个具体路径结合起来,新建一个索引。这个函数定义在bleve/index.go文件内,在bleve包内。

  1. // New index at the specified path, must not exist.
  2. // The provided mapping will be used for all
  3. // Index/Search operations.
  4. func New(path string, mapping mapping.IndexMapping) (Index, error) {
  5. return newIndexUsing(path, mapping, Config.DefaultIndexType, Config.DefaultKVStore, nil)
  6. }

这个函数调用了newIndexUsing函数,前两个参数就是New函数传进来的,第3个和第4个参数通过 Config变量给出。接下来的主要逻辑都在newIndexUsing函数实现,本节剩余部分都在newIndexUsing函数内部讨论。该函数位于index_impl.go文件内,在bleve包里。

首先,总体说一下newIndexUsing函数的功能。校验参数的合法性,根据参数从物理层面打开一个具体类型的index,保存元数据,关联统计模块。

  1. err := mapping.Validate()
  2. if err != nil {
  3. return nil, err
  4. }

校验indexMapping的合法性。具体在bleve/mapping.go中实现,主要是检查analyzer和DocumentMapping可以被建立。

  1. if kvconfig == nil {
  2. kvconfig = map[string]interface{}{}
  3. }
  4. if kvstore == "" {
  5. return nil, fmt.Errorf("bleve not configured for file based indexing")
  6. }

必须给出kv存储的具体方式,默认是用boltdb,一个golang实现的kv存储。

  1. rv := indexImpl{
  2. path: path,
  3. name: path,
  4. m: mapping,
  5. meta: newIndexMeta(indexType, kvstore, kvconfig),
  6. }
  7. rv.stats = &IndexStat{i: &rv}

初始化一个indexImpl结构,注意此结构还不是具体的index,而是包含index及其meta数据,还有统计信息的一个结构。

  1. if path != "" {
  2. err = rv.meta.Save(path)
  3. if err != nil {
  4. return nil, err
  5. }
  6. kvconfig["create_if_missing"] = true
  7. kvconfig["error_if_exists"] = true
  8. kvconfig["path"] = indexStorePath(path)
  9. } else {
  10. kvconfig["path"] = ""
  11. }

保存索引的meta数据,这个meta数据只是包含了index的具体类型,底层kv存储的具体类型。index的元数据如mapping不在这里保存。

  1. indexTypeConstructor := registry.IndexTypeConstructorByName(rv.meta.IndexType)
  2. if indexTypeConstructor == nil {
  3. return nil, ErrorUnknownIndexType
  4. }

获取index的Constructor,index类型默认是upsitedown。这个Constructor是一个函数,是在upsidedown包的init函数初始化设置的。在bleve/registry/index_type.go文件中定义。

  1. rv.i, err = indexTypeConstructor(rv.meta.Storage, kvconfig, Config.analysisQueue)
  2. if err != nil {
  3. return nil, err
  4. }

调用Index的Constructor,如果是upsidedown,函数是upsidedown.go文件的NewUpsideDownCouch函数。返回一个index.Index接口,如果是upsidedown,则也是UpsideDownCouch结构。

  1. err = rv.i.Open()
  2. if err != nil {
  3. if err == index.ErrorUnknownStorageType {
  4. return nil, ErrorUnknownStorageType
  5. }
  6. return nil, err
  7. }

打开上一步建立的index.Index接口。包括打开kv存储,初始化reader等,具体细节没有往下深究。

  1. mappingBytes, err := json.Marshal(mapping)
  2. if err != nil {
  3. return nil, err
  4. }
  5. err = rv.i.SetInternal(mappingInternalKey, mappingBytes)
  6. if err != nil {
  7. return nil, err
  8. }

将indexMapping序列化后保存至刚打开的index。

  1. indexStats.Register(&rv)

注册index的统计信息。

1.3 相关Golang特性说明

1.3.1 init()函数

这里要额外说一下1.2中Config这个变量。通过查找,可以看到Config是一个package内的全局变量。

  1. var Config *configuration

然而这个变量是一个指向configuration结构的指针,它是通过init()函数初始化的。

  1. func init() {
  2. bootStart := time.Now()
  3. // build the default configuration
  4. Config = newConfiguration()
  5. // set the default highlighter
  6. Config.DefaultHighlighter = html.Name
  7. // default kv store
  8. Config.DefaultKVStore = ""
  9. // default mem only kv store
  10. Config.DefaultMemKVStore = gtreap.Name
  11. // default index
  12. Config.DefaultIndexType = upsidedown.Name
  13. bootDuration := time.Since(bootStart)
  14. bleveExpVar.Add("bootDuration", int64(bootDuration))
  15. indexStats = NewIndexStats()
  16. bleveExpVar.Set("indexes", indexStats)
  17. initDisk()
  18. }

对于init函数的解释,来自知乎五分钟理解golang的init函数。init()函数是Golang的一个特性,它先于main函数执行。init函数的主要作用:

  • 初始化不能采用初始化表达式初始化的变量。
  • 程序运行前的注册。
  • 实现sync.Once功能。
  • 其他

init函数的主要特点有:

  • init函数先于main函数自动执行,不能被其他函数调用;
  • init函数没有输入参数、返回值;
  • 每个包可以有多个init函数;
  • 包的每个源文件也可以有多个init函数,这点比较特殊;
  • 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
  • 不同包的init函数按照包导入的依赖关系决定执行顺序。

golang程序初始化:

  1. 初始化导入的包(包的初始化顺序并不是按导入顺序(“从上到下”)执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化,与变量初始化依赖关系类似;
  2. 初始化包作用域的变量;
  3. 执行包的init函数。

1.3.2 struct类型的tag

在1.1节IndexMappingImpl结构体定义中,我们看到定义结构体的每个成员时用到了三个字段,前两个字段是成员变量名和类型,第三个字段就是struct的Tag。

  1. TypeMapping map[string]*DocumentMapping `json:"types,omitempty"`
  2. DefaultMapping *DocumentMapping `json:"default_mapping"`

struct的Tag是用双引号或反向单引号括起来的字符串,可以通过reflect包来访问和获取。

  1. // ojb是indexMappingImpl结构的一个实例
  2. t := reflect.TypeOf(obj)
  3. for i := 0; i < t.NumField(); i++ {
  4. if t.Field(i).CanInterface(){
  5. fmt.Printf("%s %s = %v -tag:%s \n",
  6. t.Field(i).Name,
  7. t.Field(i).Type,
  8. v.Field(i).Interface(),
  9. t.Field(i).Tag)
  10. }
  11. }

在我们的例子中,Tag的内容是json:后跟一个双引号括起来的value列表,这表示json在marshel这个结构体是,对应的成员应该如何处理。json:"default_mapping"这个tag,表示json在marshel这个结构体时,该成员应该以default_mapping为key,unmarshel时遇到default_mapping这个key,也会解码到对应的成员。json:"types,omitempty"第二个参数omitempty的含义是,当该成员为empty值(0、nil等)时忽略该成员。

其他的例子还包括bison和protobuf,功能类似。

  1. type User struct {
  2. Name string `json:"name,omitempty" bson:"name,omitempty" protobuf:"1"`
  3. Secret string `json:"-,omitempty" bson:"secret,omitempty" protobuf:"2"`
  4. }

1.4 实践落地

在实践中,如果我们要使用bleve,肯定会新建一个索引。最简单的新建索引的方式,已经在本文最开头的代码中给出。

  1. mapping := bleve.NewIndexMapping()
  2. index, err := bleve.New("example.bleve", mapping)
  3. if err != nil {
  4. fmt.Println(err)
  5. return
  6. }

但是,实际使用中我们可能会对不同的域有不同的检索需求,还可能会使用不同的Analyzer。根据不同的个性化需求,我们需要使用更具体的接口来进行初始化,目前对这一块还不是特别了解。但是,总体来说新建一个index应该包含上步。第一,建立一个IndexMapping然后个性化配置;第二,在一个文件上打开索引文件,这一步可能需要使用一些更具体的接口来进行配置。

Bleve代码阅读(一)——新建索引的更多相关文章

  1. Bleve代码阅读(二)——Index Mapping

    引言 Bleve是Golang实现的一个全文检索库,类似Lucene之于Java.在这里通过阅读其代码,来学习如何使用及定制检索功能.也是为了通过阅读代码,学习在具体环境下Golang的一些使用方式. ...

  2. 代码阅读分析工具Understand 2.0试用

    Understand 2.0是一款源代码阅读分析软件,功能强大.试用过一段时间后,感觉相当不错,确实可以大大提高代码阅读效率.由于Understand功能十分强大,本文不可能详尽地介绍它的所有功能,所 ...

  3. Android 上的代码阅读器 CoderBrowserHD 修改支持 go 语言代码

    我在Android上的代码阅读器用的是 https://github.com/zerob13/CoderBrowserHD 改造的版本,改造后的版本我放在 https://github.com/ghj ...

  4. Linux协议栈代码阅读笔记(二)网络接口的配置

    Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...

  5. [置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  6. 图形化代码阅读工具——Scitools Understand

    Scitools出品的Understand 2.0.用了很多年了,比Source Insight强大很多.以前的名字叫Understand for C/C++,Understand for Java, ...

  7. Python - 关于代码阅读的一些建议

    初始能力 让阅读思路保持清晰连贯,主力关注在流程架构和逻辑实现上,不被语法.技巧和业务流程等频繁地阻碍和打断. 建议基本满足以下条件,再开始进行代码阅读: 具备一定的语言基础:熟悉基础语法,常用的函数 ...

  8. MediaInfo代码阅读

      MediaInfo是一个用来分析媒体文件的开源工具. 支持的文件非常全面,基本上支持所有的媒体文件. 最近是在做HEVC开发,所以比较关注MediaInfo中关于HEVC的分析与处理. 从Meid ...

  9. Tools - 一些代码阅读的方法

    1 初始能力 让阅读思路清晰连贯,保持在程序的流程架构和逻辑实现上,不被语法.编程技巧和业务流程等频繁地阻碍和打断. 语言基础:熟悉基础语法,常用的函数.库.编程技巧等: 了解设计模式.构建工具.代码 ...

随机推荐

  1. 流程控制之if判断,while循环,for循环

    if判断? 什么是if判断? 判断一个条件如果成立则做...不成立则... 为什么要有判断? 让计算机像人一样具备判断的能力 如何用if判断 if 条件1: code1    code2    cod ...

  2. Baltic Dry Index

    波罗的海干散货指数(Baltic Dry Index,缩写BDI),是航运业的经济指标,它包含了航运业的干散货交易量的转变. BDI波罗的海指数是由几条主要航线的即期运费(Spot Rate)加权计算 ...

  3. jenkins--svn+Email自动触发2(jenkins系统配置)

    jenkins系统配置-SonarQube servers配置: 邮件通知设置: 邮件调试问题: 在 系统设置 --> Extended E-mail Notification: 找到 Enab ...

  4. BZOJ3729Gty的游戏——阶梯博弈+巴什博弈+非旋转treap(平衡树动态维护dfs序)

    题目描述 某一天gty在与他的妹子玩游戏.妹子提出一个游戏,给定一棵有根树,每个节点有一些石子,每次可以将不多于L的石子移动到父节点,询问将某个节点的子树中的石子移动到这个节点先手是否有必胜策略.gt ...

  5. BZOJ1041 HAOI2008圆上的整点(数论)

    求x2+y2=r2的整数解个数,显然要化化式子.考虑求正整数解. y2=r2-x2→y2=(r-x)(r+x)→(r-x)(r+x)为完全平方数→(r-x)(r+x)/d2为完全平方数,d=gcd(r ...

  6. MT【23】用算术几何不等式证明数列极限存在

    评:如果不需要精确到3,上界的求法可以利用$$(1+\frac{1}{n})^n*\frac{1}{2}*\frac{1}{2}<(\frac{n+\frac{1}{n}*n+\frac{1}{ ...

  7. LOJ #2802. 「CCC 2018」平衡树(整除分块 + dp)

    题面 LOJ #2802. 「CCC 2018」平衡树 题面有点难看...请认真阅读理解题意. 转化后就是,给你一个数 \(N\) ,每次选择一个 \(k \in [2, N]\) 将 \(N\) 变 ...

  8. 自学工业控制网络之路2.2-PROFINET

    返回 自学工业控制网络之路 自学工业控制网络之路2.2-PROFINET PROFINET由PROFIBUS国际组织(PROFIBUS International,PI)推出,是新一代基于工业以太网技 ...

  9. Java 关键字final的一小结

     * final类不能被继承,没有子类,final类中的方法默认是final的.  * final方法不能被子类的方法覆盖,但可以别继承  (方法)  * final 成员变量 表示常量,只能被赋值一 ...

  10. 【BZOJ2424】[HAOI2010]订货(费用流)

    [BZOJ2424][HAOI2010]订货(费用流) 题面 BZOJ 洛谷 题解 傻逼费用流吧... 一开始理解错意思了,仓库大小为\(m\)的含义是留到下个月最多为\(m\),而不是任意时刻的容量 ...