分布式限流:令牌桶与漏桶算法在网关层的落地
作者 :Weisian
发布时间:2026年4月

直击痛点:
"双十一流量洪峰瞬间冲垮服务器,99%的开发者只知道加机器,却不知道限流 才是成本最低的保命手段;面试官问'令牌桶和漏桶的区别',80%的人只背了'令牌桶允许突发',却说不出分布式环境下如何保证限流精准性才是落地难点。"
分布式限流是微服务架构中最核心的防御机制之一:
- 四大算法:计数器、滑动窗口、漏桶、令牌桶,各有优劣;
- 三层限流:Nginx层(防DDoS)、网关层(细粒度)、应用层(熔断降级);
- 分布式难点:如何跨多个节点精准统计流量?Redis原子操作 vs 本地近似限流;
- 工业实践:Sentinel、Resilience4j、Spring Cloud Gateway限流。
本文将从生活类比 切入,结合底层原理 、代码实战 、面试考点 ,彻底讲透分布式限流的设计与落地:
✅ 四大限流算法深度对比(图解+代码);
✅ 单机限流:Guava RateLimiter实战;
✅ 分布式限流:Redis+Lua原子操作实现;
✅ 三层限流架构:Nginx→网关→应用层;
✅ Sentinel集群限流实战;
✅ 面试高频题标准答案(直接背);
✅ 选型指南:不同场景怎么选限流方案。
📌 核心一句话 :
限流=给系统装流量阀门 ,分布式限流=给整个集群装总阀门;计数器简单但有临界漏洞,滑动窗口解决临界但内存占用高,漏桶强制匀速限流但无法应对突发,令牌桶兼顾平滑和突发是工业首选;分布式限流需用Redis+Lua保证原子性,或使用Sentinel实现集群限流,网关层是分布式限流的最佳落地位置。
📌 面试金句先记牢:
- 计数器算法(固定窗口):固定时间窗口计数,临界时刻流量翻倍,存在超限流风险;
- 滑动窗口:拆分小窗口解决临界问题,精度更高,复杂度中等;
- 漏桶算法 :水流匀速流出,强制平滑流量,无法应对突发合理流量;
- 令牌桶算法 :桶内存令牌,请求拿令牌,支持匀速+突发流量,生产最常用;
- 分布式限流必须用原子操作(Redis Lua),否则多机并发会超量;
- 分布式限流关键:Redis+Lua保证原子性,避免竞态条件;
- 三层限流:Nginx(IP限流,防DDoS) → Gateway(集群全局限流) → Sentinel(接口限流,单机保护)。
一、为什么要限流?
1.1 限流的核心意义:系统的"保命阀门"
用生活类比最容易理解:
- 限流 = 景区限流:景区最大承载5000人,超过就不让进,避免拥挤踩踏;
- 分布式限流 = 整个景区连锁园区限流:所有园区入口统一管控,总人数不超标。
高并发下不限流的后果:
- 服务雪崩:一个接口流量暴涨,拖垮整个微服务集群;
- 资源耗尽:数据库连接池、线程池、CPU 100%,服务假死;
- SLA不达标:核心业务响应超时,用户流失;
- 恶意攻击:DDoS流量直接打垮服务器。

1.2 限流的四个维度
| 维度 | 说明 | 示例 |
|---|---|---|
| QPS限流 | 限制每秒请求数 | 1000 QPS |
| 并发线程数限流 | 限制同时处理的请求数 | 50并发 |
| IP限流 | 限制单个IP的访问频率 | 100次/分钟 |
| 用户限流 | 限制单个用户的访问频率 | 10次/秒 |

1.3 分布式限流 vs 单机限流
| 类型 | 适用场景 | 精准性 | 实现难度 |
|---|---|---|---|
| 单机限流 | 单体应用、单实例服务 | 高(本地计数) | 低 |
| 分布式限流 | 微服务集群、多实例部署、网关统一管控 | 高(全局计数) | 中 |
核心痛点 :
分布式环境下,流量会负载均衡到N台机器,每台机器单独限流=总流量=N倍单机限流,直接导致限流失效!
例:集群3台机器,单机限流100QPS → 集群总限流300QPS,远超预期100QPS。

二、四大限流算法深度对比
2.1 计数器算法(固定窗口)
原理:将时间划分为固定窗口,每个窗口内计数,超过阈值则拒绝。
时间线: |---1s---||---1s---||---1s---|
请求数: 1000 1000 1000
窗口1 窗口2 窗口3

临界问题:
- 窗口1的最后100ms(999个请求)+ 窗口2的前100ms(999个请求)= 1998个请求
- 实际1s内通过了1998个请求,远超阈值1000
java
/**
* 计数器限流(有临界问题)
*/
public class CounterRateLimiter {
private long limit = 1000; // 每秒限制1000个请求
private long interval = 1000; // 窗口大小1秒
private long windowStart = System.currentTimeMillis(); // 起始窗口
private long count = 0; // 计数器
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 窗口过期,重置
if (now - windowStart > interval) {
// 进入下一个窗口
windowStart = now;
count = 0;
}
// 判断是否超限
if (count < limit) {
count++;
return true;
}
return false;
}
}
2.2 滑动窗口算法
原理:
-
把大窗口拆分为多个小格子(如1秒拆为10个100ms小窗口);
-
每次请求只统计最近1秒的所有小窗口总和;
-
随着时间推移,窗口持续滑动,淘汰旧数据。
时间线: |---|---|---|---|---|---|---|
格子: 1 2 3 4 5 6 7 8 (每个100ms)
滑动窗口: [5 6 7 8 9 10 11 12 13 14] → 统计滑动的10个格子

java
/**
* 滑动窗口限流
*/
public class SlidingWindowRateLimiter {
private int limit = 1000; // 每秒限制1000个请求
private int windowSize = 10; // 拆分为10个格子
private long slotSize = 100; // 每个格子100ms
private long[] counters = new long[windowSize];
private long currentSlot = 0;
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
long slotIndex = (now / slotSize) % windowSize;
// 重置过期格子
if (slotIndex != currentSlot) {
counters[(int) slotIndex] = 0;
currentSlot = slotIndex;
}
// 统计当前窗口总请求数
long total = 0;
for (long counter : counters) {
total += counter;
}
if (total >= limit) {
return false;
}
counters[(int) slotIndex]++;
return true;
}
}
优势
- 解决固定窗口临界问题;
- 精度可通过小格子数量调整;
- 实现比漏桶/令牌桶简单。
2.3 漏桶算法(强制平滑流量)
原理:请求先进入漏桶,以恒定速率处理,桶满则拒绝。
生活类比 :
漏斗装水,无论倒多快,水流速度永远固定,水满就溢出。

请求流入
↓
┌─────┐
│ 桶 │ ← 恒定速率流出
└─────┘
↓
处理
特点:强行平滑突发流量,但无法应对瞬时高峰。
java
/**
* 漏桶限流
*/
public class LeakyBucketRateLimiter {
private long capacity = 1000; // 桶容量
private long rate = 100; // 每秒处理100个请求
private long water = 0; // 当前水量
private long lastLeakTime = System.currentTimeMillis();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 计算漏掉的水量
long leakCount = (now - lastLeakTime) * rate / 1000;
water = Math.max(0, water - leakCount);
lastLeakTime = now;
// 判断是否溢出
if (water < capacity) {
water++;
return true;
}
return false;
}
}
2.4 令牌桶算法(工业首选)
原理:
- 系统以恒定速率向桶中放入令牌;
- 每个请求必须获取1个令牌才能执行;
- 桶满后,多余令牌被丢弃;
- 桶内有大量令牌时,可瞬间处理突发流量。
生活类比 :
餐厅发号器,每秒发10个号,号可以存起来,有号就能吃饭,没号就等待;号攒多了,可同时接待多人。

令牌放入
↓
┌─────┐
│ 桶 │ ← 请求获取令牌
└─────┘
↓
处理
特点:允许一定程度的突发(桶容量决定),Guava RateLimiter基于此实现。
java
/**
* 令牌桶限流
*/
public class TokenBucketRateLimiter {
private long capacity = 1000; // 桶容量
private long rate = 100; // 每秒生成100个令牌
private long tokens = capacity; // 当前令牌数
private long lastRefillTime = System.currentTimeMillis();
public synchronized boolean tryAcquire(int required) {
long now = System.currentTimeMillis();
// 补充令牌
long refillCount = (now - lastRefillTime) * rate / 1000;
tokens = Math.min(capacity, tokens + refillCount);
lastRefillTime = now;
// 获取令牌
if (tokens >= required) {
tokens -= required;
return true;
}
return false;
}
}
优势
- 支持匀速+突发流量,最贴合实际业务;
- 桶容量控制最大突发量;
- 分布式环境易实现。
2.5 四大算法对比
| 算法 | 突发处理 | 平滑度 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 计数器 | 差(临界问题) | 差 | 低 | 粗略限流 |
| 滑动窗口 | 中 | 中 | 中 | 监控统计 |
| 漏桶 | 无(强行平滑) | 高 | 中 | 恒定速率处理 |
| 令牌桶 | 有(桶容量) | 高 | 中 | 工业通用 |

三、单机限流:Guava RateLimiter实战
3.1 Guava RateLimiter使用
java
import com.google.common.util.concurrent.RateLimiter;
/**
* Guava令牌桶限流
*/
@Service
@Slf4j
public class GuavaRateLimiterService {
// 每秒生成10个令牌(QPS=10)
private RateLimiter rateLimiter = RateLimiter.create(10.0);
/**
* 非阻塞获取
*/
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
/**
* 阻塞获取
*/
public void acquire() {
rateLimiter.acquire();
}
/**
* 超时获取
*/
public boolean tryAcquire(long timeout, TimeUnit unit) {
return rateLimiter.tryAcquire(timeout, unit);
}
}
/**
* 使用示例
*/
@RestController
public class DemoController {
@Autowired
private GuavaRateLimiterService rateLimiterService;
@GetMapping("/api/test")
public Result test() {
if (!rateLimiterService.tryAcquire()) {
return Result.error("系统繁忙,请稍后重试");
}
// 业务逻辑
return Result.success();
}
}

3.2 核心API
RateLimiter.create(qps):创建令牌桶,指定每秒令牌数;acquire():阻塞获取令牌;tryAcquire():非阻塞获取,失败直接返回false;tryAcquire(timeout, unit):超时获取。
3.3 Guava RateLimiter的局限
- 单机有效:多实例部署时,总限流值 = 实例数 × 单机限流值,超出预期
- 无集群协调:无法统计全局流量
适用场景
单体应用、单实例服务、无需集群协调的场景。
四、分布式限流:Redis+Lua原子操作
4.1 为什么用 Redis + Lua?
分布式限流核心要求:原子性
- 多机器同时请求Redis,若先查再改,会出现并发超量问题;
- Lua脚本可以把多个Redis命令打包为一个原子操作,保证计数精准;
- Redis单线程执行Lua,无并发竞争,性能极高。

4.2 令牌桶Lua脚本
lua
-- Redis令牌桶限流Lua脚本
-- KEYS[1]: 令牌桶Key
-- ARGV[1]: 每秒生成的令牌数
-- ARGV[2]: 桶容量
-- ARGV[3]: 当前时间戳(毫秒)
-- ARGV[4]: 需要的令牌数
-- 返回值: 1-允许, 0-拒绝
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- 每秒生成令牌数
local capacity = tonumber(ARGV[2]) -- 桶容量
local now = tonumber(ARGV[3]) -- 当前时间(秒)
local requested = tonumber(ARGV[4]) -- 请求令牌数
-- 获取上次时间和当前令牌数
local lastRefillTime = redis.call('hget', key, 'last_time')
local tokens = redis.call('hget', key, 'tokens')
if lastRefillTime == false then
-- 第一次请求,初始化
tokens = capacity
lastRefillTime = now
else
tokens = tonumber(tokens)
lastRefillTime = tonumber(lastRefillTime)
-- 计算应该补充的令牌数
local delta = math.max(0, now - lastRefillTime) * rate
tokens = math.min(capacity, tokens + delta)
end
-- 判断是否放行
if tokens >= requested then
-- 扣减令牌
tokens = tokens - requested
redis.call('hset', key, 'tokens', tokens)
redis.call('hset', key, 'last_time', now)
redis.call('expire', key, 10) -- 设置过期时间
return 1
else
return 0
end
4.3 Java实现Redis分布式限流
java
@Component
@Slf4j
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String LUA_SCRIPT =
"local key = KEYS[1] " +
"local rate = tonumber(ARGV[1]) " +
"local capacity = tonumber(ARGV[2]) " +
"local now = tonumber(ARGV[3]) " +
"local requested = tonumber(ARGV[4]) " +
"local lastRefillTime = redis.call('hget', key, 'last_time') " +
"local tokens = redis.call('hget', key, 'tokens') " +
"if lastRefillTime == false then " +
" tokens = capacity " +
" lastRefillTime = now " +
"else " +
" tokens = tonumber(tokens) " +
" lastRefillTime = tonumber(lastRefillTime) " +
" local delta = math.max(0, now - lastRefillTime) * rate " +
" tokens = math.min(capacity, tokens + delta) " +
"end " +
"if tokens >= requested then " +
" tokens = tokens - requested " +
" redis.call('hset', key, 'tokens', tokens) " +
" redis.call('hset', key, 'last_time', now) " +
" redis.call('expire', key, 10) " +
" return 1 " +
"else " +
" return 0 " +
"end";
private DefaultRedisScript<Long> redisScript;
@PostConstruct
public void init() {
redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(LUA_SCRIPT);
redisScript.setResultType(Long.class);
}
/**
* 尝试获取令牌
* @param key 限流Key(如:rate_limit:api:/order/create)
* @param rate 每秒生成令牌数(QPS)
* @param capacity 桶容量(允许的突发流量)
* @param requested 需要的令牌数(通常为1)
* @return true-允许,false-拒绝
*/
public boolean tryAcquire(String key, double rate, int capacity, int requested) {
try {
long now = System.currentTimeMillis() / 1000; // 转换为秒
List<String> keys = Collections.singletonList(key);
Long result = redisTemplate.execute(
redisScript,
keys,
String.valueOf(rate),
String.valueOf(capacity),
String.valueOf(now),
String.valueOf(requested)
);
return result != null && result == 1;
} catch (Exception e) {
log.error("Redis限流异常", e);
// 异常时放行(熔断降级)
return true;
}
}
/**
* 简化方法:QPS限流
*/
public boolean tryAcquire(String key, int qps) {
return tryAcquire(key, qps, qps, 1);
}
}
/**
* 限流注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
String key() default "";
int qps() default 100;
int capacity() default 100;
}
/**
* 限流AOP切面
*/
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
@Autowired
private RedisRateLimiter rateLimiter;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit)
throws Throwable {
// 获取限流Key
String key = rateLimit.key();
if (StringUtils.isEmpty(key)) {
// 从请求中获取:方法名+用户ID/IP
key = getKeyFromRequest(joinPoint);
}
// 限流判断
if (!rateLimiter.tryAcquire(key, rateLimit.qps(), rateLimit.capacity(), 1)) {
log.warn("限流拦截, key: {}", key);
return Result.error("系统繁忙,请稍后重试");
}
return joinPoint.proceed();
}
private String getKeyFromRequest(ProceedingJoinPoint joinPoint) {
// 获取请求IP、用户ID等
HttpServletRequest request = getCurrentRequest();
String ip = request.getRemoteAddr();
String method = joinPoint.getSignature().toShortString();
return "rate_limit:" + method + ":" + ip;
}
}
/**
* 使用示例
*/
@RestController
public class OrderController {
@PostMapping("/order/create")
@RateLimit(key = "create_order", qps = 100, capacity = 200)
public Result createOrder(@RequestBody OrderRequest request) {
// 业务逻辑
return Result.success();
}
}
五、三层限流架构
5.1 架构图
客户端请求
↓
┌─────────────────────────────────────────┐
│ 第一层:Nginx(IP限流,防DDoS) │
│ - limit_req_zone: 100r/s per IP │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第二层:Spring Cloud Gateway(用户限流) │
│ - Redis令牌桶: 1000 QPS per user │
│ - 按API分组限流 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 第三层:Sentinel(接口熔断降级) │
│ - QPS限流、并发线程数限流 │
│ - 熔断降级、热点参数限流 │
└─────────────────────────────────────────┘
↓
后端服务

分布式系统必须做多层限流,层层防护,避免单点失效:
| 层级 | 组件 | 作用 | 粒度 |
|---|---|---|---|
| 接入层 | Nginx/OpenResty | 防DDoS、IP粗粒度限流 | 粗 |
| 网关层 | Spring Cloud Gateway/APISIX/Kong | 集群全局限流、用户/接口限流 | 细 |
| 应用层 | Sentinel/Resilience4j | 单机保护、熔断降级 | 极细 |
| 资源层 | Semaphore/连接池 | 数据库/线程池限流 | 本地 |
5.2 Nginx层限流
nginx
# nginx.conf
http {
# 定义限流区域(10MB内存,按IP限流)
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /api/ {
# 使用限流(burst=20允许排队,nodelay立即响应)
limit_req zone=mylimit burst=20 nodelay;
# 限流时返回503
limit_req_status 503;
proxy_pass http://backend;
}
}
}

5.3 Spring Cloud Gateway限流
分布式限流最佳落地位置,为什么选择网关层?
- 统一入口:所有流量必须经过网关,一次配置,全集群生效;
- 业务无侵入:无需修改业务代码,专注流量管控;
- 精细化维度:支持IP、用户ID、接口、设备ID限流;
- 提前拦截:流量在网关层被拒绝,不会进入后端服务,节省资源。

yaml
# application.yml
spring:
cloud:
gateway:
routes:
- id: order_route
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 100 # 每秒令牌数
redis-rate-limiter.burstCapacity: 200 # 桶容量
java
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
5.4 Sentinel应用层限流

java
@Service
@Slf4j
public class SentinelService {
/**
* 使用注解定义限流规则
*/
@SentinelResource(
value = "createOrder",
blockHandler = "blockHandler",
fallback = "fallbackHandler"
)
public Result createOrder(OrderRequest request) {
// 业务逻辑
return Result.success();
}
/**
* 限流降级处理
*/
public Result blockHandler(OrderRequest request, BlockException ex) {
log.warn("限流触发: {}", ex.getMessage());
return Result.error("系统繁忙,请稍后重试");
}
/**
* 异常降级处理
*/
public Result fallbackHandler(OrderRequest request, Throwable ex) {
log.error("业务异常: {}", ex.getMessage());
return Result.error("服务异常,请稍后重试");
}
}
/**
* 动态限流规则配置
*/
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("createOrder");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(100); // QPS限制100
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule.setWarmUpPeriodSec(10); // 预热时间10秒
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
六、分布式限流的高级策略

6.1 热点参数限流
java
@Service
public class HotKeyLimitService {
/**
* 热点参数限流:同一商品ID限流10 QPS
*/
@SentinelResource(value = "queryProduct")
@SentinelHotSpot(
resource = "queryProduct",
rules = @HotSpotRule(parameterIdx = 0, count = 10)
)
public Product queryProduct(Long productId) {
return productMapper.selectById(productId);
}
}
6.2 动态限流规则(基于Apollo/Nacos)
java
@Component
@Slf4j
public class DynamicRateLimitConfig {
@Autowired
private RedisRateLimiter rateLimiter;
// 从配置中心动态获取限流值
@Value("${rate.limit.order.create:100}")
private int orderCreateQps;
@NacosConfigListener(dataId = "rate-limit-config", group = "DEFAULT_GROUP")
public void onConfigChange(String config) {
JSONObject configJson = JSON.parseObject(config);
orderCreateQps = configJson.getIntValue("order.create.qps");
log.info("限流配置更新: order.create.qps = {}", orderCreateQps);
}
public boolean checkRateLimit(String key) {
return rateLimiter.tryAcquire(key, orderCreateQps);
}
}
七、面试高频真题(标准答案直接背)
Q1:令牌桶和漏桶的区别是什么?
答案:
| 维度 | 令牌桶 | 漏桶 |
|---|---|---|
| 突发处理 | 允许突发(桶容量决定) | 不允许突发,强行平滑 |
| 输出速率 | 平均速率 + 突发 | 恒定速率 |
| 实现方式 | 周期性放令牌 | 请求入桶,恒速率流出 |
| 适用场景 | 通用(Guava、Sentinel) | 恒定流量(数据库限流) |
关键区别:令牌桶允许一定程度的突发,漏桶完全平滑。
Q2:分布式限流为什么需要Lua脚本?
答案 :
原因:
- 分布式环境需要全局统一计数,Redis提供集中存储;
- 多机并发请求时,Lua脚本保证多个Redis命令原子执行,避免超量;
- Redis单线程执行,无竞争,性能极高;
- 内存操作,响应时间毫秒级,不影响网关性能。
问题示例:
实例A:读取令牌数=10 → 计算后=9 → 准备写入
实例B:读取令牌数=10 → 计算后=9 → 写入
实例A:写入9
结果:10个请求通过了20个 → 限流失效
解决方案:Redis+Lua脚本保证整个操作原子执行,单线程无竞态。

Q3:如何设计一个支持动态调整规则的分布式限流系统?
答案 :
架构设计:
- 配置中心:Apollo/Nacos存储限流规则(QPS、桶容量)
- 规则监听:客户端监听配置变更,动态更新限流器
- 限流执行:Redis+Lua令牌桶
- 监控告警:限流次数、拒绝率上报监控系统
核心代码:
java
@NacosConfigListener
public void onRuleChange(String config) {
RateLimitRule newRule = parseRule(config);
// 更新限流器配置
rateLimiter.updateRule(newRule);
}
Q4:Sentinel和Guava RateLimiter有什么区别?
答案:
| 维度 | Guava RateLimiter | Sentinel |
|---|---|---|
| 范围 | 单机 | 分布式(支持集群) |
| 功能 | 仅限流 | 限流+熔断+降级+热点 |
| 规则管理 | 代码配置 | 动态配置(Apollo/Nacos) |
| 监控 | 无 | 控制台实时监控 |
| 适用 | 单体应用 | 微服务架构 |
Q5:Nginx、Gateway、Sentinel三层限流怎么分工?
答案:
| 层级 | 作用 | 粒度 | 策略 |
|---|---|---|---|
| Nginx | 防DDoS | IP | 单IP 10r/s |
| Gateway | 用户限流 | 用户/API | 每用户1000 QPS |
| Sentinel | 应用保护 | 接口/方法 | 接口200 QPS + 熔断 |
原则:层层递减,外层粗粒度防攻击,内层细粒度保核心。
- 网关层:统一入口、全局精准、业务无侵入,拦截无效流量;
- 应用层:单机保护、熔断降级,防止自身资源耗尽;
- 生产必须两者结合,网关拦大部分,应用做兜底。

Q6:限流阈值怎么设置?
答案 :
计算公式:
单机QPS上限 = (CPU核数 × 1000) / 平均响应时间(ms)
集群QPS上限 = 单机QPS上限 × 实例数 × 0.7(预留30%buffer)
压测确定:
- 压力测试找到系统瓶颈
- 设定阈值为瓶颈值的70%
- 预留弹性空间应对突发
Q7:设计一个支持动态调整规则的分布式限流系统
答案:
- 存储层:MySQL存储限流规则(接口、QPS、容量、维度);
- 配置中心:Nacos/Apollo推送动态规则,支持实时生效;
- 限流核心:Redis+Lua实现令牌桶/漏桶,保证原子性;
- 网关层:Spring Cloud Gateway拉取规则,执行限流;
- 本地缓存:Caffeine缓存规则,减少配置中心请求;
- 监控告警:Prometheus监控限流次数,超阈值告警;
- 兜底策略:Redis宕机时,开启本地Guava限流降级。
八、避坑指南:分布式限流90%的人都踩过
- 不用Lua脚本,导致超量限流
先GET再SET,多机并发会出现计数错误,必须用Lua原子操作。 - 固定窗口临界问题
核心业务不用固定窗口,改用滑动窗口或令牌桶。 - Redis Key不过期
冷数据残留占用内存,必须设置EXPIRE。 - 只做单机限流,不做分布式
集群环境下,单机限流=无效限流,总流量会超标。 - 漏桶用在秒杀场景
漏桶强制匀速,秒杀突发流量会被错误拒绝。 - 网关层不做降级
Redis宕机时,限流服务不可用,必须开启本地降级兜底。 - 不区分维度限流
全部接口用同一规则,核心接口被非核心接口拖垮。

总结
1. 核心知识点速记口诀
四大算法要记牢,计数器简单有临界,
滑动窗口细粒度,漏桶平滑突发出,
令牌桶是工业王,允许突发平滑流。
单机限流用Guava,分布式Redis+Lua,
三层架构层层防,Nginx网关Sentinel。
面试常问令牌桶,突发平滑都能做,
Lua脚本保原子,分布式下不出错。

2. 核心要点回顾
- 四大算法:计数器(有临界)、滑动窗口(解决临界)、漏桶(强行平滑)、令牌桶(兼顾突发)
- 单机限流:Guava RateLimiter,令牌桶实现
- 分布式限流:Redis+Lua保证原子性
- 三层架构:Nginx(IP) → Gateway(用户) → Sentinel(接口)
- Sentinel:限流+熔断+降级,工业级方案
3. 实战建议
- 简单场景:Guava RateLimiter
- 分布式场景:Redis+Lua令牌桶
- 微服务架构:Sentinel(推荐)
- 网关层:Spring Cloud Gateway + Redis限流
- 防DDoS :Nginx
limit_req - 秒杀/营销活动 :网关令牌桶限流+Sentinel降级;
- 第三方API调用 :漏桶算法,保证匀速请求;

写在最后
从计数器的临界问题到令牌桶的平滑突发,限流算法的演进史就是在精准度和复杂度之间不断权衡的过程。单机用Guava,分布式用Redis+Lua,微服务用Sentinel------没有银弹,只有场景适配。
很多开发者以为限流就是加个计数器,结果临界时刻流量翻倍打垮系统;有人过度设计分布式限流,用重量级方案解决简单问题。记住:Nginx防DDoS,网关限用户,Sentinel保核心,三层防线层层递进,才能在高并发下立于不败之地。
如果觉得有帮助,欢迎点赞、收藏、转发!