Posted in

Zincsearch索引创建与检索

文章封面

zincsearch是一个由Golang编写的开源轻量级搜索引擎,虽然暂不支持分布式,但是基本功能均已经满足,对一些小型项目,足以支持大部分使用场景,前面一篇文章 ZincSearch – 轻量级全文搜索引擎(Elasticsearch替代品)已经介绍了Zincsearch的适用场景和安装方法,这篇文章,我们以golang为例,讲一下zincsearch的实际使用。

zincsearch如何创建索引

什么是索引?简单来说,索引就是一张表,包含多个记录,检索就是在表中查询满足条件的记录。

现在我们用代码去创建一个索引,在golang中这个比较简单,因为zincsearch提供有Golang的SDK

下载SDK:go get -u github.com/zinclabs/sdk-go-zincsearch

indexName := "student"
data := zinc.MetaIndexSimple{
 Name:        zinc.PtrString(indexName),
 StorageType: zinc.PtrString("disk"),
 Settings: &zinc.MetaIndexSettings{
   NumberOfShards:   zinc.PtrInt32(3), // 设置3个分片,提高并发性能
   NumberOfReplicas: zinc.PtrInt32(1), // 设置1个副本,保证数据可靠性
},
}
apiClient := client.NewAPIClient(configuration)
resp, r, err := apiClient.Index.Create(context.Background()).Data(data).Execute()
if err != nil {
 fmt.Fprintf(os.Stderr, "Error when calling `Index.Create``: %v\n", err)
 fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
}

这样一来,我们就创建好了一个名为student的索引了,索引数据存储在磁盘上。你可以理解为,已经创建了一张名为student的数据表

zincsearch如何创建模板

zincsearch的模板是什么呢?zincsearch中的模板类似于Elasticsearch中的mapping,可以定义有哪些字段,字段的类型是什么,是否参与索引,是否开启分词,分词策略以及排序聚合等。如果把索引理解为数据表,那么你可以将模板理解为“表结构”。

如果你对Elasticsearch没有了解也没关系,前面说过了,你可以将索引理解为数据库中的一张数据表,但是呢我们只创建了一张表,但是表结构还没有定义,我们可以将索引模板理解为定义表结构。

比如我们定义了一个学生的模板,要检索学生的学号、姓名、座右铭,其中需要通过姓名、学号来进行检索,座右铭只用于展示,那么我们就可以建一个学生模板:

tpl := zinc.MetaTemplate{
 IndexTemplate: &zinc.MetaIndexTemplate{
   IndexPatterns: []string{"student"}, // 匹配所有student开头的索引
   Priority:      zinc.PtrInt32(1),  // 设置模板优先级
   Template: &zinc.MetaTemplateTemplate{
     Settings: &zinc.MetaIndexSettings{
       NumberOfShards:   zinc.PtrInt32(3),
       NumberOfReplicas: zinc.PtrInt32(1),
    },
     Mappings: &zinc.MetaMappings{
       Properties: &map[string]zinc.MetaProperty{
         "name": {
           Type:           zinc.PtrString("text"),     // 文本类型,支持全文搜索
           Highlightable:  zinc.PtrBool(true),         // 支持搜索结果高亮
           Analyzer:       zinc.PtrString("standard"), // 使用标准分析器
           SearchAnalyzer: zinc.PtrString("standard"), // 搜索时使用标准分析器
           Fields: &map[string]zinc.MetaProperty{ // 添加keyword子字段
             "keyword": {
               Type:  zinc.PtrString("keyword"), // 用于精确匹配
               Index: zinc.PtrBool(true),
            },
          },
        },
         "number": {
           Type: zinc.PtrString("numeric"), // 数值类型,支持范围查询
        },
         "motto": {
           Type:  zinc.PtrString("text"), // 关键字类型,用于精确匹配
           Index: zinc.PtrBool(false),       // 不索引,节省空间
        },
      },
    },
  },
},
 Name: zinc.PtrString("student_template"),
}

这里整理了一下zincsearch支持的字段类型,以及每个字段的特性

字段类型(type)说明是否支持排序/聚合是否支持全文搜索
text文本类型,默认用于全文搜索(可分词)❌(不能排序)✅ 支持分词搜索
keyword关键词类型,不分词,适合标签、ID 等✅(可排序)✅ 精确匹配搜索
numeric数值类型,整数或浮点数✅(可排序、聚合)
boolean布尔值类型(true/false)
date日期类型(ISO 8601 格式或自定义格式)

zincsearch如何添加文档

问题又来了,那什么是文档呢?前面以及提到过,可以把索引当作表,模板当作表结构,那文档自然可以联想到表记录了,一个文档对应一行记录。我们先前已经创建了一个学生表,现在我们需要往里面灌入一些数据

添加文档有两种方式,一种是逐行添加,一种是批量添加,由于我们在日常工作中,一般都是批量添加索引文档,所以这里只详细说明批量添加,逐行添加也可以参考

接口文档:https://github.com/zincsearch/sdk-go-zincsearch/blob/main/docs/Document.md#bulkv2

这里用go resty v2来实现go的sdk

type BulkV2Payload struct {
Index   string                   `json:"index"`
Records []map[string]interface{} `json:"records"`
}

func (c *Client) BulkV2(index string, payload *BulkV2Payload) error {
_, err := c.httpClient.R().SetBody(payload).
Post("/api/_bulkv2")
if err != nil {
return err
}

return nil
}

调用:

var bulkData []map[string]interface{}
for _, student := range students {
 bulkData = append(bulkData, map[string]interface{}{
   "_id":         student.Number, // 使用学号作为文档ID
   "number":        student.Number,
   "name":        student.Name,
   "otto":      student.Otto,
})
}

// 提交剩余的文档
if len(bulkData) > 0 {
 payload := &zincsearch.BulkV2Payload{
   Index:   indexName,
   Records: bulkData,
}
 err = client.BulkV2(indexName, payload)
 if err != nil {
   log.Fatalln(err)
}
}

如何检索

前面一张带有数据的数据表就创建完成了,现在我们需要根据业务场景来检索我们需要的数据,现在我们要查询名称叫张三的学生,然后根据学生排序:

package main

import (
   "fmt"
   "io"
   "log"
   "net/http"
   "strings"
)

func main() {
   query = `{
       "search_type": "match",
       "query":
       {
           "term": "张三"
       },
       "from": 0,
       "max_results": 20,
       "sort_fields": ["number"],
       "_source": []
   }`
   req, err := http.NewRequest("POST", "http://localhost:4080/api/games3/_search", strings.NewReader(query))
   if err != nil {
       log.Fatal(err)
  }
   req.SetBasicAuth("admin", "Complexpass#123")
   req.Header.Set("Content-Type", "application/json")
   req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36")

   resp, err := http.DefaultClient.Do(req)
   if err != nil {
       log.Fatal(err)
  }
   defer resp.Body.Close()
   log.Println(resp.StatusCode)
   body, err := io.ReadAll(resp.Body)
   if err != nil {
       log.Fatal(err)
  }
   fmt.Println(string(body))
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注