作为后端程序员我们需知道6限流是保障系统高可用的核心手段和幂等性、熔断降级并称 “三高系统三板斧”核心目标是限制系统的请求流量 / 并发数防止流量峰值打垮系统如秒杀、大促、恶意攻击场景。下面我会从限流核心原理、常用限流算法、Java 落地实现、如何实战选型四个维度给出可落地的限流方案一、限流核心认知1. 限流的核心目标保护系统避免高流量导致 CPU / 内存 / 数据库耗尽防止系统雪崩公平性合理分配系统资源避免单个用户 / 接口占用全部资源可降级限流时返回友好提示如 “系统繁忙请稍后再试”而非直接崩溃。2. 限流的关键维度限流维度适用场景示例接口 QPS通用场景如订单接口、用户接口限制/order/submit接口 QPS1000并发数资源密集型接口如库存扣减限制同时处理的库存扣减请求 200按用户 / IP防刷场景如登录、短信验证码单个 IP / 用户 1 分钟内最多调用 10 次按接口分组微服务场景订单服务所有接口总 QPS5000全局限流系统级保护整个应用的总 QPS10000二、常用限流算法从原理到实现1. 固定窗口算法简单易实现核心原理将时间划分为固定大小的 “窗口”如 1 秒每个窗口内维护一个计数器请求进来时计数器 1若计数器超过阈值触发限流窗口结束后计数器重置为 0。实现基于 AtomicInteger/** * 固定窗口限流QPS限流 */ public class FixedWindowLimiter { // 限流阈值如1秒最多100次请求 private final int limit 100; // 窗口大小毫秒 private final long windowSize 1000; // 窗口起始时间 private volatile long windowStart System.currentTimeMillis(); // 窗口内计数器 private final AtomicInteger count new AtomicInteger(0); public boolean tryAcquire() { long now System.currentTimeMillis(); // 1. 检查是否进入新窗口若是则重置计数器和窗口起始时间 if (now - windowStart windowSize) { windowStart now; count.set(0); } // 2. 计数器1判断是否超过阈值 return count.incrementAndGet() limit; } }优缺点优点实现简单、性能高缺点“临界问题”—— 如窗口阈值 100第 1 秒最后 10ms 来 99 个请求第 2 秒前 10ms 来 99 个请求20ms 内共 198 个请求突破限流阈值。2. 滑动窗口算法优化固定窗口核心原理将固定窗口拆分为更小的 “子窗口”如 1 秒拆分为 10 个 100ms 子窗口维护一个滑动窗口包含最近的 N 个子窗口请求进来时累加所有子窗口的计数器若超过阈值则限流窗口滑动时移除过期的子窗口新增新的子窗口。实现基于 ArrayDeque/** * 滑动窗口限流QPS限流 */ public class SlidingWindowLimiter { // 限流阈值 private final int limit 100; // 总窗口大小毫秒 private final long windowSize 1000; // 子窗口数量 private final int subWindowCount 10; // 子窗口大小 private final long subWindowSize windowSize / subWindowCount; // 存储子窗口的计数器双端队列 private final DequeWindowCounter windowQueue new ArrayDeque(); // 保证线程安全 private final ReentrantLock lock new ReentrantLock(); /** * 子窗口计数器 */ private static class WindowCounter { // 子窗口起始时间 long startTime; // 子窗口内请求数 int count; public WindowCounter(long startTime) { this.startTime startTime; this.count 0; } } public boolean tryAcquire() { lock.lock(); try { long now System.currentTimeMillis(); // 1. 移除过期的子窗口 while (!windowQueue.isEmpty() now - windowQueue.peekFirst().startTime windowSize) { windowQueue.pollFirst(); } // 2. 计算当前窗口总请求数 int totalCount windowQueue.stream().mapToInt(c - c.count).sum(); // 3. 判断是否超过阈值 if (totalCount limit) { return false; } // 4. 更新当前子窗口的计数器无则创建 if (windowQueue.isEmpty() || now - windowQueue.peekLast().startTime subWindowSize) { windowQueue.offerLast(new WindowCounter(now)); } windowQueue.peekLast().count; return true; } finally { lock.unlock(); } } }优缺点优点解决固定窗口的临界问题限流更精准缺点实现稍复杂需维护子窗口性能略低于固定窗口。3. 令牌桶算法平滑限流核心原理令牌桶以固定速率生成令牌如 100 个 / 秒存入令牌桶令牌桶有最大容量满了之后新令牌丢弃请求进来时需从桶中获取 1 个令牌获取成功则处理请求失败则限流支持 “突发流量”若桶中有累积令牌可一次性处理多个请求如桶满 100 个可瞬间处理 100 个请求。实现基于 Guava RateLimiterGuava 提供了开箱即用的令牌桶实现无需手动写算法import com.google.common.util.concurrent.RateLimiter; /** * 令牌桶限流Guava实现 */ public class TokenBucketLimiter { // 创建令牌桶每秒生成100个令牌QPS100 private final RateLimiter rateLimiter RateLimiter.create(100.0); /** * 尝试获取令牌非阻塞 */ public boolean tryAcquire() { // 获取1个令牌无等待时间 return rateLimiter.tryAcquire(1); } /** * 获取令牌阻塞最多等100ms */ public boolean tryAcquireWithWait() { return rateLimiter.tryAcquire(1, 100, TimeUnit.MILLISECONDS); } }优缺点优点支持突发流量限流平滑实现简单Guava 开箱即用缺点不支持 “并发数限流”仅适用于 QPS 限流。4. 漏桶算法匀速限流核心原理漏桶有固定容量请求进入漏桶后排队漏桶以固定速率 “漏水”处理请求速率恒定若漏桶满了新请求直接被限流适合 “要求请求匀速处理” 的场景如对接第三方接口避免高频调用。实现基于 BlockingQueue/** * 漏桶限流 */ public class LeakyBucketLimiter { // 漏桶容量 private final int bucketCapacity 100; // 漏水速率每秒处理的请求数 private final int leakRate 10; // 阻塞队列模拟漏桶 private final BlockingQueueLong bucket new ArrayBlockingQueue(bucketCapacity); // 漏水线程 private final ScheduledExecutorService executor Executors.newSingleThreadScheduledExecutor(); public LeakyBucketLimiter() { // 启动漏水线程每秒漏出leakRate个请求 executor.scheduleAtFixedRate(() - { for (int i 0; i leakRate; i) { bucket.poll(); } }, 0, 1, TimeUnit.SECONDS); } public boolean tryAcquire() { // 请求入桶失败则限流 return bucket.offer(System.currentTimeMillis()); } // 关闭线程池 public void close() { executor.shutdown(); } }优缺点优点严格控制请求处理速率避免突发流量冲击第三方系统缺点无法处理突发流量请求排队可能导致响应延迟。三、Java 后端限流落地实践1. 本地限流单实例适用场景单实例部署、低并发场景快速验证限流逻辑。实现方式基于 Guava RateLimiter令牌桶基于 AtomicInteger固定窗口示例Spring Boot 接口本地限流RestController RequestMapping(/api) public class RateLimitController { // 令牌桶限流QPS10 private final RateLimiter rateLimiter RateLimiter.create(10.0); GetMapping(/test) public ResultString test() { // 尝试获取令牌无等待 if (!rateLimiter.tryAcquire()) { return Result.fail(请求过于频繁请稍后再试); } // 执行业务逻辑 return Result.success(操作成功); } }2. 分布式限流多实例适用场景微服务多实例部署、高并发场景需全局统一限流如整个订单服务 QPS1000。实现方式Redis Lua 脚本Redis Lua 脚本保证限流逻辑的原子性避免并发问题Service public class RedisRateLimiter { Autowired private StringRedisTemplate redisTemplate; // Lua脚本固定窗口限流 private static final String FIXED_WINDOW_LUA_SCRIPT local key KEYS[1] local limit tonumber(ARGV[1]) local windowSize tonumber(ARGV[2]) -- 获取当前计数器 local count tonumber(redis.call(get, key) or 0) if count limit then return 0 end -- 计数器1设置过期时间 count redis.call(incr, key) if count 1 then redis.call(expire, key, windowSize) end return 1 ; // 加载Lua脚本 private final DefaultRedisScriptLong script new DefaultRedisScript(FIXED_WINDOW_LUA_SCRIPT, Long.class); /** * 分布式限流 * param key 限流key如order:submit:qps * param limit 限流阈值 * param windowSize 窗口大小秒 * return true-允许访问false-限流 */ public boolean tryAcquire(String key, int limit, int windowSize) { Long result redisTemplate.execute( script, Collections.singletonList(key), String.valueOf(limit), String.valueOf(windowSize) ); return result ! null result 1; } }接口使用分布式限流RestController RequestMapping(/order) public class OrderController { Autowired private RedisRateLimiter redisRateLimiter; PostMapping(/submit) public ResultString submitOrder() { // 分布式限流订单提交接口1秒最多1000次请求 boolean allow redisRateLimiter.tryAcquire(order:submit:qps, 1000, 1); if (!allow) { return Result.fail(系统繁忙请稍后再试); } // 执行业务逻辑 return Result.success(订单提交成功); } }3. 网关限流全局限流适用场景微服务网关层Spring Cloud Gateway/Spring Cloud Zuul统一拦截所有请求实现全局 / 接口级限流。实现方式Spring Cloud Gateway Redis/** * 网关限流过滤器 */ Component public class RateLimitGatewayFilter implements GatewayFilter, Ordered { Autowired private RedisRateLimiter redisRateLimiter; Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求路径作为限流key String path exchange.getRequest().getPath().toString(); String key gateway:limit: path; // 限流规则QPS1000 boolean allow redisRateLimiter.tryAcquire(key, 1000, 1); if (!allow) { // 限流返回 ServerHttpResponse response exchange.getResponse(); response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); String body {\code\:429,\msg\:\请求过于频繁\}; return response.writeWith(Mono.just(response.bufferFactory().wrap(body.getBytes()))); } // 放行 return chain.filter(exchange); } Override public int getOrder() { // 过滤器执行顺序优先于业务过滤器 return -1; } }4. 开源框架限流生产首选Sentinel阿里开源支持流量控制、熔断降级、系统保护可配置限流规则QPS / 并发数 / 按 IP / 按用户支持控制台可视化配置Resilience4j轻量级熔断限流框架替代 Hystrix支持令牌桶 / 滑动窗口限流Spring Cloud Circuit Breaker整合 Resilience4j/Sentinel快速接入 Spring 生态。Sentinel 示例注解式限流引入依赖dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-sentinel/artifactId /dependency接口限流RestController RequestMapping(/product) public class ProductController { // 限流规则QPS100超出则返回自定义提示 SentinelResource(value product:detail, blockHandler blockHandler) GetMapping(/detail/{id}) public ResultProductDTO detail(PathVariable Long id) { // 执行业务逻辑 return Result.success(getProductById(id)); } // 限流兜底方法 public ResultProductDTO blockHandler(Long id, BlockException e) { return Result.fail(商品查询过于频繁请稍后再试); } }四、限流选型与注意事项1.选型原则场景首选方案次选方案单实例、简单限流Guava RateLimiter令牌桶固定窗口算法多实例、全局限流Redis Lua 脚本 / Sentinel网关限流匀速处理请求漏桶算法滑动窗口算法支持突发流量令牌桶算法滑动窗口算法防刷、按 IP / 用户限流Sentinel按来源限流Redis IP / 用户 ID 作为限流 key2.注意事项限流粒度避免过粗如全局限流或过细如每个接口都单独限流按业务分组限流兜底策略限流时返回友好提示而非 500 错误核心接口可配置 “降级接口”如返回缓存数据动态调整生产环境需支持动态修改限流阈值如 Sentinel 控制台、Nacos 配置无需重启服务避免死锁 / 性能问题分布式限流需保证 Lua 脚本原子性本地限流需保证线程安全监控告警限流触发时记录日志、发送告警如钉钉 / 短信便于排查流量异常。总结限流的核心算法有 4 种固定窗口简单、滑动窗口精准、令牌桶平滑支持突发、漏桶匀速落地方式分 3 类本地限流单实例、分布式限流多实例RedisLua、网关限流全局生产环境优先选择成熟框架Sentinel/Resilience4j减少手动实现的 bug支持动态配置和可视化监控。say it simply限流的本质是 “取舍”—— 牺牲部分请求保证系统核心功能可用选择合适的算法和落地方式才能在流量峰值下保障系统稳定。