package circuitbreaker import ( "sync" "time" "go.uber.org/zap" ) type State int const ( StateClosed State = iota StateOpen StateHalfOpen ) type Config struct { FailureThreshold int // 连续失败次数阈值,超过则开启熔断 SuccessThreshold int // 半开状态下连续成功次数,恢复到关闭 Timeout time.Duration // 熔断开启后等待多久进入半开 Name string // 熔断器名称,用于日志 } func DefaultConfig(name string) Config { return Config{ FailureThreshold: 5, SuccessThreshold: 3, Timeout: 30 * time.Second, Name: name, } } type Breaker struct { cfg Config mu sync.Mutex state State fails int wins int openAt time.Time } func New(cfg Config) *Breaker { return &Breaker{cfg: cfg} } func (b *Breaker) Allow() bool { b.mu.Lock() defer b.mu.Unlock() switch b.state { case StateClosed: return true case StateOpen: if time.Since(b.openAt) > b.cfg.Timeout { b.state = StateHalfOpen b.wins = 0 return true } return false case StateHalfOpen: return true default: return true } } func (b *Breaker) RecordSuccess() { b.mu.Lock() defer b.mu.Unlock() b.fails = 0 if b.state == StateHalfOpen { b.wins++ if b.wins >= b.cfg.SuccessThreshold { b.state = StateClosed b.wins = 0 zap.L().Info("circuit breaker closed", zap.String("breaker", b.cfg.Name), ) } } } func (b *Breaker) RecordFailure() { b.mu.Lock() defer b.mu.Unlock() b.fails++ if b.state == StateHalfOpen { b.state = StateOpen b.openAt = time.Now() b.wins = 0 zap.L().Warn("circuit breaker re-opened from half-open", zap.String("breaker", b.cfg.Name), ) return } if b.fails >= b.cfg.FailureThreshold { b.state = StateOpen b.openAt = time.Now() zap.L().Warn("circuit breaker opened", zap.String("breaker", b.cfg.Name), zap.Int("consecutive_failures", b.fails), ) } } func (b *Breaker) State() State { b.mu.Lock() defer b.mu.Unlock() return b.state } func (b *Breaker) Execute(fn func() error) error { if !b.Allow() { zap.L().Debug("circuit breaker rejected request", zap.String("breaker", b.cfg.Name), ) return &OpenError{Name: b.cfg.Name} } if err := fn(); err != nil { b.RecordFailure() return err } b.RecordSuccess() return nil } type OpenError struct { Name string } func (e *OpenError) Error() string { return "circuit breaker '" + e.Name + "' is open" }