在高并发场景下,比如电商秒杀、春运抢票、直播带货,突然涌入的海量请求往往会击穿系统瓶颈 —— 数据库连接耗尽、服务线程池满负荷、接口响应超时,最终导致系统雪崩。而限流,就是应对这类问题的 “安全阀”:通过限制单位时间内的请求量,让系统在可控范围内提供服务,避免因过载而彻底崩溃。今天我们就从 “为什么要限流” 出发,逐层拆解限流技术栈的核心原理与落地方案。
在讲技术前,先明确限流的核心价值 —— 它不是 “限制用户体验”,而是 “在有限资源下保障多数用户的正常体验”,主要解决三类问题:
限流不是 “单点操作”,而是需要在用户侧→网关层→应用层→分布式层形成 “多层防御”,不同层级的技术选型和目标不同,我们逐层拆解。
前端是限流的 “第一道防线”,目标是从源头减少不必要的请求,提升用户体验(避免 “点了没反应,反复点”),常用方案有:
function throttle(fn, delay) { let lastTime = 0; return (...args) => { const now = Date.now(); if (now - lastTime > delay) { fn.apply(this, args); lastTime = now; } };}// 1秒内只能触发1次请求const limitedRequest = throttle(fetchData, 1000);
注意:前端限流仅为 “辅助手段”,不能作为唯一防线 —— 用户可通过修改代码绕过,必须配合后端限流。
应用层限流针对单个服务实例,核心是通过算法控制 “单位时间内处理的请求数”,是限流技术栈的 “核心层”。常用的单机限流算法有 4 种,各有优劣:
public class CounterLimiter { private int count = 0; private long lastResetTime = System.currentTimeMillis(); private final int limit = 100; // 1秒最多100次请求 private final long interval = 1000; // 1秒 public synchronized boolean allow() { long now = System.currentTimeMillis(); // 超过1秒,重置计数器 if (now - lastResetTime > interval) { count = 0; lastResetTime = now; } if (count < limit) { count++; return true; } return false; }}
import com.google.common.util.concurrent.RateLimiter;public class TokenBucketLimiter { // 每秒生成100个令牌(QPS=100) private final RateLimiter rateLimiter = RateLimiter.create(100.0); public boolean allow() { // 尝试获取1个令牌,无令牌则返回false(非阻塞) return rateLimiter.tryAcquire(); }}
当服务部署多实例时(比如 10 台机器),单机限流(QPS=100)会导致总 QPS=1000,若实际需要总 QPS=500,就必须用分布式限流—— 跨实例统一计数,控制全局请求量。常用方案有 2 种:
Redis 的INCR(原子自增)和EXPIRE(过期时间)特性,天然适合分布式计数,核心逻辑:
为避免 “先 INCR 再 EXPIRE” 的原子性问题(比如 INCR 后服务宕机,键未设置过期),需用Lua 脚本保证操作原子性,代码示例(Redis Lua):
-- 限流脚本:key=限流标识,limit=阈值,expire=过期时间(秒)local key = KEYS[1]local limit = tonumber(ARGV[1])local expire = tonumber(ARGV[2])-- 原子自增并获取当前计数local current = redis.call('incr', key)-- 第一次计数时,设置过期时间if current == 1 then redis.call('expire', key, expire)end-- 判断是否超过阈值if current > limit then return 0 -- 拒绝else return 1 -- 允许end
进阶优化:若担心 Redis 单点故障,可部署 Redis 集群;若需要更精细的时间粒度(比如 100ms 窗口),可结合 “滑动窗口” 思想,用 Redis 的ZSET存储请求时间戳,统计窗口内的请求数。
消息队列(MQ)的 “队列缓冲” 特性,可同时实现 “削峰” 和 “限流”:
适用场景:秒杀、抢票等 “流量突发且允许短暂延迟” 的场景,比如阿里 “双 11” 用 RocketMQ 承载秒杀流量,后端按能力消费。
网关(如 Nginx、Spring Cloud Gateway、APISIX)是所有请求的 “入口”,在网关层做限流,可提前拦截无效请求,避免请求穿透到后端服务,减轻应用层压力。
Nginx 通过ngx_http_limit_req_module模块实现限流,基于 “漏桶算法”,配置示例:
# 1. 定义限流规则:key为$binary_remote_addr(客户端IP),1秒最多100次请求(rate=100r/s),桶容量10limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;server { listen 80; server_name api.example.com; location /api/user { # 2. 应用限流规则,burst=10表示允许10个请求排队等待 limit_req zone=api_limit burst=10 nodelay; # 限流后返回429状态码(Too Many Requests) limit_req_status 429; # 转发到后端服务 proxy_pass http://user-service; }}
Spring Cloud Gateway 基于 “过滤器” 实现限流,可结合 Redis 实现分布式限流,核心依赖:
<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>
配置示例(application.yml):
spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/api/user/**filters: - name: RequestRateLimiter args: # 用Redis存储限流计数 redis-rate-limiter.replenishRate: 100 # 每秒生成100个令牌 redis-rate-limiter.burstCapacity: 200 # 令牌桶最大容量200 # 限流key:客户端IP key-resolver: "#{@ipKeyResolver}"
自定义 KeyResolver(按 IP 限流):
@Configurationpublic class RateLimiterConfig { @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() ); }}
掌握了技术栈,落地时还需注意 3 个关键问题,否则可能 “限流生效了,但业务崩了”:
阈值不能 “拍脑袋”,需结合压测结果 + 业务预期:
限流不是 “拒绝就完事”,需给用户友好反馈,常见降级策略:
限流需要 “动态调整”,需实时监控以下指标:
限流技术栈的核心逻辑是 “分层防御 + 按需选型”:
未来,限流会更 “智能”—— 结合 AI 实时分析流量特征(比如识别爬虫流量、异常峰值),动态调整阈值;同时,云原生场景下,限流会与 K8s HPA(弹性伸缩)结合,实现 “限流 + 扩容” 的自动化联动,进一步提升系统稳定性。
如果你在限流落地中遇到过 “阈值难确定”“分布式一致性” 等问题,欢迎在评论区交流,一起完善限流方案!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。