限流
限流(Rate Limiting)通过控制单位时间内请求处理量,防止系统因流量过载而崩溃,是高可用架构的核心防护手段。
算法对比
| 算法 | 原理 | 特点 | 适用场景 |
|---|---|---|---|
| 固定窗口 | 固定时间窗内计数,超出拒绝 | 实现简单,存在临界突刺问题 | 对精度要求不高的场景 |
| 滑动窗口 | 按当前时间向前滑动统计 | 解决临界突刺,内存稍大 | 通用限流 |
| 漏桶 | 请求入桶,以固定速率流出 | 平滑输出,无法应对突发 | 恒定速率处理 |
| 令牌桶 | 按速率生成令牌,有令牌才放行 | 允许一定突发,最常用 | 接口限流、API 网关 |
令牌桶是生产中最常见的选择:平均速率可控,同时允许短时突发流量消耗桶中积累的令牌。
方案一:Bucket4j(本地令牌桶)
适合单机或无需跨实例同步的场景。
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>8.10.1</version>
</dependency>基本用法
@Component
public class RateLimitService {
// 每秒 100 个令牌,桶容量 100
private final Bucket bucket = Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofSeconds(1))))
.build();
public boolean tryConsume() {
return bucket.tryConsume(1);
}
// 阻塞等待令牌(适合后台任务)
public void consume() throws InterruptedException {
bucket.asBlocking().consume(1);
}
}多维度限流(接口 + 用户)
@Component
public class UserRateLimiter {
// 每个用户独立的桶
private final Map<String, Bucket> userBuckets = new ConcurrentHashMap<>();
private Bucket getBucket(String userId) {
return userBuckets.computeIfAbsent(userId, k ->
Bucket.builder()
.addLimit(Bandwidth.classic(10, Refill.intervally(10, Duration.ofMinutes(1))))
.build()
);
}
public boolean tryConsume(String userId) {
return getBucket(userId).tryConsume(1);
}
}注解 + 拦截器封装
// 1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
long capacity() default 100; // 桶容量
long refillTokens() default 100; // 每次补充令牌数
long refillPeriodSeconds() default 1; // 补充周期(秒)
String keyPrefix() default ""; // 限流 key 前缀
}
// 2. 拦截器
@Component
@RequiredArgsConstructor
@Slf4j
public class RateLimitInterceptor implements HandlerInterceptor {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod method)) return true;
RateLimit annotation = method.getMethodAnnotation(RateLimit.class);
if (annotation == null) return true;
String key = annotation.keyPrefix() + ":" + request.getRequestURI();
Bucket bucket = buckets.computeIfAbsent(key, k ->
Bucket.builder()
.addLimit(Bandwidth.classic(
annotation.capacity(),
Refill.greedy(annotation.refillTokens(),
Duration.ofSeconds(annotation.refillPeriodSeconds()))
))
.build()
);
if (bucket.tryConsume(1)) {
return true;
}
log.warn("限流触发: uri={}", request.getRequestURI());
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().write("{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}");
return false;
}
}
// 3. 使用
@RestController
public class UserController {
@GetMapping("/users")
@RateLimit(capacity = 50, refillTokens = 50, refillPeriodSeconds = 1)
public List<User> listUsers() {
return userService.findAll();
}
}方案二:Resilience4j RateLimiter
Resilience4j 是 Spring Boot 3.x 推荐的弹性组件,支持限流、熔断、重试、舱壁等模式。
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.2.0</version>
</dependency>resilience4j:
ratelimiter:
instances:
userApi:
limit-for-period: 100 # 每个周期最多 100 次请求
limit-refresh-period: 1s # 周期长度
timeout-duration: 0 # 等待令牌超时时间(0 = 立即失败)
register-health-indicator: true
orderApi:
limit-for-period: 20
limit-refresh-period: 1s
timeout-duration: 500ms # 等待最多 500ms注解方式
@Service
public class UserService {
@RateLimiter(name = "userApi", fallbackMethod = "getUserFallback")
public User getById(Long id) {
return userRepository.findById(id).orElseThrow();
}
// 限流触发时的降级方法(参数列表 + RequestNotPermitted)
public User getUserFallback(Long id, RequestNotPermitted ex) {
log.warn("限流降级: userId={}", id);
return User.empty(); // 返回空对象或抛出友好异常
}
}编程式
@Component
@RequiredArgsConstructor
public class OrderService {
private final RateLimiterRegistry rateLimiterRegistry;
public Order createOrder(CreateOrderRequest req) {
RateLimiter limiter = rateLimiterRegistry.rateLimiter("orderApi");
// 尝试获取许可(立即返回 boolean)
if (!limiter.acquirePermission()) {
throw new TooManyRequestsException("下单过于频繁");
}
return processOrder(req);
}
// 装饰函数式
public Supplier<Order> decoratedCreate(CreateOrderRequest req) {
return RateLimiter.decorateSupplier(
rateLimiterRegistry.rateLimiter("orderApi"),
() -> processOrder(req)
);
}
}与熔断、重试组合
resilience4j:
ratelimiter:
instances:
paymentApi:
limit-for-period: 30
limit-refresh-period: 1s
circuitbreaker:
instances:
paymentApi:
failure-rate-threshold: 50
sliding-window-size: 10
retry:
instances:
paymentApi:
max-attempts: 3
wait-duration: 500ms// 组合顺序:Retry(CircuitBreaker(RateLimiter(fn)))
@RateLimiter(name = "paymentApi")
@CircuitBreaker(name = "paymentApi")
@Retry(name = "paymentApi")
public PaymentResult pay(PaymentRequest request) {
return paymentClient.pay(request);
}方案三:Redis + Lua 滑动窗口(分布式)
跨多实例共享限流计数,适合集群部署。
@Component
@RequiredArgsConstructor
public class RedisRateLimiter {
private final StringRedisTemplate redisTemplate;
// Lua 脚本:滑动窗口限流(原子操作)
private static final String SLIDING_WINDOW_SCRIPT = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local unique = ARGV[4]
-- 移除窗口外的旧记录
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 统计窗口内的请求数
local count = redis.call('ZCARD', key)
if count < limit then
-- 未达到限制,记录本次请求
redis.call('ZADD', key, now, unique)
redis.call('EXPIRE', key, math.ceil(window / 1000))
return 1
end
return 0
""";
private final DefaultRedisScript<Long> script = new DefaultRedisScript<>(
SLIDING_WINDOW_SCRIPT, Long.class);
/**
* 滑动窗口限流
* @param key 限流 key(如 "ratelimit:api:/users:127.0.0.1")
* @param windowMs 窗口大小(毫秒)
* @param limit 窗口内最大请求数
*/
public boolean tryAcquire(String key, long windowMs, int limit) {
long now = System.currentTimeMillis();
Long result = redisTemplate.execute(
script,
List.of(key),
String.valueOf(now),
String.valueOf(windowMs),
String.valueOf(limit),
UUID.randomUUID().toString()
);
return Long.valueOf(1L).equals(result);
}
}基于 Redis 令牌桶(Redisson)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.2</version>
</dependency>@Component
@RequiredArgsConstructor
public class RedissonRateLimiter {
private final RedissonClient redissonClient;
public RRateLimiter getOrCreate(String name, long rate, long rateInterval) {
RRateLimiter limiter = redissonClient.getRateLimiter(name);
// 初始化:每 rateInterval 秒补充 rate 个令牌
limiter.trySetRate(RateType.OVERALL, rate, rateInterval, RateIntervalUnit.SECONDS);
return limiter;
}
public boolean tryAcquire(String name) {
RRateLimiter limiter = getOrCreate(name, 100, 1);
return limiter.tryAcquire();
}
// 带超时等待
public boolean tryAcquireWithTimeout(String name, long timeout, TimeUnit unit) {
RRateLimiter limiter = getOrCreate(name, 100, 1);
return limiter.tryAcquire(timeout, unit);
}
}Redis 集成详见 Redis集成。
方案四:Spring Cloud Gateway 网关层限流
网关统一限流,应用层无需关心,适合微服务架构。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- name: RequestRateLimiter
args:
# 令牌桶:每秒补充 10 个令牌,桶容量 20
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
# 根据什么维度限流(此处按 IP 限流)
key-resolver: "#{@ipKeyResolver}"@Configuration
public class GatewayConfig {
// 按 IP 限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
.getAddress().getHostAddress()
);
}
// 按用户 ID 限流(需解析 Token)
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String token = exchange.getRequest().getHeaders()
.getFirst(HttpHeaders.AUTHORIZATION);
String userId = jwtUtil.extractUserId(token);
return Mono.just("user:" + userId);
};
}
// 按接口路径限流
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getPath().value()
);
}
}方案五:Sentinel(阿里巴巴,功能最全)
Sentinel 支持 QPS 限流、并发线程数限流、热点参数限流、系统自适应限流,并提供实时监控控制台。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台地址
port: 8719
eager: true@Service
public class ProductService {
// @SentinelResource 定义资源 + 降级方法
@SentinelResource(
value = "getProduct",
blockHandler = "handleBlock", // 限流/熔断时调用
fallback = "handleFallback" // 业务异常时调用
)
public Product getProduct(Long id) {
return productRepository.findById(id).orElseThrow();
}
// blockHandler:FlowException(限流)/ DegradeException(熔断)
public Product handleBlock(Long id, BlockException ex) {
log.warn("Sentinel 限流: id={}, rule={}", id, ex.getRule());
return Product.empty();
}
// fallback:业务异常
public Product handleFallback(Long id, Throwable t) {
log.error("服务降级: id={}", id, t);
return Product.empty();
}
}
// 编程式配置规则(也可通过控制台动态推送)
@PostConstruct
public void initRules() {
// QPS 限流规则
FlowRule rule = new FlowRule("getProduct")
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(50) // 每秒最多 50 次
.setStrategy(RuleConstant.STRATEGY_DIRECT)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); // 直接拒绝
FlowRuleManager.loadRules(List.of(rule));
// 热点参数限流(对特定参数值单独限流)
ParamFlowRule paramRule = new ParamFlowRule("getProduct")
.setParamIdx(0) // 第 0 个参数(id)
.setCount(10); // 每秒最多 10 次
ParamFlowRuleManager.loadRules(List.of(paramRule));
}限流响应统一处理
@RestControllerAdvice
public class GlobalExceptionHandler {
// Bucket4j / 自定义限流异常
@ExceptionHandler(TooManyRequestsException.class)
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResponse<?> handleRateLimit(TooManyRequestsException ex) {
return ApiResponse.error(429, "请求过于频繁,请稍后再试");
}
// Resilience4j
@ExceptionHandler(RequestNotPermitted.class)
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResponse<?> handleRateLimiter(RequestNotPermitted ex) {
return ApiResponse.error(429, "系统繁忙,请稍后再试");
}
// Sentinel
@ExceptionHandler(FlowException.class)
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ApiResponse<?> handleSentinel(FlowException ex) {
return ApiResponse.error(429, "当前访问人数过多");
}
}全局异常处理详见 全局异常处理。
方案选型建议
| 场景 | 推荐方案 |
|---|---|
| 单机、简单接口限流 | Bucket4j |
| 单机、需要熔断/重试组合 | Resilience4j |
| 多实例、分布式限流 | Redis(Redisson / Lua 脚本) |
| 微服务网关统一限流 | Spring Cloud Gateway + Redis |
| 大流量、需实时监控 | Sentinel |