Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在项目中使用hertz client 发送http请求,但是偶尔会出现connection is closed by peer while being in the connection pool,不知道是否client配置有问题,麻烦指点下 #1267

Closed
MakePeng opened this issue Jan 23, 2025 · 1 comment

Comments

@MakePeng
Copy link

Describe the Question

hertz client 发送http请求,但是偶尔会出现connection is closed by peer while being in the connection pool

Reproducible Code

package utils

import (
"context"
"encoding/json"
"github.com/cloudwego/hertz/pkg/app/client"
"github.com/cloudwego/hertz/pkg/app/client/retry"
"github.com/cloudwego/hertz/pkg/common/config"
"github.com/cloudwego/hertz/pkg/common/hlog"
"github.com/cloudwego/hertz/pkg/protocol"
"io"
"net/http"
"net/url"
"sync"
"time"
)

var (
httpClient *client.Client
once sync.Once
//retryableErrPattern = regexp.MustCompile(connection is closed by peer while being in the connection pool)
)

// newClient
//
// @description: hertz客户端初始化
// @return client.Client
// @return error
func newClient() (client.Client, error) {
c, err := client.NewClient(
client.WithDialTimeout(3
time.Second),
client.WithMaxConnsPerHost(1024), //每个主机可能建立的最大连接数
client.WithMaxConnDuration(10
time.Second), //最大的连接持续时间,keep-alive 连接在此持续时间后被关闭
client.WithMaxConnWaitTimeout(10time.Second), //等待空闲连接的最大时间
client.WithClientReadTimeout(10
time.Second), //完整读取响应(包括 body)的最大持续时间
client.WithWriteTimeout(10time.Second), //HTTP 客户端的写入超时时间
client.WithRetryConfig(
retry.WithMaxAttemptTimes(3), // 最大的尝试次数,包括初始调用
retry.WithInitDelay(3
time.Millisecond), // 初始延迟
retry.WithMaxDelay(10time.Millisecond), // 最大延迟,不管重试多少次,策略如何,都不会超过这个延迟
retry.WithMaxJitter(10
time.Millisecond), // 延时的最大扰动,结合 RandomDelayPolicy 才会有效果
retry.WithDelayPolicy(retry.CombineDelay(
retry.FixedDelayPolicy,
retry.BackOffDelayPolicy,
retry.RandomDelayPolicy,
)),
),
)
if err != nil {
return nil, err
}
//重试函数设置
c.SetRetryIfFunc(func(req *protocol.Request, resp *protocol.Response, err error) bool {
// cannot retry if the request body is not rewindable
if req.IsBodyStream() {
return false
}
//判断是否幂等
if isIdempotent(req) {
return true
}
// Retry non-idempotent requests if the server closes
// the connection before sending the response.
//
// This case is possible if the server closes the idle
// keep-alive connection on timeout.
//
// Apache and nginx usually do this.
if err == io.EOF {
return true
}
// 使用正则表达式匹配错误信息
//if err != nil && retryableErrPattern.MatchString(err.Error()) {
//
// return true
//}
if err != nil {
return true
}
return false
})
return c, err
}

// isIdempotent
//
// @description: 判断是否幂等
// @param req
// @return bool
func isIdempotent(req *protocol.Request) bool {
return req.Header.IsGet() ||
req.Header.IsHead() ||
req.Header.IsPut() ||
req.Header.IsDelete() ||
req.Header.IsOptions() ||
req.Header.IsTrace()
}

// InitClient
//
// @description: 初始化全局的HTTP客户端
func InitClient() {
once.Do(func() {
var err error
httpClient, err = newClient()
if err != nil {
// Handle error appropriately, possibly logging or panicking
hlog.Errorf("Failed to initialize Hertz client: %v", err)
panic(err) // or some other error handling strategy
}
})
}

// Do
//
// @description: 执行http请请求方法
// @param method
// @param urlStr
// @param body
// @param headers
// @param params
// @param respType
// @return error
func Do(method, urlStr string, body interface{}, headers map[string]string, params url.Values, respType interface{}) error {
req := &protocol.Request{}
res := &protocol.Response{}
//请求设置
req.SetOptions(config.WithDialTimeout(1time.Second),
config.WithReadTimeout(3
time.Second),
config.WithWriteTimeout(3*time.Second))
//设置请求方式
req.SetMethod(method)
//设置请求url
req.SetRequestURI(urlStr)
//设置请求头
for k, v := range headers {
req.SetHeader(k, v)
}
// 设置查询参数
if params != nil {
req.SetQueryString(params.Encode())
}
//设置body
if body != nil {
req.Header.SetContentTypeBytes([]byte("application/json"))
bodyBytes, err := json.Marshal(body)
if err != nil {
return err
}
req.SetBody(bodyBytes)
}
err := httpClient.Do(context.Background(), req, res)
if err != nil {
return err
}

// 解析响应数据到respType
if respType != nil {
	err = json.Unmarshal(res.Body(), respType)
	if err != nil {
		hlog.Errorf("convert res is fail err:%v bodys :%v status :%v", err, string(res.Body()), res.StatusCode())
		return err
	}
}
return nil

}

// SendGet
//
// @description: 发起GET请求
// @receiver c
// @param url
// @param headers
// @param params
// @param respType
// @return error
func SendGet(url string, headers map[string]string, params url.Values, respType interface{}) error {
return Do(http.MethodGet, url, nil, headers, params, respType)
}

// SendPost
//
// @description: 发起POST请求
// @receiver c
// @param url
// @param body
// @param headers
// @param params
// @param respType
// @return error
func SendPost(url string, body interface{}, headers map[string]string, params url.Values, respType interface{}) error {
//return Do(http.MethodPost, url, body, headers, params, respType)
maxAttempts := 3
delay := 1 * time.Millisecond
var err error
for attempt := 1; attempt <= maxAttempts; attempt++ {
err = Do(http.MethodPost, url, body, headers, params, respType)
if err == nil {
return nil
}

	if attempt < maxAttempts {
		time.Sleep(delay)
	}
}
return err

}

Expected behavior

希望可以规避这种错误的发生,

Screenshots

If applicable, add screenshots to help explain your question.

Hertz version:

github.com/cloudwego/hertz v0.9.5

Environment:

生产

Additional context

Add any other context about the question here.

@xiaost
Copy link
Contributor

xiaost commented Jan 23, 2025

谢谢反馈。

这跟Client配置没有直接关系。请跟你连接的对端讨论关闭行为,日志已经说明得很清楚。
另外你给个的代码不是复现问题的代码,只能说是你的代码。
关于什么叫复现代码,可以自行学习。如果有明确的复现代码再反馈。

@xiaost xiaost closed this as completed Jan 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants