Go语言中HTTP客户端超时与重试的实现代码
作者:王码码2035哦
1. HTTP客户端的基本概念
HTTP客户端是Go语言中进行网络请求的重要工具,标准库net/http提供了强大的HTTP客户端功能。在实际应用中,合理设置超时和实现重试机制是保证服务稳定性和可靠性的关键。
本文将详细介绍Go语言中的HTTP客户端,重点讲解超时设置和重试机制的实现,帮助开发者构建健壮的HTTP客户端。
2. 基础HTTP客户端
2.1 基本用法
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}2.2 自定义客户端
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}3. 超时设置
3.1 连接超时
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func main() {
client := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
},
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Success")
}3.2 完整的超时配置
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func createHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
},
Timeout: 30 * time.Second,
}
}
func main() {
client := createHTTPClient()
resp, err := client.Get("https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Success")
}4. 重试机制
4.1 简单重试
package main
import (
"fmt"
"net/http"
"time"
)
func retryRequest(url string, maxRetries int) (*http.Response, error) {
var resp *http.Response
var err error
for i := 0; i < maxRetries; i++ {
resp, err = http.Get(url)
if err == nil && resp.StatusCode == http.StatusOK {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
time.Sleep(time.Second * time.Duration(i+1))
}
return nil, fmt.Errorf("failed after %d retries: %v", maxRetries, err)
}
func main() {
resp, err := retryRequest("https://api.example.com/data", 3)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Success")
}4.2 指数退避重试
package main
import (
"fmt"
"math"
"net/http"
"time"
)
func exponentialBackoffRetry(url string, maxRetries int) (*http.Response, error) {
client := &http.Client{
Timeout: 10 * time.Second,
}
var resp *http.Response
var err error
for i := 0; i < maxRetries; i++ {
resp, err = client.Get(url)
if err == nil && resp.StatusCode == http.StatusOK {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
// 指数退避
backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
time.Sleep(backoff)
}
return nil, fmt.Errorf("failed after %d retries: %v", maxRetries, err)
}
func main() {
resp, err := exponentialBackoffRetry("https://api.example.com/data", 3)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Success")
}5. 完整的HTTP客户端示例
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"time"
)
type HTTPClient struct {
client *http.Client
maxRetries int
}
func NewHTTPClient() *HTTPClient {
return &HTTPClient{
client: &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
},
Timeout: 30 * time.Second,
},
maxRetries: 3,
}
}
func (c *HTTPClient) Get(ctx context.Context, url string) ([]byte, error) {
return c.doRequest(ctx, http.MethodGet, url, nil)
}
func (c *HTTPClient) Post(ctx context.Context, url string, body interface{}) ([]byte, error) {
jsonBody, _ := json.Marshal(body)
return c.doRequest(ctx, http.MethodPost, url, bytes.NewReader(jsonBody))
}
func (c *HTTPClient) doRequest(ctx context.Context, method, url string, body io.Reader) ([]byte, error) {
var lastErr error
for i := 0; i < c.maxRetries; i++ {
req, err := http.NewRequestWithContext(ctx, method, url, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
lastErr = err
time.Sleep(time.Second * time.Duration(i+1))
continue
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return io.ReadAll(resp.Body)
}
lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode)
time.Sleep(time.Second * time.Duration(i+1))
}
return nil, fmt.Errorf("failed after %d retries: %v", c.maxRetries, lastErr)
}
func main() {
client := NewHTTPClient()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
data, err := client.Get(ctx, "https://api.example.com/data")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(data))
}6. 总结
HTTP客户端是Go语言网络编程的重要组成部分,合理设置超时和实现重试机制是保证服务稳定性的关键。
在使用HTTP客户端时,应该注意以下几点:
- 设置合理的超时时间,避免请求 hang 住
- 实现重试机制,提高请求成功率
- 使用指数退避策略,避免对服务端造成压力
- 使用 context 控制请求生命周期
- 合理配置连接池参数,提高性能
通过合理配置HTTP客户端,我们可以构建更加健壮、可靠的网络应用程序。
在将 HTTP 客户端部署到生产环境前,建议逐一检查以下项目:
Client.Timeout已设置(建议 10-30 秒)Transport.DialContext.Timeout已设置(建议 5 秒)Transport.TLSHandshakeTimeout已设置(建议 10 秒)Transport.ResponseHeaderTimeout已设置(建议 5-10 秒)Transport.IdleConnTimeout已设置(建议 30-90 秒)Transport.MaxIdleConns和MaxIdleConnsPerHost已调优重试次数已限制(建议 3-5 次,避免雪崩)
重试使用指数退避策略
POST/PUT 请求确认幂等性或手动控制重试
每次请求后正确关闭
resp.Bodyhttp.Client全局复用,不在循环/Handler 中创建为不同后端服务配置差异化超时策略
到此这篇关于Go语言中HTTP客户端超时与重试实现的文章就介绍到这了,更多相关Go语言HTTP客户端超时与重试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
