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

1.开发环境准备

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"testing"

	es "github.com/elastic/go-elasticsearch/v7"
	"github.com/stretchr/testify/assert"
)

var (
	client *es.Client
)

func init() {
	var err error
	client, err = es.NewClient(es.Config{
		Addresses: []string{"http://localhost:9200"},
		Username:  "username",
		Password:  "password",
	})
	if err != nil {
		log.Fatal(err)
	}
}

func TestNewESClient(t *testing.T) {
	t.Log(client.Info())
}

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

创建索引:

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

查看索引信息:

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

删除索引:

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

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

创建文档:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type Book struct {
	Author  string     `json:"author"`
	Name    string     `json:"name"`
	Pages   int        `json:"pages"`
	Price   float64    `json:"price"`
	PubDate *time.Time `json:"pubDate"`
	Summary string     `json:"summary"`
}

func TestCreateDocument(t *testing.T) {
	a := assert.New(t)
	body := &bytes.Buffer{}
	pubDate := time.Now()
	err := json.NewEncoder(body).Encode(&Book{
		Author:  "金庸",
		Price:   96.0,
		Name:    "天龙八部",
		Pages:   1978,
		PubDate: &pubDate,
		Summary: "...",
	})
	a.Nil(err)
	response, err := client.Create("book", "10001", body)
	a.Nil(err)
	t.Log(response)
}

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type doc struct {
	Doc interface{} `json:"doc"`
}

type Book struct {
	Author  string     `json:"author,omitempty"`
	Name    string     `json:"name,omitempty"`
	Pages   int        `json:"pages,omitempty"`
	Price   float64    `json:"price,omitempty"`
	PubDate *time.Time `json:"pubDate,omitempty"`
	Summary string     `json:"summary,omitempty"`
}

func TestPartialUpdateDocument(t *testing.T) {
	a := assert.New(t)
	body := &bytes.Buffer{}
	err := json.NewEncoder(body).Encode(&doc{
		Doc: &Book{
			Name: "《天龙八部》",
		},
	})
	a.Nil(err)
	response, err := client.Update("book", "10001", body)
	a.Nil(err)
	t.Log(response)
}

删除文档:

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

获取文档:

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

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func TestBulk(t *testing.T) {
	createBooks := []*Book{
		{
			ID:     "10002",
			Name:   "神雕侠侣",
			Author: "金庸",
		},
		{
			ID:     "10003",
			Name:   "射雕英雄传",
			Author: "金庸",
		},
	}
	deleteBookIds := []string{"10001"}

	a := assert.New(t)
	body := &bytes.Buffer{}
	for _, book := range createBooks {
		meta := []byte(fmt.Sprintf(`{ "index" : { "_id" : "%s" } }%s`, book.ID, "\n"))
		data, err := json.Marshal(book)
		a.Nil(err)
		data = append(data, "\n"...)
		body.Grow(len(meta) + len(data))
		body.Write(meta)
		body.Write(data)
	}
	for _, id := range deleteBookIds {
		meta := []byte(fmt.Sprintf(`{ "delete" : { "_id" : "%s" } }%s`, id, "\n"))
		body.Grow(len(meta))
		body.Write(meta)
	}
	t.Log(body.String())

	response, err := client.Bulk(body, client.Bulk.WithIndex("book"))
	a.Nil(err)
	t.Log(response)
}

5.文档搜索

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

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

参考