🔄 重试与限流
网络不稳?限流触发?本库内置重试,并提供可插拔限流。
自动重试
doRequest 对网络错误和 5xx 服务端错误自动重试:
go
for i := 0; i <= c.Retries; i++ {
resp, err = c.HTTPClient.Do(req)
if err == nil && resp.StatusCode < 500 {
break // 成功
}
// ... 关闭 body,等待,重试
time.Sleep(defaultRetryDelay) // 500ms
}- 🔄 默认重试 2 次(共 3 次请求)
- ⏱ 固定退避
500ms(defaultRetryDelay) - 🚫 只重试网络错误与 5xx,不重试 4xx(客户端错误不可恢复)
配置重试次数
go
client := ipapi.NewClient()
client.Retries = 5 // 改成 5 次💡 直接改字段
Retries 是导出字段,创建后直接赋值即可。也可在 NewClient 后调整。
设为 0 则不重试:
go
client.Retries = 0速率限制 RateLimiter
Client.RateLimiter 是 <-chan time.Time。非空时,每次请求前阻塞等待令牌:
go
// 每秒最多 1 次请求
client := ipapi.NewClient()
client.RateLimiter = time.Tick(time.Second)为什么用通道
- 🧵 天然并发安全,多 goroutine 自动排队
- 🔌 可插拔:塞任意
<-chan time.Time - 🪶 比锁/接口轻量
自定义限流策略
用 time.Tick 是固定速率。要做令牌桶、动态调速,自己造 channel 即可:
go
// 漏桶:每 200ms 放一个令牌 = 5 QPS
client.RateLimiter = time.Tick(200 * time.Millisecond)
// 突发+恢复:缓冲通道
bucket := make(chan time.Time, 10) // 突发 10
go func() {
for range time.Tick(time.Second) {
select {
case bucket <- time.Now():
default:
}
}
}()
client.RateLimiter = bucket限流错误处理
触发服务端限流(HTTP 429)会返回 ErrRateLimited:
go
if errors.Is(err, ipapi.ErrRateLimited) {
time.Sleep(time.Minute) // 业务层退避
}重试 + 限流协同
🎨 一图抵千言
下图展示一次 GetIPInfo 调用的完整时序:限流器先放行令牌,doRequest 进入重试循环,遇到网络错误或 5xx 时退避 500ms 后重试,4xx 则立即终止。
下面是等价的文字版流程,便于复制粘贴:
请求 → RateLimiter 阻塞拿令牌 → 发请求 → 成功?
│ No
├─ 网络错误/5xx → sleep 500ms → 重试
└─ 4xx → 立即返回错误重试循环展开时序
下图聚焦 for i := 0; i <= c.Retries; i++ 循环内部,逐轮展开网络错误/5xx 重试与 4xx 立即终止的对照:
可重试错误判断
业务层判断是否值得重试,用 IsRetryableError:
go
for attempt := 0; attempt < 3; attempt++ {
info, err := client.GetIPInfo(ctx, ip, "json")
if err == nil {
break
}
if !ipapi.IsRetryableError(err) {
break // 不可重试,放弃
}
time.Sleep(time.Duration(1<<attempt) * time.Second) // 指数退避
}哨兵错误重试分类
下图用状态图展示 IsRetryableError 的判定视角:错误被归入「可重试」或「不可重试」终态,业务层据此决定是否继续。
配额规划建议
| 流量级 | 建议配置 |
|---|---|
| 偶发 | 默认 |
| 中等 | RateLimiter = time.Tick |
| 高并发 | 令牌桶 + Retries 适当降低 |
| 生产 | 申请付费 Key 提升配额 |
下一步
- 📖 看
IsRetryableError - 🛡 学 错误处理
- 📖 看
Client字段