Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言HTTP客户端超时与重试

Go语言中HTTP客户端超时与重试的实现代码

作者:王码码2035哦

Golang标准库虽然提供了基础的HTTP客户端实现,但在高并发、高可用场景下,我们需要更精细化的策略来应对复杂的网络环境,这篇文章主要介绍了Go语言中HTTP客户端超时与重试实现的相关资料,需要的朋友可以参考下

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客户端时,应该注意以下几点:

  1. 设置合理的超时时间,避免请求 hang 住
  2. 实现重试机制,提高请求成功率
  3. 使用指数退避策略,避免对服务端造成压力
  4. 使用 context 控制请求生命周期
  5. 合理配置连接池参数,提高性能

通过合理配置HTTP客户端,我们可以构建更加健壮、可靠的网络应用程序。

在将 HTTP 客户端部署到生产环境前,建议逐一检查以下项目:

到此这篇关于Go语言中HTTP客户端超时与重试实现的文章就介绍到这了,更多相关Go语言HTTP客户端超时与重试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文