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("查詢完畢")
}
運行效果:
head插件查詢:
歡迎不吝指出問題,加以改正!