Day 2:限流与熔断——生产环境第一道防线
今天做什么
网关没有限流,一个突发流量就能把所有 LLM API 额度打爆。今天加两道防线:令牌桶限流 + 熔断器。
代码
go
package main
import (
"sync"
"sync/atomic"
"time"
)
// ===== 令牌桶限流器 =====
type TokenBucket struct {
rate float64 // 每秒生成令牌数
capacity float64 // 桶容量(允许突发)
tokens float64 // 当前令牌数
lastRefill time.Time
mu sync.Mutex
}
func NewTokenBucket(rate, capacity float64) *TokenBucket {
return &TokenBucket{
rate: rate,
capacity: capacity,
tokens: capacity,
lastRefill: time.Now(),
}
}
func (tb *TokenBucket) Allow() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
// 补充令牌
now := time.Now()
elapsed := now.Sub(tb.lastRefill).Seconds()
tb.tokens += elapsed * tb.rate
if tb.tokens > tb.capacity {
tb.tokens = tb.capacity
}
tb.lastRefill = now
// 尝试消耗一个令牌
if tb.tokens >= 1 {
tb.tokens--
return true
}
return false
}
// ===== 熔断器 =====
type CircuitBreaker struct {
failureCount atomic.Int64
successCount atomic.Int64
threshold int64 // 连续失败N次触发熔断
state atomic.Int32 // 0=关闭 1=开启 2=半开
lastFailTime atomic.Int64
cooldown time.Duration
}
const (
StateClosed = 0
StateOpen = 1
StateHalfOpen = 2
)
func NewCircuitBreaker(threshold int64, cooldown time.Duration) *CircuitBreaker {
return &CircuitBreaker{
threshold: threshold,
cooldown: cooldown,
}
}
func (cb *CircuitBreaker) Allow() bool {
state := cb.state.Load()
if state == StateClosed {
return true
}
if state == StateHalfOpen {
return true
}
// 熔断中,检查是否过了冷却期
if time.Now().Unix()-cb.lastFailTime.Load() > int64(cb.cooldown.Seconds()) {
cb.state.Store(StateHalfOpen)
return true
}
return false
}
func (cb *CircuitBreaker) RecordSuccess() {
cb.failureCount.Store(0)
cb.state.Store(StateClosed)
}
func (cb *CircuitBreaker) RecordFailure() {
count := cb.failureCount.Add(1)
cb.lastFailTime.Store(time.Now().Unix())
if count >= cb.threshold {
cb.state.Store(StateOpen)
log.Printf("🔴 熔断触发!连续 %d 次失败", count)
}
}
// ===== 网关集成 =====
type Gateway struct {
router *ModelRouter
limiter *TokenBucket
breaker *CircuitBreaker
}
func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 第一关:限流
if !g.limiter.Allow() {
http.Error(w, `{"error":"rate limited"}`, 429)
return
}
// 第二关:熔断
if !g.breaker.Allow() {
http.Error(w, `{"error":"service unavailable"}`, 503)
return
}
// 正常转发
g.router.ProxyHandler(w, r)
}
func main() {
gw := &Gateway{
router: NewRouter(),
limiter: NewTokenBucket(100, 200), // 每秒100个请求,突发200
breaker: NewCircuitBreaker(10, 30*time.Second), // 10次失败熔断30秒
}
http.Handle("/v1/chat/completions", http.HandlerFunc(gw.ServeHTTP))
log.Fatal(http.ListenAndServe(":8080", nil))
}运行
bash
go run main.go
# 压测限流
ab -n 1000 -c 100 http://localhost:8080/v1/chat/completions
# 部分请求会返回 429Day 2 完成。网关不怕突发流量了。

