go-elasticsearch是ES官方提供的Go语言客户端。本文将结合ES 7.12对这个库做一个基本入门使用的演示。

1.开发环境准备

创建一个空的项目,并使用go moudles引入go-elasticsearch的依赖:

1mkdir go-es-showcase
2cd go-es-showcase
3go mod init go-es-showcase
4export GOPROXY="https://goproxy.io"
5go get github.com/elastic/go-elasticsearch/[email protected]
6go get github.com/stretchr/testify

这里基于go testing单元测试编码风格来演示对go-elasticsearch这个库的使用。 首先在项目的根目录下创建一个名称为go_esclient_test.go,并演示一下如何创建客户端:

 1package main
 2
 3import (
 4	"testing"
 5
 6	es "github.com/elastic/go-elasticsearch/v7"
 7	"github.com/stretchr/testify/assert"
 8)
 9
10var (
11	client *es.Client
12)
13
14func init() {
15	var err error
16	client, err = es.NewClient(es.Config{
17		Addresses: []string{"http://localhost:9200"},
18		Username:  "username",
19		Password:  "password",
20	})
21	if err != nil {
22		log.Fatal(err)
23	}
24}
25
26func TestNewESClient(t *testing.T) {
27	t.Log(client.Info())
28}

2.索引的创建、修改和删除

创建索引:

 1func TestCreateIndex(t *testing.T) {
 2	a := assert.New(t)
 3	response, err := client.Indices.Create("book-0.1.0", client.Indices.Create.WithBody(strings.NewReader(`
 4	{
 5		"aliases": {
 6			"book":{}
 7		},
 8		"settings": {
 9			"analysis": {
10				"normalizer": {
11					"lowercase": {
12						"type": "custom",
13						"char_filter": [],
14						"filter": ["lowercase"]
15					}
16				}
17			}
18		},
19		"mappings": {
20			"properties": {
21				"name": {
22					"type": "keyword",
23					"normalizer": "lowercase"
24				},
25				"price": {
26					"type": "double"
27				},
28				"summary": {
29					"type": "text",
30					"analyzer": "ik_max_word"
31				},
32				"author": {
33					"type": "keyword"
34				},
35				"pubDate": {
36					"type": "date"
37				},
38				"pages": {
39					"type": "integer"
40				}
41			}
42		}
43	}
44	`)))
45	a.Nil(err)
46	t.Log(response)
47}

查看索引信息:

1func TestGetIndex(t *testing.T) {
2	a := assert.New(t)
3	response, err := client.Indices.Get([]string{"book"})
4	a.Nil(err)
5	t.Log(response)
6}

删除索引:

1func TestDeleteIndex(t *testing.T) {
2	a := assert.New(t)
3	response, err := client.Indices.Delete([]string{"book-0.1.0"})
4	a.Nil(err)
5	t.Log(response)
6}

3.文档的创建、修改和删除

创建文档:

 1type Book struct {
 2	Author  string     `json:"author"`
 3	Name    string     `json:"name"`
 4	Pages   int        `json:"pages"`
 5	Price   float64    `json:"price"`
 6	PubDate *time.Time `json:"pubDate"`
 7	Summary string     `json:"summary"`
 8}
 9
10func TestCreateDocument(t *testing.T) {
11	a := assert.New(t)
12	body := &bytes.Buffer{}
13	pubDate := time.Now()
14	err := json.NewEncoder(body).Encode(&Book{
15		Author:  "金庸",
16		Price:   96.0,
17		Name:    "天龙八部",
18		Pages:   1978,
19		PubDate: &pubDate,
20		Summary: "...",
21	})
22	a.Nil(err)
23	response, err := client.Create("book", "10001", body)
24	a.Nil(err)
25	t.Log(response)
26}

覆盖性更新文档,如果给定的文档ID不存在,将创建文档:

 1func TestIndexDocument(t *testing.T) {
 2	a := assert.New(t)
 3	body := &bytes.Buffer{}
 4	pubDate := time.Now()
 5	err := json.NewEncoder(body).Encode(&Book{
 6		Author:  "金庸",
 7		Price:   96.0,
 8		Name:    "天龙八部",
 9		Pages:   1978,
10		PubDate: &pubDate,
11		Summary: "...",
12	})
13	a.Nil(err)
14	response, err := client.Index("book", body, client.Index.WithDocumentID("10001"))
15	a.Nil(err)
16	t.Log(response)
17}

局部性更新文档,下面的代码借助go json的omitempty,在将更新数据对象序列化成json,可以只序列化非零值字段,实现局部更新。 实际项目采用这种方式时,需要注意某个字段的零值具有业务意义时,可以采用对应的指针类型实现。

 1type doc struct {
 2	Doc interface{} `json:"doc"`
 3}
 4
 5type Book struct {
 6	Author  string     `json:"author,omitempty"`
 7	Name    string     `json:"name,omitempty"`
 8	Pages   int        `json:"pages,omitempty"`
 9	Price   float64    `json:"price,omitempty"`
10	PubDate *time.Time `json:"pubDate,omitempty"`
11	Summary string     `json:"summary,omitempty"`
12}
13
14func TestPartialUpdateDocument(t *testing.T) {
15	a := assert.New(t)
16	body := &bytes.Buffer{}
17	err := json.NewEncoder(body).Encode(&doc{
18		Doc: &Book{
19			Name: "《天龙八部》",
20		},
21	})
22	a.Nil(err)
23	response, err := client.Update("book", "10001", body)
24	a.Nil(err)
25	t.Log(response)
26}

删除文档:

1func TestDeleteDocument(t *testing.T) {
2	a := assert.New(t)
3	response, err := client.Delete("book", "10001")
4	a.Nil(err)
5	t.Log(response)
6}

获取文档:

1func TestGetDocument(t *testing.T) {
2	a := assert.New(t)
3	response, err := client.Get("book", "10001")
4	a.Nil(err)
5	t.Log(response)
6}

4.文档的批量创建和删除

批量操作对应ES的REST API是:

1POST /<target>/_bulk
2{ "index" : { "_id" : "1" } }
3{ "field1" : "value1" }
4{ "delete" : { "_id" : "2" } }
5{ "create" : { "_id" : "3" } }
6{ "field1" : "value3" }
7{ "update" : {"_id" : "1" } }
8{ "doc" : {"field2" : "value2"} }

对应index, create, update操作,提交的数据都是由两行组成,第一行是meta数据,描述操作信息,第二行是具体提交的数据,对于delete操作只有一行meta数据。 对照REST API,在go client的示例代码如下:

 1func TestBulk(t *testing.T) {
 2	createBooks := []*Book{
 3		{
 4			ID:     "10002",
 5			Name:   "神雕侠侣",
 6			Author: "金庸",
 7		},
 8		{
 9			ID:     "10003",
10			Name:   "射雕英雄传",
11			Author: "金庸",
12		},
13	}
14	deleteBookIds := []string{"10001"}
15
16	a := assert.New(t)
17	body := &bytes.Buffer{}
18	for _, book := range createBooks {
19		meta := []byte(fmt.Sprintf(`{ "index" : { "_id" : "%s" } }%s`, book.ID, "\n"))
20		data, err := json.Marshal(book)
21		a.Nil(err)
22		data = append(data, "\n"...)
23		body.Grow(len(meta) + len(data))
24		body.Write(meta)
25		body.Write(data)
26	}
27	for _, id := range deleteBookIds {
28		meta := []byte(fmt.Sprintf(`{ "delete" : { "_id" : "%s" } }%s`, id, "\n"))
29		body.Grow(len(meta))
30		body.Write(meta)
31	}
32	t.Log(body.String())
33
34	response, err := client.Bulk(body, client.Bulk.WithIndex("book"))
35	a.Nil(err)
36	t.Log(response)
37}

5.文档搜索

下面代码演示了go-elasticsearch提供的搜索查询功能,实际中查询请求体建议使用go template动态生成。

 1func TestSearch(t *testing.T) {
 2	a := assert.New(t)
 3	body := &bytes.Buffer{}
 4	body.WriteString(`
 5	{
 6		"_source":{
 7		  "excludes": ["author"]
 8		}, 
 9		"query": {
10		  "match_phrase": {
11			"author": "古龙"
12		  }
13		},
14		"sort": [
15		  {
16			"pages": {
17			  "order": "desc"
18			}
19		  }
20		], 
21		"from": 0,
22		"size": 5
23	}
24	`)
25	response, err := client.Search(client.Search.WithIndex("book"), client.Search.WithBody(body))
26	a.Nil(err)
27	t.Log(response)
28}

参考