Golang学习篇—elasticsearch游标分页查询

1. 需求

业务需求:ES中修改历史数据中的某个字段或增加字段,历史上亿的数据,使用游标分页查询的方式查询大量历史数据。

《elasticsearch权威指南》直接看官网在线版的

2. 使用第三方包

使用第三方包:github.com/olivere/elastic

3. 代码样例 

esclient.go

package ESOperation

import (
	"BehaviorAnalysisSystem/ESDataModify/global"
	"context"
	"encoding/json"
	"github.com/olivere/elastic"
	"github.com/olivere/elastic/config"
	"strings"
	"time"
)

//ES中时间字段名,UTC时间
const (
	TimeKey = "MARK_TIME"
	TypeKey = "docs" //ES中文档类型名
)

type ESClient struct {
	elsticClent *elastic.Client
}

func NewESClient() *ESClient {
	return &ESClient{}
}

// 初始化客户端
func (this *ESClient) ESInit(ip, port string) error {

	var err error
	//获取ES信息
	URL := "http://" + ip + ":" + port + "/"
	global.Log.Debug("ES信息:%v", URL)

	//获取elastic日志路径
	Infolog, _, _ := global.GetElasticlogPath()
	global.Log.Debug("elastic日志路径:%v", Infolog)

	//elastic配置
	elsticConfig := config.Config{
		URL:      URL,
		Infolog:  Infolog,
		Errorlog: Infolog,
		Tracelog: Infolog,
	}
	this.elsticClent, err = elastic.NewClientFromConfig(&elsticConfig)
	if err != nil {
		global.Log.Error("ES客户端初始化失败,error info:%v", err)
		return err
	}
	global.Log.Info("ES客户端初始化成功")

	//测试ES
	info, code, err := this.elsticClent.Ping(URL).Do(context.Background())
	if err != nil {
		global.Log.Error("[Init]elsticClent.Ping error,error info:%v", err.Error())
		return err
	}
	global.Log.Debug("elasticsearch returned with code %d and version %s:", code, info.Version.Number)
	return nil
}

// 查询
func (this *ESClient) ESQuery(indexKey, typeKey, startTime, endTime string) error {

	//转换时间格式
	//startUTCTime := this.trans2UTCTime(startTime)
	//endUTCTime := this.trans2UTCTime(endTime)
	startUTCTime := startTime
	endUTCTime := endTime

	//查询时间段
	q := elastic.NewRangeQuery(TimeKey)
	q.Gte(startUTCTime)
	q.Lt(endUTCTime)
	global.Log.Debug("timeKey:%s,startTime:%s,endTime:%s", TimeKey, startUTCTime, endUTCTime)

	//查询ES
	var startIndex int = 0
	//ES一次查询数量
	const OnceQueryNum int = 1

	searchResult, err := this.elsticClent.Search().
		Index(indexKey).
		Type(typeKey).
		Query(q).
		From(startIndex).Size(OnceQueryNum).
		Do(context.TODO())
	if nil != err {
		global.Log.Error("ES查询错误,error info:%v", err.Error())
		return err
	}

	//判断是否查询到文档
	if searchResult.Hits == nil {
		global.Log.Error("ES查询到的文档为nil")
		return err
	}
	global.Log.Debug("ES查询到的命中数据条数TotalHits:%v", searchResult.Hits.TotalHits)

	//遍历查询到的文档组合服务操作数据对象切片
	for _, hit := range searchResult.Hits.Hits {
		item := make(map[string]interface{})
		err := json.Unmarshal(*hit.Source, &item)
		if err != nil {
			global.Log.Error("[GetServiceOperation]json.Unmarshal error,error info:%v", err.Error())
			continue
		}
		global.Log.Debug("ES查询到的数据条数TotalHits:%v", hit)
		global.Log.Debug("item:%v", item)

		global.Log.Debug("SS_ID:%v", item["SS_ID"].(string))

		if _, ok := item["SS_APE_INST_ADDR_CODE"]; ok {
			areaCode := "610502" //存在
			item["SS_APE_INST_ADDR_CODE"] = areaCode
		}

		if _, ok := item["SS_APE_INST_ADDR"]; ok {
			//存在
			areaCode := "610500"
			item["SS_APE_INST_ADDR"] = areaCode
			global.Log.Debug("存在:%v", item["SS_APE_INST_ADDR"].(string))
		} else {
			areaCode := "610500"
			item["SS_APE_INST_ADDR"] = areaCode
			global.Log.Debug("不存在")
		}

		//修改原始数据
		err = this.ESModify(hit.Index, hit.Type, hit.Id, item)
		if err != nil {
			return err
		}
	}

	//判断是否查询完
	if int64(startIndex+len(searchResult.Hits.Hits)) >= searchResult.Hits.TotalHits {
		//break
	}

	//更新下次查询起始位置
	startIndex += len(searchResult.Hits.Hits)

	return nil
}

// 游标查询
func (this *ESClient) ESScrollQuery(indexKey, keepAlive string, size int) error {

	//初始化ScrollID 取出第一条数据
	res, err := this.elsticClent.Scroll(indexKey).Scroll(keepAlive).Size(size).Do(context.TODO())
	if err != nil {
		global.Log.Error("elastic首次查询游标失败:%v", err)
		return err
	}
	global.Log.Debug("首次游标分页查询ScrollID:%v", res.ScrollId)

	if res.ScrollId == "" {
		global.Log.Error("elastic首次查询游标为空:%v", indexKey)
		return err
	}

	//遍历查询到的文档组合服务操作数据对象切片
	for _, hit := range res.Hits.Hits {
		item := make(map[string]interface{})
		err := json.Unmarshal(*hit.Source, &item)
		if err != nil {
			global.Log.Error("json.Unmarshal error,error info:%v", err.Error())
			continue
		}

		//根据设备ID查找组织编码
		if _, ok := item["DEVICE_ID"]; !ok {
			global.Log.Debug("设备ID不存在")
			continue
		}
		global.Log.Debug("设备ID存在:%v", item["DEVICE_ID"])


		if _, ok := item["SS_APE_INST_ADDR"]; ok {
			areaCode := "610500" //存在
			item["SS_APE_INST_ADDR"] = areaCode
		} else {
			areaCode := "610500"
			item["SS_APE_INST_ADDR"] = areaCode
		}

		//修改原始数据
		err = this.ESModify(hit.Index, hit.Type, hit.Id, item)
		if err != nil {
			return err
		}

		global.Log.Debug("ID:%v, *hit.Source:%v", hit.Id, string(*hit.Source)) //string(*hit.Source)
	}

	//记录分页查询起始位置
	startIndex := len(res.Hits.Hits)
	global.Log.Debug("索引%v数据总量:%v,已经获取:%v", indexKey, res.Hits.TotalHits, startIndex)

	scrollID := res.ScrollId
	for {
		// 根据ScrollID检索下一个批次的结果,注意:初始搜索请求和每个后续滚动请求返回一个新的_scroll_id,只有最近的_scroll_id才能被使用。
		searchResult, err := this.elsticClent.Scroll("1m").ScrollId(scrollID).Do(context.TODO())
		if err != nil && !strings.Contains(err.Error(), "EOF") {
			global.Log.Error("elastic游标查询数据失败:%v", err)
			return err
		}

		//判断游标ID
		if searchResult.ScrollId == "" {
			global.Log.Error("elastic首次查询游标为空:%v", indexKey)
			return err
		}
		scrollID = res.ScrollId

		//判断是否查询到文档
		if searchResult.Hits == nil {
			global.Log.Error("游标查询到的文档为nil")
			return err
		}
		global.Log.Debug("ES查询到的命中数据条数TotalHits:%v", searchResult.Hits.TotalHits)

		//遍历查询到的文档组合服务操作数据对象切片
		for _, hit := range searchResult.Hits.Hits {
			item := make(map[string]interface{})
			err := json.Unmarshal(*hit.Source, &item)
			if err != nil {
				global.Log.Error("json.Unmarshal error,error info:%v", err.Error())
				continue
			}
			global.Log.Debug("SS_ID:%v", item["SS_ID"].(string))
			if _, ok := item["SS_APE_INST_ADDR"]; ok {
				areaCode := "610500" //存在
				item["SS_APE_INST_ADDR"] = areaCode
			} else {
				areaCode := "610500"
				item["SS_APE_INST_ADDR"] = areaCode
			}

			//修改原始数据
			err = this.ESModify(hit.Index, hit.Type, hit.Id, item)
			if err != nil {
				continue
			}

			global.Log.Debug("数据ID:%v, *hit.Source:%v", hit.Id, string(*hit.Source)) //string(*hit.Source)
		}

		//判断是否分页查询完毕
		if int64(startIndex+len(searchResult.Hits.Hits)) >= searchResult.Hits.TotalHits {
			break
		}
		//更新下次分页查询起始位置
		startIndex += len(searchResult.Hits.Hits)
		global.Log.Debug("索引%v数据总量:%v,已经获取:%v", indexKey, searchResult.Hits.TotalHits, startIndex)
	}

	// 清除游标
	_, err = this.elsticClent.ClearScroll().ScrollId(res.ScrollId).Do(context.TODO())
	if err != nil {
		global.Log.Error("清除游标失败,error info:%v", err)
		return err
	}
	global.Log.Debug("清除游标成功")
	return nil
}

// 修改原始数据
func (this *ESClient) ESModify(index, typekey, id string, item map[string]interface{}) error {
	global.Log.Debug("index:%v,type:%v,id:%v", index, typekey, id)
	_, err := this.elsticClent.Update().
		Index(index).
		Type(typekey).
		Id(id).
		Doc(item).
		Do(context.Background())
	if err != nil {
		global.Log.Error("ES修改失败,index:%v,type:%v,id:%v,error info:%v", index, typekey, id, err)
	}
	global.Log.Error("修改索引%v成功", index)
	return nil
}

// 增加字段原始数据
func (this *ESClient) ESAddFiled(index, typekey, id string, item map[string]interface{}) error {
	global.Log.Debug("index:%v,type:%v,id:%v", index, typekey, id)
	_, err := this.elsticClent.Update().
		Index(index).
		Type(typekey).
		Id(id).
		Doc(item).
		Do(context.Background())
	if err != nil {
		global.Log.Error("ES增加字段失败,index:%v,type:%v,id:%v,error info:%v", index, typekey, id, err)
	}
	global.Log.Error("增加索引%v成功", index)
	return nil
}

func (this *ESClient) trans2UTCTime(strLocalTime string) string {
	localTime, _ := time.ParseInLocation("2006-01-02 15:04:05", strLocalTime, time.Local)
	utcTime := localTime.In(time.UTC)
	strUtcTime := utcTime.Format("2006-01-02T15:04:05.000Z")
	return strUtcTime
}

 

测试用例:

package ESOperation

import (
	"BehaviorAnalysisSystem/ESDataModify/global"
	"testing"
)

const (
	IndexKey = "t_vias_sna_motor_vehicle*"
)

func TestESClient_ESInit(t *testing.T) {
	if !global.Init("test") {
		return
	}

	defer func() {
		global.Log.Flush()
	}()
	es := NewESClient()
	err := es.ESInit("172.20.32.244", "9200")
	if err != nil {
		global.Log.Error("初始化失败")
	}
	global.Log.Info("初始化成功")

	//err = es.ESQuery(IndexKey, TypeKey, "2010-08-01 13:12:59", "2010-08-01 13:13:00")
	//if err != nil {
	//	global.Log.Error("查询失败")
	//}
	//global.Log.Error("查询成功")

	err = es.ESScrollQuery(IndexKey, "5m", 1000)
	if err != nil {
		global.Log.Error("查询失败")
	}
	global.Log.Error("查询完毕")
}

运行效果:

Golang学习篇—elasticsearch游标分页查询_第1张图片

head插件查询:

Golang学习篇—elasticsearch游标分页查询_第2张图片


欢迎不吝指出问题,加以改正!

你可能感兴趣的:(Golang)