Elasticsearch写入、读取、更新、删除以及批量操作(golang)

目录

1、背景

2、elasticsearch基础操作

2.1 创建es链接客户端

2.2 创建、删除索引

2.3 插入文档

2.3 查询文档

2.4 删除一条文档

2.5 更新文档

2.6 逻辑查询

2.7 滚动查询(批量查询)

2.8 批量插入数据

2.9 批量更新文档

2.10 批量删除文档

3、检索条件设置

4、测试

5、总结 


1、背景

     自从trans到自动驾驶事业部数仓团队后,每天的工作基本都是围绕数据展开,重点是在检索方向上,因此熟练掌握elasticsearch的基本知识成为当务之急,es基础也是提升工作效率的必备技能。

      在项目中,我们使用了官方提供的包:github.com/elastic/go-elasticsearch,但是在使用的过程中,发现这个包比较偏底层,开发成本略高,且不容易理解,因此打算调研一个使用方便、开发成本低的三方包。

      目前网络上使用较多的三方包为:

https://github.com/olivere/elastic

目前最新版本:7.0.32,其发布都是在2年前(2022.3.19),好久没更新了,但是使用热度依然不减。下面就使用这个包来完成es文档的基本操作,包括增加、删除、更新、查询以及对应的批量操作。

2、elasticsearch基础操作

2.1 创建es链接客户端

首先定义一个es实例结构:

type EsInstance struct {
	Client *elastic.Client    // es客户端
	Index  map[string]Indexes // 所有索引
}

type Indexes struct {
	Name    string `json:"name"`    // 索引名称
	Mapping string `json:"mapping"` // 索引结构
}

创建实例:

package elastic

import (
	"github.com/olivere/elastic/v7"
)

type EsInstance struct {
	Client *elastic.Client    // es客户端
	Index  map[string]Indexes // 所有索引
}

type Indexes struct {
	Name    string `json:"name"`    // 索引名称
	Mapping string `json:"mapping"` // 索引结构
}

func NewEsInstance() (*EsInstance, error) {
	client, err := elastic.NewClient(
		elastic.SetURL("http://127.0.0.1:9200"), // 支持多个服务地址,逗号分隔
		elastic.SetBasicAuth("user_name", "user_password"),
		elastic.SetSniff(false), // 跳过ip检查,默认是true
	)
	if err != nil {
		return &EsInstance{}, err
	}
	return &EsInstance{
		Client: client,
		Index:  map[string]Indexes{},
	}, nil
}

2.2 创建、删除索引

对于索引,如果我们只是要做普通的标签检索,索引的mapping结构不需要单独设置,如果要支持向量检索,索引的mapping是需要手动创建的,创建索引:

func (es *EsInstance) CreateIndex(index string, mapping string) error {
	// 判断索引是否存在
	exists, err := es.Client.IndexExists(index).Do(context.Background())
	if err != nil {
		return err
	}
	// 索引存在,直接返回
	if exists {
		return nil
	}
	// 创建索引
	createIndex, err := es.Client.CreateIndex(index).BodyString(mapping).Do(context.Background()) // 指定索引名称和结构
	if err != nil {
		return err
	}
	if !createIndex.Acknowledged {
		return errors.New("create index failed")
	}
	es.Index[index] = Indexes{Name: index, Mapping: mapping}
	return nil
}

删除索引:

func (es *EsInstance) DeleteIndex(index string) error {
	// 判断索引是否存在
	exists, err := es.Client.IndexExists(index).Do(context.Background())
	if err != nil {
		return err
	}
	// 索引不存在,直接返回
	if !exists {
		return nil
	}
	// 删除索引
	deleteIndex, err := es.Client.DeleteIndex(index).Do(context.Background())
	if err != nil {
		return err
	}
	if !deleteIndex.Acknowledged {
		return errors.New("delete index failed")
	}
	return nil
}

2.3 插入文档

定义文档结构:

type Record struct {
	Index  string `json:"_index"` // 索引名称
	Type   string `json:"_type"`  // 索引类型
	Id     string `json:"_id"`    // 索引ID
	Source Source `json:"source"` // 文档内容
}
type Source struct {
	Id   string `json:"id"`   // 学号
	Name string `json:"name"` // 姓名
	Age  int    `json:"age"`  // 年龄
	Sex  string `json:"sex"`  // 性别
}

插入文档,文档id自定义,如果不传入id,es将会自动生成一个id:


func (es *EsInstance) InsertOneRecord(indexName string, record Record) error {
	_, err := es.Client.Index().Index(indexName).Id(record.Source.Id).BodyJson(record).Do(context.Background())
	if err != nil {
		return err
	}
	return nil
}

2.3 查询文档

根据文档id获取一条文档。

func (es *EsInstance) GetOneRecord(indexName, id string) (*Record, error) {
	record := &Record{}
	fmt.Println("index name: ", indexName, " id: ", id)
	result, err := es.Client.Get().Index(indexName).Id(id).Do(context.Background())
	if err != nil {
		fmt.Printf("Error getting a document: %s\n", err.Error())
		return record, err
	} else {
		fmt.Println("Document got!")
	}
	if result.Found {
		err := json.Unmarshal(result.Source, &record.Source)
		if err != nil {
			fmt.Printf("Error unmarshalling the source: %s\n", err.Error())
		}
	}
	record.Id = result.Id
	record.Index = result.Index
	record.Type = result.Type
	return record, nil
}

2.4 删除一条文档

根据文档id删除一条文档。

func (es *EsInstance) DeleteOneRecord(indexName, id string) error {
	_, err := es.Client.Delete().Index(indexName).Id(id).Do(context.Background())
	if err != nil {
		fmt.Printf("Error deleting a document: %s\n", err.Error())
	}
	return nil
}

2.5 更新文档

根据文档id更新文档:

func (es *EsInstance) UpdateOneRecord(indexName, id string, record Source) error {
	_, err := es.Client.Update().Index(indexName).Id(id).Doc(record).Do(context.Background())
	if err != nil {
		fmt.Printf("Error updating a document: %s\n", err.Error())
	}
	return nil
}

2.6 逻辑查询

根据自定义的dsl进行数据检索,该函数不支持滚动,只能进行单次检索,每次最多1W条:

// 只返回设定的条数,最多10000
func (es *EsInstance) Search(indexName string, size int, query elastic.Query) ([]Record, error) {
	var json = jsoniter.ConfigCompatibleWithStandardLibrary
	records := make([]Record, 0)
	if size > 10000 {
		size = 10000
	} else if size < 1 {
		size = 1
	}
	searchResult, err := es.Client.Search().Index(indexName).Query(query).Size(size).Do(context.Background())
	if err != nil {
		fmt.Printf("Error searching: %s\n", err.Error())
		return records, err
	} else {
		fmt.Println("Search successful!")
	}
	if searchResult.Hits.TotalHits.Value > 0 {
		for _, hit := range searchResult.Hits.Hits {
			record := &Record{}
			err := json.Unmarshal(hit.Source, &record.Source)
			if err != nil {
				fmt.Printf("Error unmarshalling the source: %s\n", err.Error())
			} else {
				record.Id = hit.Id
				records = append(records, *record)
			}
		}
	}
	return records, err
}

2.7 滚动查询(批量查询)

滚动查询的意思是,分批多次查询,直到检索到全部数据:

// 滚动搜索,每次最多1000条
func (es *EsInstance) SearchScroll(indexName string, size int, query elastic.Query) ([]Record, error) {
	var json = jsoniter.ConfigCompatibleWithStandardLibrary
	records := make([]Record, 0)

	if size > 1000 {
		size = 1000
	} else if size < 1 {
		size = 1
	}
	searchResult, err := es.Client.Scroll(indexName).Query(query).Size(size).Do(context.Background())
	if err != nil {
		fmt.Printf("Error searching: %s\n", err.Error())
		return records, err
	}
	if searchResult.Hits.TotalHits.Value > 0 {
		for _, hit := range searchResult.Hits.Hits {
			record := &Record{}
			err := json.Unmarshal(hit.Source, &record.Source)
			if err != nil {
				fmt.Printf("Error unmarshalling the source: %s\n", err.Error())
			} else {
				record.Id = hit.Id
				record.Index = hit.Index
				record.Type = hit.Type
				records = append(records, *record)
			}
		}
	}
	scrollId := searchResult.ScrollId
	fmt.Printf("\n..........begin scroll...........\n")
	for {
		scrollResult, err := es.Client.Scroll().ScrollId(scrollId).Do(context.Background())
		if err != nil {
			fmt.Printf("Error searching: %s\n", err.Error())
			return records, err
		}
		// 检查是否还有结果
		if scrollResult.Hits.TotalHits.Value == 0 {
			break
		}
		// 更新滚动ID
		scrollId = scrollResult.ScrollId

		// 处理结果
		for _, hit := range scrollResult.Hits.Hits {
			record := &Record{}
			err := json.Unmarshal(hit.Source, &record.Source)
			if err != nil {
				fmt.Printf("Error unmarshalling the source: %s\n", err.Error())
			} else {
				record.Id = hit.Id
				record.Index = hit.Index
				record.Type = hit.Type
				records = append(records, *record)
			}
		}
	}
	es.Client.ClearScroll(scrollId).Do(context.Background())
	return records, err
}

这个函数,只是演示了滚动查询的操作步骤,在实际使用中,不能这样操作,很有可能会出现因为数据量过大导致的内存不够问题,理想的操作步骤应该是滚动一次处理一次数据,而不是把所有数据全部检索出来单次返回。

2.8 批量插入数据

为了提升操作es的性能,批量操作肯定是首选,批量操作主要使用Bulk的api,如下:

func (es *EsInstance) BatchInserRecords(indexName string, records []Record) error {
	req := es.Client.Bulk().Index(indexName)
	for _, record := range records {
		u := &record.Source
		doc := elastic.NewBulkIndexRequest().Id(record.Id).Doc(u)
		req.Add(doc)
	}
	if req.NumberOfActions() < 0 {
		fmt.Printf("no actions to bulk insert\n")
		return nil
	}
	if _, err := req.Do(context.Background()); err != nil {
		fmt.Println("bulk insert error:" + err.Error())
		return err
	}
	return nil
}

2.9 批量更新文档

批量更新和批量插入大同小异,值更换一个api即可:

func (es *EsInstance) BatchUpdateRecords(indexName string, records []Record) error {
	req := es.Client.Bulk().Index(indexName)
	for _, record := range records {
		u := &record.Source
		doc := elastic.NewBulkUpdateRequest().Id(record.Id).Doc(u)
		req.Add(doc)
	}
	if req.NumberOfActions() < 0 {
		fmt.Printf("no actions to bulk insert\n")
		return nil
	}
	if _, err := req.Do(context.Background()); err != nil {
		fmt.Println("bulk insert error:" + err.Error())
		return err
	}
	return nil
}

2.10 批量删除文档

func (es *EsInstance) BatchDeleteRecords(indexName string, records []Record) error {
	req := es.Client.Bulk().Index(indexName)
	for _, record := range records {
		doc := elastic.NewBulkDeleteRequest().Id(record.Id)
		req.Add(doc)
	}
	if req.NumberOfActions() < 0 {
		fmt.Printf("no actions to bulk insert\n")
		return nil
	}
	if _, err := req.Do(context.Background()); err != nil {
		fmt.Println("bulk insert error:" + err.Error())
		return err
	}
	return nil
}

3、检索条件设置

一般情况下,bool查询使用的比较多,就以bool查询为例说明,构造bool查询:

boolQuery := elastic.NewBoolQuery()

增加一个match查询条件:

boolQuery.Filter(elastic.NewMatchQuery("tag_name", "turn_left"))

增加一个range查询:

boolQuery.Filter(elastic.NewRangeQuery("start_time").Gte(1723737600000))

其他各类的检索条件我们可以根据自身需要进行设置,如果对dsl有一定的了解,这些api使用起来非常简单。

4、测试

假设要检索某辆车、某个时间段内路口左转的数据,构造dsl代码如下:

boolQuery.Filter(elastic.NewMatchQuery("tag_name", "turn_left"))
boolQuery.Filter(elastic.NewMatchQuery("car_id", "京A0001"))
boolQuery.Filter(elastic.NewRangeQuery("start_time").Gte(1723737600000))
boolQuery.Filter(elastic.NewRangeQuery("end_time").Lte(1723823999000))

之后调用2.7章节的滚动查询函数,则可以检索出所有数据:

SearchScroll("index_2024", 4000, boolQuery)

5、总结 

以上总共介绍了10个api,基本上就满足了日常对es操作的需求,测试、验证适用于使用单条检索,这样方便验证数据的准确性,批量操作用于验证数据、逻辑没有问题后的操作,支持批量主要是为了提升效率,以快速完成对数据的操作。 

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值