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))
}