限流算法深度解析:从计数器到令牌桶
核心观点: 限流算法不是单纯的技术选择,而是业务需求与技术约束的权衡。本文从一个真实需求的演进过程出发,深度剖析5种限流算法的设计思路、实现细节和适用场景。 引子:一个API接口的限流需求演进 需求背景 你是某电商平台的后端工程师,负责维护商品查询接口 /api/products/{id}。 初始状态(无限流): @RestController public class ProductController { @Autowired private ProductService productService; @GetMapping("/api/products/{id}") public Result getProduct(@PathVariable Long id) { Product product = productService.getById(id); return Result.success(product); } } 系统运行良好,日常流量500 QPS,数据库和服务器完全可以承受。 第一次危机:流量突增 某天,运营部门做了一个促销活动,流量突然涨到5000 QPS(10倍)。 后果: 10:00:00 - 活动开始,流量5000 QPS 10:00:30 - 数据库连接池耗尽(最大连接数100) 10:01:00 - 响应时间从50ms暴增到5秒 10:01:30 - 服务器CPU 100%,系统崩溃 CTO找你谈话: “给这个接口加个限流,最多支持1000 QPS。” 你的第一反应:计数器! 需求演进1:固定窗口计数器 需求: 每秒最多1000个请求。 你快速实现了一个固定窗口计数器: public class FixedWindowRateLimiter { private final int maxRequests; private final AtomicInteger counter = new AtomicInteger(0); private volatile long windowStart = System.currentTimeMillis(); public FixedWindowRateLimiter(int maxRequests) { this.maxRequests = maxRequests; } public boolean tryAcquire() { long now = System.currentTimeMillis(); // 新窗口,重置计数器 if (now - windowStart >= 1000) { synchronized (this) { if (now - windowStart >= 1000) { counter.set(0); windowStart = now; } } } // 当前窗口未超限 return counter.incrementAndGet() <= maxRequests; } } 部署上线,问题解决!系统稳定运行。 ...