阶段2(上):后端专题 Go/Gin/PostgreSQL/MySQL/Redis/MQ/gRPC 的课程、企业场景、面试题和可运行示例
This commit is contained in:
1 parent
0bae9916a9
commit
5eb67c5d08
65 files changed
+6457
-77
No files matched your search
@@ -0,0 +1,82 @@
|
||||
package resilience
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var errDown = errors.New("下游 503")
|
||||
|
||||
func TestRetrySucceedsAfterTransientErrors(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
err := Retry(context.Background(), 5, time.Millisecond, func(context.Context) error {
|
||||
if calls.Add(1) < 3 {
|
||||
return errDown
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil || calls.Load() != 3 {
|
||||
t.Fatalf("err=%v calls=%d", err, calls.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryStopsOnPermanent(t *testing.T) {
|
||||
var calls int
|
||||
err := Retry(context.Background(), 5, time.Millisecond, func(context.Context) error {
|
||||
calls++
|
||||
return Permanent{errors.New("参数错误")}
|
||||
})
|
||||
if calls != 1 || err == nil {
|
||||
t.Fatalf("不可重试的错误只应调用一次,calls=%d", calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryRespectsDeadline(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
|
||||
defer cancel()
|
||||
start := time.Now()
|
||||
err := Retry(ctx, 10, 20*time.Millisecond, func(context.Context) error { return errDown })
|
||||
if !errors.Is(err, context.DeadlineExceeded) || time.Since(start) > 200*time.Millisecond {
|
||||
t.Fatalf("应该在截止时间后停止:%v,耗时 %v", err, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBreaker(t *testing.T) {
|
||||
now := time.Unix(0, 0)
|
||||
b := NewBreaker(3, 10*time.Second)
|
||||
b.now = func() time.Time { return now }
|
||||
ctx := context.Background()
|
||||
fail := func(context.Context) error { return errDown }
|
||||
ok := func(context.Context) error { return nil }
|
||||
|
||||
for range 3 {
|
||||
_ = b.Do(ctx, fail)
|
||||
}
|
||||
if err := b.Do(ctx, ok); !errors.Is(err, ErrOpen) {
|
||||
t.Fatalf("连续失败 3 次后应该熔断,得到 %v", err)
|
||||
}
|
||||
now = now.Add(11 * time.Second) // 冷却结束 → 半开,试探失败 → 重新打开
|
||||
if err := b.Do(ctx, fail); !errors.Is(err, errDown) {
|
||||
t.Fatalf("半开时应放行试探请求:%v", err)
|
||||
}
|
||||
if err := b.Do(ctx, ok); !errors.Is(err, ErrOpen) {
|
||||
t.Fatal("试探失败后应该重新熔断")
|
||||
}
|
||||
now = now.Add(11 * time.Second) // 再次半开,试探成功 → 关闭
|
||||
if err := b.Do(ctx, ok); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Do(ctx, ok); err != nil {
|
||||
t.Fatal("恢复后应该正常放行")
|
||||
}
|
||||
// 业务错误不计入熔断
|
||||
for range 5 {
|
||||
_ = b.Do(ctx, func(context.Context) error { return Permanent{errors.New("用户不存在")} })
|
||||
}
|
||||
if err := b.Do(ctx, ok); err != nil {
|
||||
t.Fatal("业务错误不应该触发熔断")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user