题目
设计基于令牌桶的HTTP限流中间件并实现熔断机制
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
并发控制,限流算法,熔断机制,Go标准库net/http使用,中间件设计
快速回答
实现要点:
- 使用
golang.org/x/time/rate实现令牌桶限流 - 通过
net/http.Handler中间件包装处理限流逻辑 - 熔断器使用状态机模式(关闭/打开/半开)
- 结合
sync/atomic实现并发安全的计数器 - 错误处理与HTTP状态码规范(429/503)
1. 核心原理
令牌桶算法:以固定速率向桶中添加令牌,请求需获取令牌才能执行。允许突发流量,保证平均速率。
熔断机制:基于错误率切换状态:
- 关闭状态:正常处理请求
- 打开状态:直接拒绝请求,定时切换半开
- 半开状态:试探性放行部分请求
2. 完整实现代码
package main
import (
"net/http"
"sync/atomic"
"time"
"golang.org/x/time/rate"
)
// 熔断器状态常量
const (
StateClosed uint32 = iota
StateOpen
StateHalfOpen
)
// RateLimiterMiddleware 令牌桶限流中间件
func RateLimiterMiddleware(r rate.Limit, b int) func(http.Handler) http.Handler {
limiter := rate.NewLimiter(r, b)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, req)
})
}
}
// CircuitBreaker 熔断器结构
type CircuitBreaker struct {
state uint32 // 当前状态
failureCount uint32 // 失败计数器
failureThreshold uint32 // 失败阈值
halfOpenSuccess uint32 // 半开状态成功计数
resetTimeout time.Duration // 重置超时
}
func NewCircuitBreaker(threshold uint32, timeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
state: StateClosed,
failureThreshold: threshold,
resetTimeout: timeout,
}
}
func (cb *CircuitBreaker) Execute(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch atomic.LoadUint32(&cb.state) {
case StateOpen:
http.Error(w, "Service Unavailable", http.StatusServiceUnavailable)
return
case StateHalfOpen:
if atomic.LoadUint32(&cb.halfOpenSuccess) > 5 {
atomic.StoreUint32(&cb.state, StateClosed)
atomic.StoreUint32(&cb.failureCount, 0)
} else {
http.Error(w, "Try Later", http.StatusServiceUnavailable)
return
}
}
// 执行实际处理
recorder := &responseRecorder{ResponseWriter: w}
h.ServeHTTP(recorder, r)
// 状态判断
if recorder.status >= 500 {
cb.recordFailure()
} else if atomic.LoadUint32(&cb.state) == StateHalfOpen {
atomic.AddUint32(&cb.halfOpenSuccess, 1)
}
})
}
func (cb *CircuitBreaker) recordFailure() {
count := atomic.AddUint32(&cb.failureCount, 1)
if count >= cb.failureThreshold && atomic.CompareAndSwapUint32(&cb.state, StateClosed, StateOpen) {
go func() {
time.Sleep(cb.resetTimeout)
atomic.CompareAndSwapUint32(&cb.state, StateOpen, StateHalfOpen)
atomic.StoreUint32(&cb.halfOpenSuccess, 0)
}()
}
}
// 辅助结构记录响应状态
type responseRecorder struct {
http.ResponseWriter
status int
}
func (r *responseRecorder) WriteHeader(status int) {
r.status = status
r.ResponseWriter.WriteHeader(status)
}
// 主函数集成
func main() {
cb := NewCircuitBreaker(10, 30*time.Second)
http.Handle("/api", cb.Execute(
RateLimiterMiddleware(rate.Limit(100), 50)( // 100rps,突发50
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 业务处理逻辑
}),
),
))
http.ListenAndServe(":8080", nil)
}
3. 最佳实践
- 分层防御:在API网关层做全局限流,服务层做本地限流
- 动态配置:结合配置中心实时调整限流阈值
- 监控集成:暴露
/metrics端点供Prometheus采集 - 优雅降级:触发限流时返回缓存数据或简化响应
4. 常见错误
- 竞态条件:熔断状态转换未使用原子操作导致并发问题
- 阈值设置不当:令牌桶容量过大失去限流意义,过小影响正常流量
- 忽略慢调用:未考虑延迟过高导致的变相服务不可用
- 日志缺失:未记录限流/熔断事件,难以排查问题
5. 扩展知识
- 滑动窗口限流:更精准控制单位时间请求量,避免令牌桶的突发特性
- 自适应限流:根据系统负载(如CPU、队列深度)动态调整限流阈值
- 分布式限流:使用Redis+Lua实现集群级别的限流
- gRPC集成:通过拦截器实现gRPC服务的限流熔断