12. 限流、熔断与降级

分布式系统韧性设计:令牌桶/漏桶/滑动窗口限流、熔断器模式、降级策略与 Sentinel 实战

高并发场景下,系统若缺乏自我保护机制,极易因流量突增而雪崩。限流控制入口流量,熔断防止故障扩散,降级保障核心业务可用。三者相辅相成,共同构成分布式系统的韧性防线。本章将从算法原理到生产实践,深入剖析限流、熔断与降级的完整技术体系。

1. 限流算法

限流的核心目标是控制单位时间内的请求处理数量,防止系统被突发流量压垮。不同的限流算法在精度、内存开销和分布式一致性方面各有取舍。

1.1 计数器(固定窗口)

固定窗口算法将时间划分为等长的窗口,每个窗口内维护一个请求计数器。当计数器达到阈值时,后续请求被拒绝。其数学表达为:设窗口大小为 $T$,最大请求数为 $N$,当前窗口 $[kT, (k+1)T)$ 内的请求计数为 $C$,则允许通过的条件是 $C < N$。

import java.util.concurrent.atomic.AtomicInteger;

public class FixedWindowRateLimiter {
    private final long windowSizeMs;
    private final int maxRequests;
    private final AtomicInteger counter = new AtomicInteger(0);
    private volatile long windowStart = System.currentTimeMillis();

    public FixedWindowRateLimiter(int maxRequests, long windowSizeMs) {
        this.maxRequests = maxRequests;
        this.windowSizeMs = windowSizeMs;
    }

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        if (now - windowStart >= windowSizeMs) {
            counter.set(0);
            windowStart = now;
        }
        return counter.incrementAndGet() <= maxRequests;
    }

    public static void main(String[] args) {
        FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(10, 1000);
        for (int i = 0; i < 15; i++) {
            System.out.println("Request " + i + ": " + limiter.tryAcquire());
        }
    }
}

临界问题:在窗口边界处可能发生流量突刺。例如窗口大小为一秒、限流一千 QPS,在上一秒末的请求数与下一秒初的请求数叠加,理论上可能出现两千 QPS 的瞬间流量,这就是固定窗口的经典缺陷。

1.2 滑动窗口

滑动窗口算法维护一个时间戳队列,每次请求到来时,移除超出窗口范围的旧时间戳,然后判断当前队列长度是否小于阈值。其时间复杂度为 $O(N)$,$N$ 为窗口内的请求数。优化版本可使用循环数组降低内存分配开销。

import java.util.LinkedList;
import java.util.Queue;

public class SlidingWindowRateLimiter {
    private final int maxRequests;
    private final long windowMs;
    private final Queue<Long> timestamps = new LinkedList<>();

    public SlidingWindowRateLimiter(int maxRequests, long windowMs) {
        this.maxRequests = maxRequests;
        this.windowMs = windowMs;
    }

    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        long boundary = now - windowMs;

        while (!timestamps.isEmpty() && timestamps.peek() <= boundary) {
            timestamps.poll();
        }

        if (timestamps.size() < maxRequests) {
            timestamps.offer(now);
            return true;
        }
        return false;
    }

    public static void main(String[] args) throws InterruptedException {
        SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(5, 1000);
        for (int i = 0; i < 8; i++) {
            System.out.println("Request " + i + ": " + limiter.tryAcquire());
            Thread.sleep(100);
        }
    }
}

滑动窗口解决了固定窗口的临界突刺问题,但在高并发场景下内存占用随窗口内请求数线性增长。生产环境中常采用滑动日志或采样滑动窗口来平衡精度与性能。

1.3 令牌桶

令牌桶算法以恒定速率向桶中放入令牌,请求到来时尝试从桶中获取令牌。若桶中有足够的令牌则允许通过,否则请求被拒绝或等待。令牌桶允许一定程度的突发流量,同时保持长期的平均速率稳定。设桶容量为 $C$,令牌放入速率为 $r$(个每秒),当前令牌数为 $tokens$,则 refill 公式为 $tokens = \min(C, tokens + r \times \Delta t)$。

public class TokenBucket {
    private final long capacity;
    private final long refillRate;
    private double tokens;
    private long lastRefillTimestamp;

    public TokenBucket(long capacity, long refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.tokens = capacity;
        this.lastRefillTimestamp = System.currentTimeMillis();
    }

    public synchronized boolean tryAcquire(int requestedTokens) {
        refill();
        if (tokens >= requestedTokens) {
            tokens -= requestedTokens;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        double tokensToAdd = (now - lastRefillTimestamp) * refillRate / 1000.0;
        tokens = Math.min(capacity, tokens + tokensToAdd);
        lastRefillTimestamp = now;
    }

    public static void main(String[] args) {
        TokenBucket bucket = new TokenBucket(10, 5);
        for (int i = 0; i < 15; i++) {
            System.out.println("Request " + i + ": " + bucket.tryAcquire(1));
        }
    }
}

Google Guava 的 RateLimiter 基于令牌桶实现,其平滑预热限制器(SmoothWarmingUp)在系统启动时逐步提高令牌发放速率,避免冷启动瞬间打满资源,这一特性在生产环境中尤为重要。

1.4 漏桶

漏桶算法以固定速率处理请求,请求先进入桶中等待,桶满则溢出丢弃。漏桶强制请求以恒定速率流出,天然具有流量整形能力,适合对请求处理速率有严格上限要求的场景。与令牌桶相比,漏桶更适合需要严格平滑输出的场景,而令牌桶更适合允许一定突发的场景。

import java.util.concurrent.*;

public class LeakyBucket {
    private final int capacity;
    private final BlockingQueue<Runnable> queue;
    private final ScheduledExecutorService scheduler;
    private final int leakRatePerSecond;

    public LeakyBucket(int capacity, int leakRatePerSecond) {
        this.capacity = capacity;
        this.leakRatePerSecond = leakRatePerSecond;
        this.queue = new LinkedBlockingQueue<>(capacity);
        this.scheduler = Executors.newScheduledThreadPool(1);
        startLeaking();
    }

    private void startLeaking() {
        scheduler.scheduleAtFixedRate(() -> {
            Runnable task = queue.poll();
            if (task != null) {
                task.run();
            }
        }, 0, 1000 / leakRatePerSecond, TimeUnit.MILLISECONDS);
    }

    public boolean tryEnqueue(Runnable task) {
        return queue.offer(task);
    }

    public static void main(String[] args) throws InterruptedException {
        LeakyBucket bucket = new LeakyBucket(5, 2);
        for (int i = 0; i < 10; i++) {
            final int id = i;
            boolean accepted = bucket.tryEnqueue(() ->
                System.out.println("Processing request " + id + " at " + System.currentTimeMillis())
            );
            System.out.println("Request " + id + " accepted: " + accepted);
            Thread.sleep(100);
        }
        Thread.sleep(5000);
    }
}

2. 熔断器 (Circuit Breaker)

2.1 状态机

熔断器通过监控服务调用的失败率、慢调用率等指标,在检测到异常时自动切断请求,防止故障级联扩散。其状态转换模型包含三个核心状态:

CLOSED ──(失败率>阈值)──→ OPEN
   ↑                         │
   └──(超时后半开)──────── HALF-OPEN ──(成功)──┘
                              └──(失败)──→ OPEN
状态行为触发条件
CLOSED正常放行,统计错误率与延迟初始状态,持续监控
OPEN直接拒绝,快速失败返回降级失败率超过配置阈值
HALF-OPEN放行少量请求试探恢复情况OPEN 状态持续一段时间后自动进入

在 CLOSED 状态下,熔断器统计最近 $N$ 次调用的结果。当失败率 $P_{fail} > \theta_{fail}$ 或慢调用率 $P_{slow} > \theta_{slow}$ 时,状态迁移至 OPEN。OPEN 状态下所有请求被快速拒绝,经过等待时长 $T_{wait}$ 后进入 HALF-OPEN,此时放行 $N_{probe}$ 个请求进行探测。若探测成功率达标则回到 CLOSED,否则重新进入 OPEN。

2.2 Resilience4j 实现

Resilience4j 是 Netflix Hystrix 的轻量级替代方案,专为函数式编程设计,无需依赖外部线程池。

import io.github.resilience4j.circuitbreaker.*;
import java.time.Duration;
import java.util.function.Supplier;

public class Resilience4jDemo {
    public static void main(String[] args) {
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
            .failureRateThreshold(50)
            .slowCallRateThreshold(80)
            .slowCallDurationThreshold(Duration.ofSeconds(2))
            .permittedNumberOfCallsInHalfOpenState(10)
            .slidingWindowSize(100)
            .waitDurationInOpenState(Duration.ofSeconds(30))
            .recordExceptions(RuntimeException.class)
            .build();

        CircuitBreaker circuitBreaker = CircuitBreaker.of("orderService", config);

        Supplier<String> decorated = CircuitBreaker.decorateSupplier(
            circuitBreaker,
            () -> {
                if (Math.random() > 0.7) {
                    throw new RuntimeException("Service error");
                }
                return "Order created";
            }
        );

        for (int i = 0; i < 20; i++) {
            try {
                String result = decorated.get();
                System.out.println("Success: " + result);
            } catch (CallNotPermittedException e) {
                System.out.println("Circuit breaker OPEN - fallback executed");
            } catch (Exception e) {
                System.out.println("Service error: " + e.getMessage());
            }
        }

        System.out.println("State: " + circuitBreaker.getState());
    }
}

3. 降级策略

降级是在系统负载过高或依赖服务不可用时,主动降低服务质量以保障核心功能可用的策略。常见的降级策略包含以下几类:

策略场景实现方式
默认值配置服务、字典数据返回本地缓存的静态数据,如默认城市列表
功能降级搜索推荐、个性化排序关闭推荐算法,切换为默认排序规则
页面降级电商大促详情页返回简化版页面,关闭部分非核心组件渲染
数据降级用户信息查询返回用户基础字段,隐藏扩展信息
读降级库存查询写请求正常处理,读请求返回缓存数据或默认值

降级的关键在于分级设计。建议将业务功能划分为 P0(核心)、P1(重要)、P2(一般)三个等级。在系统负载达到百分之七十时关闭 P2 功能,百分之八十五时关闭 P1 功能,始终保证 P0 功能可用。降级开关应支持动态配置和秒级生效,通常通过配置中心如 Nacos、Apollo 实现热更新。

4. Sentinel 实战

Sentinel 是阿里巴巴开源的流量控制组件,提供流量控制、熔断降级、系统负载保护、热点参数限流等丰富功能。其设计理念是将流量控制与业务逻辑解耦,通过规则配置实现动态防护。

import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import java.util.Collections;

public class SentinelDemo {
    static {
        FlowRule rule = new FlowRule();
        rule.setResource("queryOrder");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1000);
        rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
        rule.setWarmUpPeriodSec(10);
        FlowRuleManager.loadRules(Collections.singletonList(rule));
    }

    public static String queryOrder(String orderId) {
        Entry entry = null;
        try {
            entry = SphU.entry("queryOrder");
            return "Order details for " + orderId;
        } catch (BlockException e) {
            return fallbackQuery(orderId);
        } finally {
            if (entry != null) entry.exit();
        }
    }

    private static String fallbackQuery(String orderId) {
        return "Fallback: cached order summary for " + orderId;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(queryOrder("ORD-" + i));
        }
    }
}

Sentinel 的集群流控模式通过 Token Server 实现多节点统一的流量控制。在集群模式下,各客户端节点向 Token Server 申请令牌,服务端根据全局规则进行统一调度。这种模式解决了单机限流无法应对分布式场景的问题,但需要额外部署 Token Server 并保证其高可用。

5. 三层防护体系

生产环境中的韧性设计应采用分层防护策略,从外向内逐层过滤异常流量:

┌─────────────────────────────────────────┐
│  接入层:Nginx/Spring Cloud Gateway      │
│  限流:IP/QPS/并发数                      │
├─────────────────────────────────────────┤
│  服务层:Sentinel/Resilience4j          │
│  熔断 + 限流 + 降级                      │
├─────────────────────────────────────────┤
│  资源层:HikariCP/Redis/Guava           │
│  连接池限流、线程池隔离                   │
└─────────────────────────────────────────┘

接入层负责粗粒度拦截,过滤明显异常的流量攻击;服务层负责细粒度业务防护,实现熔断和降级;资源层负责物理资源保护,防止数据库连接池耗尽、线程池阻塞等问题。三层协同工作,任何一层被击穿时其他层仍可提供兜底保护。

6. Redis 分布式限流

单机限流在微服务架构中无法满足多节点一致性需求。Redis 作为中心化存储,天然适合实现分布式限流。分布式限流的核心挑战在于:如何在多节点并发访问下保证计数操作的原子性,避免竞态条件导致的限流失效。

6.1 Redisson RateLimiter

Redisson 提供了基于 Redis 的分布式限流实现,支持令牌桶和滑动窗口两种模式。其内部通过 Redis 的哈希结构存储令牌数量和时间戳,结合 Lua 脚本保证操作的原子性。

import org.redisson.Redisson;
import org.redisson.api.*;
import org.redisson.config.Config;

public class RedissonRateLimiterDemo {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        RRateLimiter rateLimiter = redisson.getRateLimiter("apiLimiter");
        rateLimiter.trySetRate(
            RateType.OVERALL,
            100,
            1,
            RateIntervalUnit.SECONDS
        );

        for (int i = 0; i < 10; i++) {
            if (rateLimiter.tryAcquire()) {
                System.out.println("Request " + i + " allowed");
            } else {
                System.out.println("Request " + i + " rejected");
            }
        }

        redisson.shutdown();
    }
}

6.2 Redis Lua 滑动窗口

使用 Redis 的 ZSet 结构结合 Lua 脚本可以实现精确的滑动窗口限流。ZSet 的 Score 存储时间戳,每个请求到来时先删除窗口外的旧记录,再统计当前窗口内的记录数。

-- redis sliding window rate limiter
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local member = ARGV[4]

redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local current = redis.call('ZCARD', key)

if current < limit then
    redis.call('ZADD', key, now, member)
    redis.call('EXPIRE', key, math.ceil(window / 1000))
    return 1
else
    return 0
end
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class RedisSlidingWindowLimiter {
    private static final String LUA_SCRIPT =
        "local key = KEYS[1] " +
        "local window = tonumber(ARGV[1]) " +
        "local limit = tonumber(ARGV[2]) " +
        "local now = tonumber(ARGV[3]) " +
        "local member = ARGV[4] " +
        "redis.call('ZREMRANGEBYSCORE', key, 0, now - window) " +
        "local current = redis.call('ZCARD', key) " +
        "if current < limit then " +
        "   redis.call('ZADD', key, now, member) " +
        "   redis.call('EXPIRE', key, math.ceil(window / 1000)) " +
        "   return 1 " +
        "else " +
        "   return 0 " +
        "end";

    private final JedisPool jedisPool;
    private final long windowMs;
    private final int maxRequests;

    public RedisSlidingWindowLimiter(JedisPool jedisPool, long windowMs, int maxRequests) {
        this.jedisPool = jedisPool;
        this.windowMs = windowMs;
        this.maxRequests = maxRequests;
    }

    public boolean tryAcquire(String clientId) {
        try (Jedis jedis = jedisPool.getResource()) {
            long now = System.currentTimeMillis();
            Object result = jedis.eval(LUA_SCRIPT,
                Collections.singletonList("ratelimit:" + clientId),
                Arrays.asList(String.valueOf(windowMs), String.valueOf(maxRequests),
                    String.valueOf(now), clientId + ":" + now));
            return Long.valueOf(1).equals(result);
        }
    }
}

Lua 脚本在 Redis 服务端原子执行,天然避免了多个客户端并发修改计数时的竞态条件。当集群规模较大时,ZSet 的内存占用需要重点关注,建议使用合理的过期策略和窗口时长。

7. Nginx/OpenResty 限流

在接入层部署限流是最经济有效的防护手段,能够在恶意流量到达应用服务器之前就将其拦截。Nginx 提供了内置的限流模块,OpenResty 则通过 Lua 脚本提供了更灵活的自定义能力。

7.1 Nginx limit_req 配置

limit_req 模块基于漏桶算法实现,limit_conn 模块限制并发连接数。

http {
    # 定义限流区域,使用二进制远程地址作为键
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
    
    # 定义并发连接限制
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    server {
        listen 80;
        
        location /api/ {
            # 漏桶限流,突发请求放入桶中,最多缓存5个
            limit_req zone=one burst=5 nodelay;
            limit_conn addr 10;
            
            # 超限返回自定义状态码
            limit_req_status 429;
            limit_conn_status 503;
            
            proxy_pass http://backend;
        }
    }
}

上述配置中,rate=10r/s 表示每秒允许十个请求,burst=5 允许突发五个请求进入队列等待处理,nodelay 表示不延迟处理突发请求。没有 nodelay 时突发请求会按漏桶速率匀速处理,加上后则立即处理但会计入令牌消耗。

7.2 OpenResty Lua 共享内存限流

OpenResty 的 lua_shared_dict 可以在多个 Worker 进程间共享内存,非常适合实现自定义限流逻辑。

-- nginx.conf
-- lua_shared_dict my_limit_req_store 10m;

local limit_req = require "resty.limit.req"

local lim, err = limit_req.new("my_limit_req_store", 200, 100)
if not lim then
    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
    return ngx.exit(500)
end

local delay, err = lim:incoming(ngx.var.binary_remote_addr, true)
if not delay then
    if err == "rejected" then
        return ngx.exit(503)
    end
    ngx.log(ngx.ERR, "failed to limit req: ", err)
    return ngx.exit(500)
end

if delay >= 0.001 then
    ngx.sleep(delay)
end

OpenResty 还支持基于令牌桶的 resty.limit.count 和基于并发数的 resty.limit.conn,可以根据业务场景灵活组合使用。在网关层实现限流时,建议结合请求头中的用户标识或 API Key 进行细粒度控制,而非仅凭 IP 地址。

8. 自适应限流(BBR/Congestion Control)

传统限流算法依赖人工设定阈值,而在生产环境中系统负载受多种因素影响(GC 频率、网络延迟、下游依赖响应时间),固定阈值往往难以适应动态变化的运行环境。自适应限流借鉴 TCP BBR 拥塞控制算法和网络排队论,通过实时监控系统指标动态调整限流阈值。

8.1 系统负载自适应

自适应限流的核心观测指标包括:CPU 使用率、内存占用、请求延迟(P50/P99)、错误率、待处理队列长度。当系统负载升高时自动收紧限流阈值,负载降低时则放宽限制。

8.2 Netflix concurrency-limits

Netflix 开源的 concurrency-limits 库实现了多种自适应限流策略,包括梯度下降法(Gradient)和 Vegas 算法。

import com.netflix.concurrency.limits.limit.GradientLimit;
import com.netflix.concurrency.limits.limit.VegasLimit;
import com.netflix.concurrency.limits.limiter.SimpleLimiter;

public class AdaptiveLimiterDemo {
    public static void main(String[] args) {
        // 使用 Vegas 算法自适应调整并发限制
        VegasLimit limit = VegasLimit.newBuilder()
            .initialLimit(10)
            .maxConcurrency(100)
            .build();

        SimpleLimiter<String> limiter = SimpleLimiter.newBuilder()
            .limit(limit)
            .build();

        for (int i = 0; i < 20; i++) {
            long startTime = System.nanoTime();

            limiter.listen(limiter.acquire("key").orElse(null))
                .onSuccess(startTime, () -> System.out.println("Request OK, limit=" + limit.getLimit()))
                .onDropped(() -> System.out.println("Request dropped, limit=" + limit.getLimit()));
        }
    }
}

Vegas 算法基于排队论中的 Little’s Law,通过比较实际延迟与最小延迟的比值来判断系统是否拥塞。当实际延迟显著高于最小延迟时,说明队列开始堆积,此时应降低并发限制。这种基于反馈的控制方式无需人工调参,特别适合负载波动频繁的业务场景。

9. 服务网格熔断(Istio/eBPF)

传统熔断器以应用库的形式嵌入代码,而在 Kubernetes 和服务网格架构中,熔断能力正逐步下沉到基础设施层,实现语言无关的统一防护。

9.1 Istio DestinationRule

Istio 通过 DestinationRule 的 outlierDetection 配置实现熔断,无需修改任何业务代码。

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service-circuit-breaker
spec:
  host: order-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        http1MaxPendingRequests: 50
        http2MaxRequests: 100
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutiveErrors: 5
      interval: 30s
      baseEjectionTime: 30s
      maxEjectionPercent: 50
      minHealthPercent: 40

上述配置中,consecutiveErrors: 5 表示连续五次错误后触发熔断,baseEjectionTime 定义了实例被驱逐的基础时长,该时长会随驱逐次数指数退避增长。maxEjectionPercent 限制了可同时被驱逐的实例比例上限,防止雪崩时将所有实例全部驱逐。

9.2 eBPF 级别的熔断拦截

eBPF 允许在 Linux 内核中安全地执行自定义程序,实现了零侵入的流量观测和控制。基于 eBPF 的熔断器可以在内核态直接拦截和丢弃异常流量,避免了数据包从内核态到用户态的多次拷贝,延迟极低。开源项目如 Pixie 和 Cilium 正在探索将 eBPF 用于服务级别的细粒度流量控制和熔断决策,代表了下一代服务网格的技术方向。

10. 限流与熔断的联动设计

限流和熔断虽然都是保护手段,但作用和触发时机截然不同。限流针对的是正常流量中的突发部分,目的是保护系统资源不被耗尽;熔断针对的是依赖服务的故障,目的是防止故障扩散。二者需要协同设计,形成互补的防护体系。

10.1 何时限流、何时熔断

限流适用于以下场景:大促活动前的流量预热、接口被恶意刷量、某个用户的请求频率异常。熔断适用于:下游数据库响应时间飙升、第三方 API 持续返回五零三错误、某个微服务实例健康检查失败。

10.2 分级降级策略

将防护体系划分为多个等级,根据系统负载自动触发相应的降级措施:

等级CPU 使用率触发措施
正常< 60%全量功能正常运行
一级保护60%-75%限流非核心接口,关闭推荐算法
二级保护75%-85%熔断慢服务,页面降级为简化版
三级保护> 85%全面降级,仅保留交易核心链路

10.3 渐进式恢复

故障恢复时应采用渐进式策略,避免大量请求瞬间涌入导致二次故障。例如从 OPEN 状态恢复时,HALF-OPEN 阶段仅放行百分之十的流量进行探测,成功后逐步提升至百分之五十,最终恢复至全量。

11. 多语言实现对比

不同技术栈都有成熟的限流库可供选择。以下从 Go、Node.js、Python 三个语言展示限流和熔断的实战实现。

11.1 Go(golang.org/x/time/rate)

Go 语言标准生态的 rate 包实现了高效的令牌桶算法,基于原子操作和睡眠调度,性能极高。

package main

import (
    "context"
    "fmt"
    "time"
    "golang.org/x/time/rate"
)

func main() {
    // 每秒产生 10 个令牌,桶容量为 20
    limiter := rate.NewLimiter(10, 20)

    ctx := context.Background()

    for i := 0; i < 25; i++ {
        err := limiter.Wait(ctx)
        if err != nil {
            fmt.Printf("Request %d: error %v\n", i, err)
            continue
        }
        fmt.Printf("Request %d: allowed at %v\n", i, time.Now().Format("15:04:05.000"))
    }

    // 非阻塞尝试获取
    if limiter.Allow() {
        fmt.Println("Non-blocking request allowed")
    } else {
        fmt.Println("Non-blocking request rejected")
    }
}

11.2 Node.js(express-rate-limit)

Node.js 生态中 express-rate-limit 是最流行的限流中间件,支持基于内存或 Redis 的存储后端。

const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

const apiLimiter = rateLimit({
    windowMs: 60 * 1000,
    max: 100,
    message: 'Too many requests from this IP, please try again later.',
    standardHeaders: true,
    legacyHeaders: false,
});

app.use('/api/', apiLimiter);

app.get('/api/status', (req, res) => {
    res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

配合 bottleneck 库可以实现更细粒度的令牌桶限流,支持集群模式下的 Redis 分布式协调。

11.3 Python(slowapi)

Python 的 slowapi 是 Flask/FastAPI 的限流扩展,基于 limits 库实现,支持内存和 Redis 存储。

from fastapi import FastAPI
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded

app = FastAPI()
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.get("/items")
@limiter.limit("5/minute")
async def get_items():
    return {"items": ["item1", "item2"]}

@app.get("/health")
@limiter.limit("60/minute")
async def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Python 生态中的 pybreaker 库提供了类似 Resilience4j 的熔断器实现,支持状态监听和自定义降级逻辑,适合异步框架如 asyncio 和 FastAPI 使用。

总结

限流、熔断与降级是保障分布式系统韧性的三驾马车。限流从入口控制流量规模,熔断在服务层面阻止故障蔓延,降级在极端情况下牺牲非核心功能保全核心链路。从单机算法到 Redis 分布式限流,从应用内嵌到服务网格下沉,从固定阈值到自适应控制,防护技术的演进始终围绕一个核心目标:让系统在异常环境下优雅降级而非彻底崩溃。生产实践中应遵循分层防护、联动设计、渐进恢复的原则,将韧性设计融入系统架构的每一层。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「distributed-systems」更多文章

  1. 边缘计算架构与就近接入
  2. 分布式系统成本优化与容量治理
  3. 单体到分布式:遗留系统迁移与绞杀者模式