Graceful degradation with rate limiting is a resilience pattern with request frequency limiting.
1import "golang.org/x/time/rate"23type DegradedService struct {4 primary Service5 fallback Service6 limiter *rate.Limiter7 degraded atomic.Bool8}910func (s *DegradedService) Handle(ctx context.Context, req Request) (Response, error) {11 // Check rate limit12 if !s.limiter.Allow() {13 return Response{}, ErrRateLimited14 }1516 // If in degraded mode — use fallback17 if s.degraded.Load() {18 return s.fallback.Process(ctx, req)19 }2021 // Try primary with timeout22 ctx, cancel := context.WithTimeout(ctx, 5*time.Second)23 defer cancel()2425 resp, err := s.primary.Process(ctx, req)26 if err != nil {27 // Switch to degraded mode28 s.degraded.Store(true)29 go s.recoveryCheck()3031 // Fallback32 return s.fallback.Process(ctx, req)33 }3435 return resp, nil36}3738func (s *DegradedService) recoveryCheck() {39 ticker := time.NewTicker(30 * time.Second)40 defer ticker.Stop()4142 for range ticker.C {43 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)44 err := s.primary.HealthCheck(ctx)45 cancel()4647 if err == nil {48 s.degraded.Store(false)49 return50 }51 }52}5354// Rate limiter per IP55type IPRateLimiter struct {56 mu sync.Mutex57 limiters map[string]*rate.Limiter58 rate rate.Limit59 burst int60}6162func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {63 rl.mu.Lock()64 defer rl.mu.Unlock()6566 limiter, exists := rl.limiters[ip]67 if !exists {68 limiter = rate.NewLimiter(rl.rate, rl.burst)69 rl.limiters[ip] = limiter70 }71 return limiter72}7374// Middleware75func RateLimitMiddleware(rl *IPRateLimiter) func(http.Handler) http.Handler {76 return func(next http.Handler) http.Handler {77 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {78 limiter := rl.GetLimiter(r.RemoteAddr)79 if !limiter.Allow() {80 http.Error(w, "Rate limited", http.StatusTooManyRequests)81 return82 }83 next.ServeHTTP(w, r)84 })85 }86}
Strategies: