go-elasticsearch/v8调用报错“read: connection reset by peer”

452 阅读2分钟

go-elasticsearch/v8调用报错“read: connection reset by peer”

我在本地通过Docker部署了ElasticSearch,并使用ES的官方Golang SDK来调用ES,写入一些数据。
ES版本等相关信息:

  1. ES with Docker: V8.11.0
  2. SDK: go-elasticsearch/v8

新建客户端和插入的方法代码如下:

func NewClientV8(ctx context.Context) *elasticsearch.Client {
	cert, err := ioutil.ReadFile("http_ca.crt")
	if err != nil {
		log.Fatalf("Failed to load certificate: %v", err)
	}

	certPool := x509.NewCertPool()
	certPool.AppendCertsFromPEM(cert)

	cfg := elasticsearch.Config{
		Addresses: []string{
			"https://localhost:9200",
		},
		Username: "elastic",
		Password: "bghCDHWKJknLrkr+8ZMF",
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 20,
			TLSClientConfig: &tls.Config{
				RootCAs: certPool,
			},
		},
	}

	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		log.Fatalf("Error creating the client: %s", err)
		return es
	}

	res, err := es.Info()
	if err != nil {
		log.Fatalf("Error getting response: %s", err)
	}
	defer res.Body.Close()

	log.Println(res)
	return es
}

func (*ES8) Bulk(ctx context.Context, docs []BulkRequest) error {

	bulkRequestBody := strings.Builder{}
	for _, doc := range docs {
		metaData := fmt.Sprintf(`{ "index" : { "_index" : "%s", "_id" : "%s" } }%s`, index, doc.Id, "\n")
		docData, err := json.Marshal(doc.Doc)
		if err != nil {
			log.Printf("Error marshaling document: %v", err)
			continue
		}
		bulkRequestBody.WriteString(metaData)
		bulkRequestBody.Write(docData)
		bulkRequestBody.WriteString("\n")
	}

	// Send the bulk request
	req := esapi.BulkRequest{
		Index:   index,
		Body:    strings.NewReader(bulkRequestBody.String()),
		Refresh: "true",
		Pretty:  true,
	}
	res, err := req.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error performing bulk request: %s", err)
		return err
	}
	defer res.Body.Close()

	// if res.IsError() {
	// 	log.Printf("Error[%s]: %s", res.Status(), res.String())
	// } else {
	// 	log.Println("Bulk insert successful", res.String())
	// }
	return nil
}

我写了一个循环,每次插入100条数据分批插入数据,总数据量50w条。
程序一开始运行良好,但当插入一些数据之后,程序报错: Error performing bulk request: read tcp 127.0.0.1:50989->127.0.0.1:9200: read: connection reset by peer

问题定位

出现了这个错误,我第一反应就是连接数过多,那么可能是连接不复用。
我重启了ES集群,并且使用下面命令netstat -an | grep "127.0.0.1.9200" | wc -l来查看连接数量。

不出所料,连接数在程序运行的过程中一直增加:

image.png

我找了很多资料,包括更改ES集群的配置、修改client的最大连接数配置等,都没有解决问题。
我甚至还去go-elasticsearch的github上提了issue:github.com/elastic/go-…

解决问题

最终,我找到了一个类似问题的issue:github.com/elastic/go-…
在Golang的http内置库中,有这么一段话:

It is the caller's responsibility to close Body. The default HTTP client's Transport may not reuse HTTP/1.x "keep-alive" TCP connections if the Body is not read to completion and closed.

回顾我自己的代码,我没有读response的body,这可能是导致连接无法复用的原因。
我修改了代码,新的代码如下:

func (*ES8) Bulk(ctx context.Context, docs []BulkRequest) error {

	bulkRequestBody := strings.Builder{}
	for _, doc := range docs {
		metaData := fmt.Sprintf(`{ "index" : { "_index" : "%s", "_id" : "%s" } }%s`, index, doc.Id, "\n")
		docData, err := json.Marshal(doc.Doc)
		if err != nil {
			log.Printf("Error marshaling document: %v", err)
			continue
		}
		bulkRequestBody.WriteString(metaData)
		bulkRequestBody.Write(docData)
		bulkRequestBody.WriteString("\n")
	}

	// Send the bulk request
	req := esapi.BulkRequest{
		Index:   index,
		Body:    strings.NewReader(bulkRequestBody.String()),
		Refresh: "true",
		Pretty:  true,
	}
	res, err := req.Do(context.Background(), es)
	if err != nil {
		log.Fatalf("Error performing bulk request: %s", err)
		return err
	}
	defer res.Body.Close()
	res.String() // 新加的
}

再重新跑程序,问题解决!连接数维持在一个很低的数量。

写在最后

如果不需要用到http的response body,建议使用如下的写法:

io.Copy(ioutil.Discard, resp.Body)