1. 为什么需要API限流?
在分布式系统架构中,API限流是保护系统稳定性的第一道防线。去年双十一期间,某电商平台因为未做好限流防护,导致瞬时流量激增引发系统雪崩,直接损失超过千万。这个惨痛教训告诉我们:没有限流的系统就像没有刹车的汽车,随时可能失控。
API限流的核心价值主要体现在三个方面:
- 防止资源耗尽:避免单个用户或服务占用过多系统资源
- 保障服务质量:确保系统在承受范围内平稳运行
- 应对突发流量:为系统提供缓冲时间进行弹性扩容
2. 主流限流算法对比
2.1 令牌桶算法(Token Bucket)
Guava RateLimiter采用的就是令牌桶算法。它的工作原理类似于游乐园门票发放系统:
- 有一个固定容量的桶(比如容量100)
- 以恒定速率往桶里放令牌(比如每秒10个)
- 每个请求需要消耗一个令牌
- 当桶空时拒绝新请求
// 创建每秒10个令牌的限流器 RateLimiter limiter = RateLimiter.create(10.0); // 获取令牌(阻塞等待) limiter.acquire(); // 尝试获取令牌(非阻塞) if(limiter.tryAcquire()) { // 执行业务逻辑 }实际项目中建议使用tryAcquire带超时参数的版本,避免线程长时间阻塞
2.2 漏桶算法(Leaky Bucket)
漏桶算法可以理解为带孔的水桶:
- 请求像水一样流入桶中
- 桶以固定速率漏水(处理请求)
- 当水满时(桶满)拒绝新请求
与令牌桶的区别在于:
- 令牌桶允许突发流量(只要桶里有令牌)
- 漏桶强制恒定速率,更平滑
2.3 滑动窗口算法
Redis限流示例中使用的就是滑动窗口的变种。它的核心思想是:
- 将时间划分为多个小窗口(比如1分钟划分为6个10秒窗口)
- 统计最近N个窗口的请求总数
- 超过阈值则拒绝
这种算法能平衡精度和性能,是分布式限流的常用方案。
3. SpringBoot集成Guava限流实战
3.1 最佳实践配置
在电商项目中,我们这样配置多维度限流:
@Configuration public class RateLimiterConfig { // 普通API限流:100次/秒 @Bean @Qualifier("apiRateLimiter") public RateLimiter apiRateLimiter() { return RateLimiter.create(100.0); } // 支付接口限流:20次/秒 @Bean @Qualifier("paymentRateLimiter") public RateLimiter paymentRateLimiter() { return RateLimiter.create(20.0); } }3.2 控制器层实现
建议使用AOP实现统一限流:
@Aspect @Component public class RateLimitAspect { @Autowired @Qualifier("apiRateLimiter") private RateLimiter apiRateLimiter; @Around("@annotation(rateLimited)") public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable { if(apiRateLimiter.tryAcquire(500, TimeUnit.MILLISECONDS)) { return joinPoint.proceed(); } throw new RateLimitExceededException("请求过于频繁"); } } // 自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimited {}3.3 性能优化技巧
- 预热模式:对于冷启动系统,使用预热限流器
// 预热期5分钟,最终达到100qps RateLimiter.create(100.0, 5, TimeUnit.MINUTES);- 动态调整:根据系统负载动态调整速率
// 监控系统CPU负载 double load = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); if(load > 4.0) { rateLimiter.setRate(rateLimiter.getRate() * 0.8); // 降级20% }4. 分布式限流Redis方案进阶
4.1 Lua脚本实现原子操作
原生Redis命令存在竞态条件,使用Lua脚本可以保证原子性:
local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local current = redis.call('GET', key) local count = 0 if current then count = tonumber(current) end if count >= limit then return 0 else redis.call('INCR', key) redis.call('EXPIRE', key, window) return 1 endSpring中调用:
public boolean isAllowed(String key, int limit, int window) { String script = "上述Lua脚本内容"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(limit), String.valueOf(window)); return result == 1; }4.2 红锁算法实现
为避免单点故障,可以使用RedLock:
public boolean redLockRateLimit(String resource, int limit, int windowSec) { List<RedisNode> nodes = getRedisNodes(); // 获取集群所有节点 int quorum = nodes.size()/2 + 1; int success = 0; for(RedisNode node : nodes) { if(acquireLock(node, resource, limit, windowSec)) { success++; if(success >= quorum) break; } } if(success < quorum) { // 释放已获取的锁 return false; } return true; }5. Resilience4j生产级配置
5.1 完整配置示例
resilience4j: ratelimiter: instances: order-service: limitForPeriod: 50 limitRefreshPeriod: 1s timeoutDuration: 100ms registerHealthIndicator: true eventConsumerBufferSize: 50 payment-service: limitForPeriod: 10 limitRefreshPeriod: 1s subscribeForEvents: true circuitbreaker: instances: order-service: registerHealthIndicator: true slidingWindowSize: 100 minimumNumberOfCalls: 105.2 监控集成
@Bean public MeterRegistryCustomizer<MeterRegistry> metrics() { return registry -> { RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry) .bindTo(registry); }; } // Prometheus配置 management: endpoints: web: exposure: include: health,metrics,prometheus metrics: export: prometheus: enabled: true6. 过滤器限流的陷阱与解决方案
6.1 内存泄漏问题
示例代码中的ConcurrentHashMap会持续增长,必须实现清理机制:
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.HOURS) public void cleanUp() { long now = System.currentTimeMillis(); requestCounts.keySet().removeIf(key -> now - lastRequestTimes.getOrDefault(key, 0L) > TimeUnit.HOURS.toMillis(1)); }6.2 集群同步方案
使用Redis+本地缓存二级方案:
// 本地缓存 LoadingCache<String, AtomicInteger> localCache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .build(key -> { Integer count = redisTemplate.opsForValue().get(key); return new AtomicInteger(count != null ? count : 0); }); // 定期同步到Redis @Scheduled(fixedRate = 30, timeUnit = TimeUnit.SECONDS) public void syncToRedis() { localCache.asMap().forEach((key, value) -> { redisTemplate.opsForValue().set(key, value.get()); }); }7. 性能压测数据对比
使用JMeter对四种方案进行测试(单机4核8G):
| 方案 | 吞吐量 (req/s) | 平均延迟 | 99线延迟 | 适用场景 |
|---|---|---|---|---|
| Guava单机 | 12,000 | 2ms | 15ms | 单机服务 |
| Redis+Lua | 8,500 | 5ms | 25ms | 分布式系统 |
| Resilience4j | 10,000 | 3ms | 20ms | 微服务架构 |
| Servlet Filter | 9,000 | 4ms | 22ms | ��统应用 |
8. 常见问题排查指南
8.1 限流不生效检查清单
配置检查:
- 确认RateLimiter.create()的参数单位是否正确
- 检查Spring的@Bean是否被正确注入
拦截路径:
- 确认Filter或AOP拦截了正确路径
- 使用Actuator的mappings端点验证
Redis连接:
- 检查Redis是否正常运行
- 确认RedisTemplate配置正确
8.2 性能问题优化
现象:限流导致TP99升高
解决方案:
- 使用tryAcquire替代acquire
- 调整等待超时时间(建议100-500ms)
- 考虑使用异步模式:
CompletableFuture.supplyAsync(() -> { if(rateLimiter.tryAcquire()) { return service.process(); } throw new RateLimitException(); }).exceptionally(ex -> { // 处理限流异常 return fallback(); });9. 多级限流架构设计
在实际生产环境中,建议采用多级限流策略:
客户端限流 → API网关限流 → 服务限流 → 方法限流具体实现:
- Nginx层限流:
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s; location /api { limit_req zone=api burst=50; proxy_pass http://backend; }- Spring Cloud Gateway:
@Bean public Customizer<RedisRateLimiter> rateLimiterCustomizer() { return config -> config.setIncludeHeaders(true); } // 路由配置 routes: - id: order-service uri: lb://order-service predicates: - Path=/orders/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 50 redis-rate-limiter.burstCapacity: 100- 方法级注解:
@RateLimiter(value = "order-service", fallbackMethod = "createOrderFallback") public Order createOrder(OrderRequest request) { // 业务逻辑 }10. 限流与熔断的协同
在实际项目中,限流需要与熔断器配合使用:
resilience4j: circuitbreaker: instances: order-service: failureRateThreshold: 50 waitDurationInOpenState: 5s slidingWindowSize: 20 ratelimiter: instances: order-service: limitForPeriod: 50 limitRefreshPeriod: 1s协同策略:
- 当熔断器打开时,直接拒绝请求
- 半开状态下允许少量请求通过
- 关闭状态下正常限流
监控看板应同时展示两种指标:
- 限流拒绝次数
- 熔断状态变化
- 请求成功率变化曲线