go语言如何使用elastic官方客户端go-elasticsearch/v8实现数据批量更新

go语言如何使用elastic官方客户端go-elasticsearch/v8实现数据批量更新

go语言的官方客户端

Elasticsearch 的官方 Go 客户端是由 Elastic 开发、维护和支持的客户端系列的最新成员之一。 初始版本于 2019 年初发布,并在过去几年中逐渐成熟,获得了重试请求、发现集群节点和各种辅助组件等功能。

以上来自”Elastic 中国社区官方博客

现在最新版已经是v8了,就在不久前,我刚刚更新到新鲜出炉的v8@8.9.0。由于v8版本变动较大,网上不多的基于golang的例子都几乎不能用了,最好还是参考上边所提到的Elastic 中国社区官方博客和官网上的例子。

Bulk功能必须使用es.Client

一开始我选择了使用es.TypedClient,虽然使用起来麻烦点儿,但毕竟是强类型的,使用还算是顺利的,直到我开始打算使用Bulk批量更新。到目前为止,我在必应上是搜索不到基于v8的Bulk使用例子,没办法只好在github官网的源代码库里找到_example目录下单范例,后来还找到这篇文章

引用博文中的一段话:

 One of the most common use cases for any Elasticsearch client is indexing documents into Elasticsearch as quickly and efficiently as possible. The most straightforward option, using the plain Elasticsearch Bulk API, comes with a lot of drawbacks: you have to manually prepare the meta and data pairs of the payload, divide the payload into batches, deserialize the response, inspect the results for errors, display a report, and so on. The default example in the repository demonstrates quite eloquently how involved it all is.
 
 For that reason, the client provides a helper component, esutil.BulkIndexer, similar to bulk helpers in other clients:

基于同样的原因,我也选择了使用esutil.BulkIndexer,因为看上去的确方便了许多。但是当我开始着手编码,立马蒙圈了,TypedClient根本没有Bulk这个方法!......过程不提,最后,我还是没辙了,决定同时在连接一个普通的Client类型。

第一次暴击

照猫画虎很简单,很快实现了我的bulk版本方法:

 func (me *YhkbElasticReceiver) bulkUpsertKnowledge(datas  []*ElKnowledge)(){  
  var countSuccessful uint64
  // Create the BulkIndexer
  bulkIndexer, err := esutil.NewBulkIndexer(esutil.BulkIndexerConfig{
  Index:         indexName,        // The default index name
  Client:        me.client,               // The Elasticsearch client
  NumWorkers:    3,       // The number of worker goroutines
  FlushBytes:    102400,  // The flush threshold in bytes
  FlushInterval: 30 * time.Second, // The periodic flush interval
  })
  if err != nil {
  log.Fatalf("Error creating the indexer: %s", err)
  }
  start := time.Now().UTC()
  for _, data:=range datas{
  sid:=data.SID()
  payload:=data.ToJson()
  doc:=esutil.BulkIndexerItem{
  Action: "update",
  Index: indexName,
  DocumentID: sid,
  Body: strings.NewReader(payload),
  OnSuccess: func(ctx context.Context, item esutil.BulkIndexerItem, res esutil.BulkIndexerResponseItem) {
  atomic.AddUint64(&countSuccessful, 1)
  //log.Println("bulk item success")
  },
  OnFailure: func(ctx context.Context, item esutil.BulkIndexerItem, res esutil.BulkIndexerResponseItem, err error) {
  if err != nil {
  log.Printf("[YhkbElReceiver.BulkUpsertKnowledge] ERROR: %s \n", err)
  } else {
  log.Printf("[YhkbElReceiver.BulkUpsertKnowledge] ERROR: %s: %s \n", res.Error.Type, res.Error.Reason)
  }
  },
  }
  err = bulkIndexer.Add(context.Background(), doc)
  if err!=nil{
  log.Println("[YhkbElReceiver.BulkUpsertKnowledge] bulk upsert Add doc fail,",err)
  panic(err)
  }
  } // end loop each data row
  // Close the indexer
  //
  if err := bulkIndexer.Close(context.Background()); err != nil {
  log.Fatalf("Unexpected error: %s", err)
  }
 
  biStats := bulkIndexer.Stats()
 
  // Report the results: number of indexed docs, number of errors, duration, indexing rate
  //
  log.Println(strings.Repeat("-", 80))
  dur := time.Since(start)
  if biStats.NumFailed > 0 {
  log.Printf("[YhkbElReceiver.BulkUpsertKnowledge]总数据[%d]行,其中失败[%d], 耗时 %v (速度:%d docs/秒)\n", biStats.NumAdded, biStats.NumFailed,
  dur.Truncate(time.Millisecond), int64(1000.0/float64(dur/time.Millisecond)*float64(biStats.NumFlushed)),
  )
  } else {
  log.Printf("[YhkbElReceiver.BulkUpsertKnowledge]处理数据[%d]行,耗时%v (速度:%d docs/秒)\n", biStats.NumFlushed, dur.Truncate(time.Millisecond),
  int64(1000.0/float64(dur/time.Millisecond)*float64(biStats.NumFlushed)),
  )
  }
  log.Println(strings.Repeat("-", 80))
 }

马上试运行了一下,立马遭受一万点暴击,出错了:

 [YhkbElReceiver.BulkUpsertKnowledge]处理数据[0]行,其中失败[100], 耗时 19ms (速度:0 docs/秒)

100%的失败了,而且BulkIndexerItem.OnFailure方法毫无动静,真是莫名其妙。还好看到了BulkIndexerConfig有OnError回调,赶紧注册个回调函数,这回终于不再沉默,打印出来了如下的错误:

 error":{"root_cause":[
 {"type":"x_content_parse_exception","reason":"[1:2] [UpdateRequest] unknown field [id]"}],
     "type":"x_content_parse_exception","reason":"[1:2] [UpdateRequest] unknown field [id]"},"status":400}

经过一番网上搜索,说是文档格式不对!后参考了一下直接用HTTP POST实现的代码,灵光乍现,包装了一下我的实体类序列化的JSON字符串,也就是重新拼接了一下上面代码中payload这一行:

 payload:=fmt.Sprintf(`{"doc":%s,"doc_as_upsert":true}`,data.ToJson())

再次运行程序,成功了! w(゚Д゚)w (ノへ ̄、)

 

柒零一 于2023-07-28

 
posted @ 2023-07-31 11:15  柒零壹  阅读(566)  评论(0编辑  收藏  举报