zoukankan      html  css  js  c++  java
  • Bleve代码阅读(一)——新建索引

    引言

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

    1 新建索引

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

    // open a new index
    mapping := bleve.NewIndexMapping()
    index, err := bleve.New("example.bleve", mapping)
    if err != nil {
    	fmt.Println(err)
    	return
    }
    

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

    1.1 新建一个IndexMapping

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

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

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

    func NewIndexMapping() *mapping.IndexMappingImpl {
    	return mapping.NewIndexMapping()
    }
    

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

    // NewIndexMapping creates a new IndexMapping that will use all the default indexing rules
    func NewIndexMapping() *IndexMappingImpl {
    	return &IndexMappingImpl{
    		TypeMapping:           make(map[string]*DocumentMapping),
    		DefaultMapping:        NewDocumentMapping(),
    		TypeField:             defaultTypeField,
    		DefaultType:           defaultType,
    		DefaultAnalyzer:       defaultAnalyzer,
    		DefaultDateTimeParser: defaultDateTimeParser,
    		DefaultField:          defaultField,
    		IndexDynamic:          IndexDynamic,
    		StoreDynamic:          StoreDynamic,
    		DocValuesDynamic:      DocValuesDynamic,
    		CustomAnalysis:        newCustomAnalysis(),
    		cache:                 registry.NewCache(),
    	}
    }
    

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

    // An IndexMappingImpl controls how objects are placed
    // into an index.
    // First the type of the object is determined.
    // Once the type is know, the appropriate
    // DocumentMapping is selected by the type.
    // If no mapping was determined for that type,
    // a DefaultMapping will be used.
    type IndexMappingImpl struct {
    	TypeMapping           map[string]*DocumentMapping `json:"types,omitempty"`
    	DefaultMapping        *DocumentMapping            `json:"default_mapping"`
    	TypeField             string                      `json:"type_field"`
    	DefaultType           string                      `json:"default_type"`
    	DefaultAnalyzer       string                      `json:"default_analyzer"`
    	DefaultDateTimeParser string                      `json:"default_datetime_parser"`
    	DefaultField          string                      `json:"default_field"`
    	StoreDynamic          bool                        `json:"store_dynamic"`
    	IndexDynamic          bool                        `json:"index_dynamic"`
    	DocValuesDynamic      bool                        `json:"docvalues_dynamic,omitempty"`
    	CustomAnalysis        *customAnalysis             `json:"analysis,omitempty"`
    	cache                 *registry.Cache
    }
    

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

    TypeMapping :一个map类型,Key是string类型,表示文档的类型。Value是DocumentMapping类型的指针,表示该类型文档对应的DocumentMapping。
    DefaultMapping:一个DocumentMapping类型的指针。当文档的类型未知时,使用的默认DocumentMapping。

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

    1.2 新建一个索引文件

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

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

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

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

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

    err := mapping.Validate()
    if err != nil {
    	return nil, err
    }
    

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

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

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

    rv := indexImpl{
    		path: path,
    		name: path,
    		m:    mapping,
    		meta: newIndexMeta(indexType, kvstore, kvconfig),
    }
    rv.stats = &IndexStat{i: &rv}
    

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

    if path != "" {
    	err = rv.meta.Save(path)
    	if err != nil {
    		return nil, err
    	}
    	kvconfig["create_if_missing"] = true
    	kvconfig["error_if_exists"] = true
    	kvconfig["path"] = indexStorePath(path)
    } else {
    	kvconfig["path"] = ""
    }
    

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

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

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

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

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

    err = rv.i.Open()
    if err != nil {
    	if err == index.ErrorUnknownStorageType {
    		return nil, ErrorUnknownStorageType
    	}
    	return nil, err
    }
    

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

    mappingBytes, err := json.Marshal(mapping)
    if err != nil {
    	return nil, err
    }
    err = rv.i.SetInternal(mappingInternalKey, mappingBytes)
    if err != nil {
    	return nil, err
    }
    

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

    indexStats.Register(&rv)
    

    注册index的统计信息。

    1.3 相关Golang特性说明

    1.3.1 init()函数

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

    var Config *configuration
    

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

    func init() {
    	bootStart := time.Now()
    
    	// build the default configuration
    	Config = newConfiguration()
    
    	// set the default highlighter
    	Config.DefaultHighlighter = html.Name
    
    	// default kv store
    	Config.DefaultKVStore = ""
    
    	// default mem only kv store
    	Config.DefaultMemKVStore = gtreap.Name
    
    	// default index
    	Config.DefaultIndexType = upsidedown.Name
    
    	bootDuration := time.Since(bootStart)
    	bleveExpVar.Add("bootDuration", int64(bootDuration))
    	indexStats = NewIndexStats()
    	bleveExpVar.Set("indexes", indexStats)
    
    	initDisk()
    }
    

    对于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。

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

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

    // ojb是indexMappingImpl结构的一个实例
    t := reflect.TypeOf(obj)
    for i := 0; i < t.NumField(); i++ {
    	if t.Field(i).CanInterface(){
    		fmt.Printf("%s %s = %v -tag:%s 
    ", 
    				t.Field(i).Name, 
    				t.Field(i).Type, 
    				v.Field(i).Interface(), 
    				t.Field(i).Tag)
    	}
    }
    

    在我们的例子中,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,功能类似。

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

    1.4 实践落地

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

    mapping := bleve.NewIndexMapping()
    index, err := bleve.New("example.bleve", mapping)
    if err != nil {
        fmt.Println(err)
        return
    }
    

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

  • 相关阅读:
    工具函数(代码块的大小,代码块起始地址,提升进程权限)
    在共享DLL中使用MFC 和在静态库中使用MFC的区别
    虚拟机检测绕过总结--不定时更新
    OSGI原形(.NET)
    iOS开发技术分享(1)— iOS本地数据存储
    将JSON映射为实体对象(iOS篇)
    灵活的路由(上)
    github开源项目
    EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态
    实体能否处于非法状态
  • 原文地址:https://www.cnblogs.com/terencezhou/p/10104219.html
Copyright © 2011-2022 走看看