在Golang的网络编程场景中,网络请求受到网络波动、服务端负载过高、链路故障等多种因素影响,很容易出现请求阻塞、失败的情况,处理网络超时和重试机制是提升请求可靠性的核心手段。

一、Golang中的网络超时处理
超时处理的核心是给网络操作设置合理的时间上限,避免程序因为单个请求卡住而消耗过多资源。Golang标准库中的http.Client和net.Dialer都提供了超时配置的能力。
1. HTTP请求超时配置
对于HTTP请求,可以通过自定义http.Client的Timeout字段设置整体请求超时时间,这个时间涵盖了从连接建立、请求发送、响应接收的全流程。
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// 创建自定义http客户端,设置整体超时时间为5秒
client := &http.Client{
Timeout: 5 * time.Second,
}
// 发送GET请求
resp, err := client.Get("http://ipipp.com/api/test")
if err != nil {
fmt.Printf("请求失败: %vn", err)
return
}
defer resp.Body.Close()
fmt.Printf("请求成功,状态码: %dn", resp.StatusCode)
}
2. 细粒度超时控制
如果需要更精细的超时控制,比如单独设置连接超时、读写超时,可以使用net.Dialer配合http.Transport实现。
package main
import (
"fmt"
"net"
"net/http"
"time"
)
func main() {
// 配置Dialer,设置连接超时3秒
dialer := &net.Dialer{
Timeout: 3 * time.Second,
}
// 自定义Transport,设置读写超时各2秒
transport := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
conn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
// 设置读写超时
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
conn.SetWriteDeadline(time.Now().Add(2 * time.Second))
return conn, nil
},
}
client := &http.Client{
Transport: transport,
}
resp, err := client.Get("http://ipipp.com/api/test")
if err != nil {
fmt.Printf("请求失败: %vn", err)
return
}
defer resp.Body.Close()
fmt.Printf("请求成功,状态码: %dn", resp.StatusCode)
}
二、重试机制的实现
重试机制是在请求失败(比如超时、临时网络错误、服务端5xx错误)时,按照预设规则再次发起请求,进一步提升请求成功率。实现重试机制需要考虑几个核心点:重试触发条件、重试次数、重试间隔、幂等性处理。
1. 基础重试逻辑实现
以下是一个简单的重试封装函数,支持设置重试次数和重试间隔,仅在遇到网络错误或者5xx状态码时触发重试。
package main
import (
"fmt"
"net/http"
"time"
)
// 重试请求函数,maxRetry为最大重试次数,interval为重试用间隔
func retryRequest(url string, maxRetry int, interval time.Duration) (*http.Response, error) {
var resp *http.Response
var err error
for i := 0; i <= maxRetry; i++ {
resp, err = http.Get(url)
if err == nil {
// 如果是5xx错误,继续重试
if resp.StatusCode >= 500 && resp.StatusCode < 600 {
resp.Body.Close()
fmt.Printf("第%d次请求返回5xx错误,准备重试n", i+1)
time.Sleep(interval)
continue
}
// 其他成功或客户端错误直接返回
return resp, nil
}
// 网络错误则重试
fmt.Printf("第%d次请求发生网络错误: %v,准备重试n", i+1, err)
if i < maxRetry {
time.Sleep(interval)
}
}
return nil, err
}
func main() {
resp, err := retryRequest("http://ipipp.com/api/test", 3, 1*time.Second)
if err != nil {
fmt.Printf("所有重试都失败: %vn", err)
return
}
defer resp.Body.Close()
fmt.Printf("最终请求成功,状态码: %dn", resp.StatusCode)
}
2. 重试机制的注意事项
- 幂等性校验:只有幂等请求(比如GET、PUT、DELETE,多次执行结果一致)才适合重试,非幂等的POST请求重试可能导致重复创建数据,需要额外加幂等性校验逻辑。
- 重试间隔策略:不要固定间隔重试,建议使用指数退避策略,比如第一次间隔1秒,第二次2秒,第三次4秒,避免短时间内大量重试压垮服务端。
- 重试触发条件:明确哪些错误需要重试,比如连接超时、读超时、服务端5xx错误可以重试,而4xx错误(比如404、401)通常不需要重试,重试也没有意义。
三、超时与重试机制的整合
实际开发中通常会把超时和重试机制整合起来,让每次重试的请求都有独立的超时控制,避免单次重试耗时过长。
package main
import (
"fmt"
"net/http"
"time"
)
// 带超时的请求函数,每次请求最多等待3秒
func requestWithTimeout(url string) (*http.Response, error) {
client := &http.Client{
Timeout: 3 * time.Second,
}
return client.Get(url)
}
// 整合超时和重试的总控函数
func requestWithRetryAndTimeout(url string, maxRetry int) (*http.Response, error) {
var resp *http.Response
var err error
for i := 0; i <= maxRetry; i++ {
resp, err = requestWithTimeout(url)
if err == nil {
if resp.StatusCode >= 500 && resp.StatusCode < 600 {
resp.Body.Close()
fmt.Printf("第%d次请求返回5xx错误,准备重试n", i+1)
time.Sleep(time.Duration(1<<i) * time.Second) // 指数退避间隔
continue
}
return resp, nil
}
fmt.Printf("第%d次请求失败: %v,准备重试n", i+1, err)
if i < maxRetry {
time.Sleep(time.Duration(1<<i) * time.Second)
}
}
return nil, err
}
func main() {
resp, err := requestWithRetryAndTimeout("http://ipipp.com/api/test", 3)
if err != nil {
fmt.Printf("请求最终失败: %vn", err)
return
}
defer resp.Body.Close()
fmt.Printf("请求成功,状态码: %dn", resp.StatusCode)
}
通过上述方式,就可以在Golang中构建完善的网络超时和重试体系,让网络请求在面对各类不稳定场景时,依然能保持较高的可靠性,减少因为临时故障导致的业务异常。