Skip to content

Commit 6bdb778

Browse files
authored
Merge pull request #20 from WJQSERVER-STUDIO/feat/httpc-split
fix: 改进重试抖动实现
2 parents efefca6 + 1ee7a73 commit 6bdb778

4 files changed

Lines changed: 63 additions & 3 deletions

File tree

client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package httpc
22

33
import (
4+
"math/rand/v2"
45
"net"
56
"net/http"
67
"runtime"
@@ -36,6 +37,7 @@ func New(opts ...Option) *Client {
3637
},
3738
//transport: transport,
3839
retryOpts: defaultRetryOptions(),
40+
randomFloat64: rand.Float64,
3941
bufferPool: newDefaultPool(defaultBufferSize),
4042
userAgent: defaultUserAgent,
4143
dumpLog: nil, // 默认不启用日志

httpc_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,55 @@ func TestPostJSONSetsContentTypeAndHonorsContext(t *testing.T) {
203203
t.Fatal("request context was not propagated")
204204
}
205205
}
206+
207+
func TestCalculateExponentialBackoffWithoutJitter(t *testing.T) {
208+
client := New(WithRetryOptions(RetryOptions{
209+
BaseDelay: 100 * time.Millisecond,
210+
MaxDelay: 700 * time.Millisecond,
211+
}))
212+
213+
tests := []struct {
214+
attempt int
215+
want time.Duration
216+
}{
217+
{attempt: 0, want: 100 * time.Millisecond},
218+
{attempt: 1, want: 200 * time.Millisecond},
219+
{attempt: 2, want: 400 * time.Millisecond},
220+
{attempt: 3, want: 700 * time.Millisecond},
221+
}
222+
223+
for _, tt := range tests {
224+
if got := client.calculateExponentialBackoff(tt.attempt, false); got != tt.want {
225+
t.Fatalf("attempt %d: backoff = %v, want %v", tt.attempt, got, tt.want)
226+
}
227+
}
228+
}
229+
230+
func TestCalculateExponentialBackoffWithJitterUsesRandomFactor(t *testing.T) {
231+
client := New(WithRetryOptions(RetryOptions{
232+
BaseDelay: 200 * time.Millisecond,
233+
MaxDelay: time.Second,
234+
Jitter: true,
235+
}))
236+
client.randomFloat64 = func() float64 { return 0.25 }
237+
238+
got := client.calculateExponentialBackoff(1, true)
239+
want := 300 * time.Millisecond
240+
if got != want {
241+
t.Fatalf("backoff with jitter = %v, want %v", got, want)
242+
}
243+
}
244+
245+
func TestCalculateExponentialBackoffWithJitterStillHonorsMaxDelay(t *testing.T) {
246+
client := New(WithRetryOptions(RetryOptions{
247+
BaseDelay: 500 * time.Millisecond,
248+
MaxDelay: 800 * time.Millisecond,
249+
Jitter: true,
250+
}))
251+
client.randomFloat64 = func() float64 { return 0.99 }
252+
253+
got := client.calculateExponentialBackoff(3, true)
254+
if got != 800*time.Millisecond {
255+
t.Fatalf("backoff with jitter cap = %v, want %v", got, 800*time.Millisecond)
256+
}
257+
}

transport.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,19 @@ func parseRetryAfter(retryAfter string) (time.Duration, error) {
252252
return 0, errors.New("invalid Retry-After value")
253253
}
254254

255-
// 指数退避计算 (修改为支持 Jitter)
255+
// 指数退避计算,启用 jitter 时在 [0.5, 1.5) 区间内随机扰动。
256256
func (c *Client) calculateExponentialBackoff(attempt int, jitter bool) time.Duration {
257257
delay := min(c.retryOpts.BaseDelay*time.Duration(1<<uint(attempt)), c.retryOpts.MaxDelay)
258258

259259
if jitter {
260-
// 添加 Jitter 抖动,防止 thundering herd 问题
261-
randomFactor := 0.8 + 0.4*float64(attempt) // 随着重试次数增加,抖动范围略微扩大
260+
randomFactor := 0.5 + c.randomFloat64()
262261
delay = time.Duration(float64(delay) * randomFactor)
262+
if delay > c.retryOpts.MaxDelay {
263+
return c.retryOpts.MaxDelay
264+
}
265+
if delay < 0 {
266+
return 0
267+
}
263268
}
264269
return delay
265270
}

types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ type Client struct {
5858
client *http.Client
5959
transport *http.Transport
6060
retryOpts RetryOptions
61+
randomFloat64 func() float64
6162
bufferPool BufferPool
6263
userAgent string
6364
dumpLog DumpLogFunc // 日志记录函数

0 commit comments

Comments
 (0)