下面是对Elastic开发的官方Go语言ElasticSearch客户端的综述

这是Go Advent Calendar 2020第八天的文章。

最近在工作中有机会使用ElasticSearch,并在那时发现其官方支持的Go客户端缺乏详细的日本语信息,因此我决定趁此机会整理一下。

go-elasticsearch是一个在GitHub上的项目,位于https://github.com/elastic/go-elasticsearch。

总结

这个官方库是在2019年发布的相对较新的库,已经被认可并维护为Elastic的官方客户端。
https://www.elastic.co/guide/en/elasticsearch/client/index.html

go-elasticsearch客户端有6系和7系两个版本,分别对应Elasticsearch的6系和7系版本。因此,请根据您使用的Elasticsearch版本确定要使用的库的版本。

使用方法

客户创立

在创建客户端时,有两种选择。首先是NewDefaultClient。这个选项不需要参数,但可以通过将ElasticSearch的终端URL放入名为ELASTICSEARCH_URL的环境变量中来自动进行配置。

elasticsearch.NewDefaultClient()

elasticsearch.NewClient(Config)是创建具有各种选项的客户端的方法。当使用Elastic Cloud等服务时,可以使用ID而不是地址进行连接。在这种情况下,忽略了设置在ELASTICSEARCH_URL环境变量中的值。可以包含诸如CACert用于证书和RetryOnStatus用于定义需要重试的状态等选项。

cert, _ := ioutil.ReadFile("path/to/ca.crt")

cfg := elasticsearch.Config{
  Addresses: []string{
    "http://localhost:9200",
    "http://localhost:9201",
  },
  Username: "foo",
  Password: "bar",
  RetryOnStatus: []int{429, 502, 503, 504},
  CACert: cert,
  Transport: &http.Transport{
    MaxIdleConnsPerHost:   10,
    ResponseHeaderTimeout: time.Second,
    DialContext:           (&net.Dialer{Timeout: time.Second}).DialContext,
    TLSClientConfig: &tls.Config{
      MinVersion:         tls.VersionTLS11,
    },
  },
}

elasticsearch.NewClient(cfg)

搜索

在使用搜索引擎的搜索建议等功能时,请按照以下方式使用搜索功能。


  var buf bytes.Buffer
  query := map[string]interface{}{
    "query": map[string]interface{}{
      "match": map[string]interface{}{
        "title": "test",
      },
    },
  }
  if err := json.NewEncoder(&buf).Encode(query); err != nil {
    log.Fatalf("Error encoding query: %s", err)
  }

  // Perform the search request.
  res, err = es.Search(
    es.Search.WithContext(context.Background()),
    es.Search.WithIndex("test"),
    es.Search.WithBody(&buf),
    es.Search.WithTrackTotalHits(true),
    es.Search.WithPretty(),
  )
  if err != nil {
    log.Fatalf("Error getting response: %s", err)
  }
  defer res.Body.Close()

  if res.IsError() {
    var e map[string]interface{}
    if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
      log.Fatalf("Error parsing the response body: %s", err)
    } else {
      // Print the response status and error information.
      log.Fatalf("[%s] %s: %s",
        res.Status(),
        e["error"].(map[string]interface{})["type"],
        e["error"].(map[string]interface{})["reason"],
      )
    }
  }

請參考實際的請求中投遞的Json結構體, 使用map[string]interface{}定義,然後將要搜索的字串放入其中。

发送HTTP请求时的JsonBody如果是这样的

{
  "size": 5,
  "query": {
    "bool": {
      "should": [{
        "match": {
          "word.autocomplete": {
            "query": "え"
          }
        }
      }, {
        "match": {
          "word.readingform": {
            "query": "え",
            "fuzziness": "AUTO",
            "operator": "and"
          }
        }
      }]
    }
  },
}'

用Go语言定义的查询通常就像这样。它确实相当复杂呢……。

query := map[string]interface{}{
    "query": map[string]interface{}{
        "bool": map[string]interface{}{
            "should": []map[string]interface{}{
                {
                    "match": map[string]interface{}{
                        "word.autocomplete": map[string]interface{}{
                            "query": normalized,
                        },
                    },
                },
                {
                    "match": map[string]interface{}{
                        "word.readingform": map[string]interface{}{
                            "query":     normalized,
                            "fuzziness": "AUTO",
                            "operator":  "and",
                        },
                    },
                },
            },
        },
    },
}

然后将其编码为JSON,并将其放入WithBody参数中的Search方法的参数中,并调用Search方法。

  if err := json.NewEncoder(&buf).Encode(query); err != nil {
    log.Fatalf("Error encoding query: %s", err)
  }

  // Perform the search request.
  res, err = es.Search(
    es.Search.WithContext(context.Background()),
    es.Search.WithIndex("test"),
    es.Search.WithBody(&buf),
    es.Search.WithTrackTotalHits(true),
    es.Search.WithPretty(),
  )

可以看出还有许多其他的参数。 使用withSort等可以实现排序等功能。

Search方法的响应似乎是对http.Response进行包装的。另外,可以使用IsError()方法来判断是否发生了500错误等。

不像Search等,Index和Create、Update可以相对简单地进行编写。基本上,go-elastic的esapi包中为每个请求准备了名为XXRequest的类型,因此只需将请求的值放入其中,并调用Do方法执行即可。

请在这里对IsError的响应进行检查。


    tag := Sample{
        ID:   id,
        Name: name,
    }

    reqByte, err := json.Marshal(tag)
    if err != nil {
        return err
    }

    requestReader := bytes.NewReader(reqByte)

    req := esapi.CreateRequest{
        Body:   requestReader,
        Pretty: true,
    }
    res, err := req.Do(ctx, r.client)
    if err != nil {
        return xerrors.Errorf("failed to update with elastic search. %w", err)
    }

    if res.IsError() {
        return xerrors.Errorf("failed to update with elastic search. Not ok. %s", res.Status())
    }
    defer res.Body.Close()

在该类型中,您还可以定义各种选项。让我们试试看UpdateRequest类型。它通常将基本请求的主体存储在Body中,但是我们可以看到可以定义各种选项,如Header和Pretty等。


type UpdateRequest struct {
    Index        string
    DocumentType string
    DocumentID   string

    Body io.Reader

    Fields              []string
    IfPrimaryTerm       *int
    IfSeqNo             *int
    Lang                string
    Parent              string
    Refresh             string
    RetryOnConflict     *int
    Routing             string
    Source              []string
    SourceExcludes      []string
    SourceIncludes      []string
    Timeout             time.Duration
    Version             *int
    VersionType         string
    WaitForActiveShards string

    Pretty     bool
    Human      bool
    ErrorTrace bool
    FilterPath []string

    Header http.Header

    ctx context.Context
}

这是ElasticSearch的Go客户端介绍,由Elastic提供支持。
虽然代码可能有点多,但由于官方提供维护,所以可以放心使用,不会有任何损失。

bannerAds