大促期间,流量瞬间飙升100倍,数据库被打挂,服务雪崩------这就是没有限流熔断的灾难。本文将深入剖析Sentinel、令牌桶、漏桶、滑动窗口四种限流算法,从原理到实战,手把手教你构建企业级服务防护体系。

文章目录
-
- 一、场景引入:一次大促的服务雪崩
-
- [1.1 真实案例](#1.1 真实案例)
- [1.2 服务雪崩的连锁反应](#1.2 服务雪崩的连锁反应)
- 二、解决方案:限流熔断核心概念
-
- [2.1 四种限流算法对比](#2.1 四种限流算法对比)
- [2.2 熔断器状态机](#2.2 熔断器状态机)
- 三、实战代码:Sentinel限流熔断
-
- [3.1 Sentinel配置](#3.1 Sentinel配置)
- [3.2 Sentinel注解方式使用](#3.2 Sentinel注解方式使用)
- [3.3 热点参数限流](#3.3 热点参数限流)
- 四、实战代码:手动实现限流算法
-
- [4.1 令牌桶算法](#4.1 令牌桶算法)
- [4.2 滑动窗口算法](#4.2 滑动窗口算法)
- [4.3 漏桶算法](#4.3 漏桶算法)
- 五、高级进阶:网关层统一限流
-
- [5.1 Spring Cloud Gateway + Sentinel](#5.1 Spring Cloud Gateway + Sentinel)
- [5.2 Nginx层限流](#5.2 Nginx层限流)
- 六、预判问题与解答
- 七、面试高频考点
- 八、总结与最佳实践
-
- [8.1 核心要点回顾](#8.1 核心要点回顾)
- [8.2 适用场景](#8.2 适用场景)
- [8.3 性能数据](#8.3 性能数据)
- 九、参考与拓展
一、场景引入:一次大促的服务雪崩
1.1 真实案例
某电商平台大促,流量瞬间涌入:
时间线:
T+0秒:大促开始,正常流量:1000 QPS
T+1秒:流量飙升到 100,000 QPS(100倍!)
T+2秒:网关层未限流,全部透传到后端
T+3秒:订单服务线程池打满,响应变慢
T+4秒:用户看到超时,疯狂重试
T+5秒:重试流量叠加,订单服务CPU 100%
T+6秒:订单服务调用库存服务,库存服务也打满
T+7秒:库存服务调用数据库,数据库连接池耗尽
T+8秒:数据库拒绝连接,所有服务报错
T+10秒:服务雪崩,全站不可用
T+30秒:运维紧急扩容,但为时已晚
T+1小时:大促黄金时间已过,损失千万
问题根源:没有限流熔断机制,流量无限制透传,一个服务故障引发连锁反应。
1.2 服务雪崩的连锁反应
服务雪崩过程:
正常情况:
用户 ──→ 网关 ──→ 订单服务 ──→ 库存服务 ──→ 数据库
1000QPS 1000QPS 1000QPS 1000QPS
雪崩过程:
用户 ──→ 网关 ──→ 订单服务 ──→ 库存服务 ──→ 数据库
10万QPS 10万QPS 10万QPS 连接池耗尽
│ │ │
▼ ▼ ▼
全部通过 线程池满 拒绝连接
响应变慢
│
▼
用户重试
│
流量叠加
│
恶性循环
│
全站宕机
二、解决方案:限流熔断核心概念
2.1 四种限流算法对比
┌─────────────────────────────────────────────────────────────────────┐
│ 限流算法对比 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 计数器(固定窗口) 滑动窗口 │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 实现简单 │ │ 精度高 │ │
│ │ 内存占用小 │ │ 无临界问题 │ │
│ │ 有临界突发问题 │ │ 内存占用大 │ │
│ │ 适合简单场景 │ │ 适合高精度场景 │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ 令牌桶 漏桶 │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 允许一定突发 │ │ 流量绝对平滑 │ │
│ │ 平均速率控制 │ │ 无突发 │ │
│ │ 适合有突发特征 │ │ 适合严格限速 │ │
│ │ 实现较复杂 │ │ 实现较复杂 │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 熔断器状态机
熔断器三种状态:
┌─────────────┐ 失败率 > 阈值 ┌─────────────┐
│ CLOSED │ ───────────────────→ │ OPEN │
│ (关闭) │ │ (打开) │
│ 正常通过 │ │ 拒绝请求 │
└─────────────┘ └─────────────┘
▲ │
│ 超时时间后 │
│ ┌───────────────────────────────┘
│ │
│ ▼ 失败率 < 阈值
│ ┌─────────────┐
└─│ HALF-OPEN │
│ (半开) │
│ 试探性通过 │
└─────────────┘
三、实战代码:Sentinel限流熔断
3.1 Sentinel配置
java
/**
* Sentinel配置
*/
@Configuration
@Slf4j
public class SentinelConfig {
@PostConstruct
public void init() {
// 初始化限流规则
initFlowRules();
// 初始化熔断规则
initDegradeRules();
// 初始化系统保护规则
initSystemRules();
log.info("✅ Sentinel规则初始化完成");
}
/**
* 限流规则
*/
private void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
// 规则1:订单创建接口,QPS限制1000
FlowRule orderCreateRule = new FlowRule();
orderCreateRule.setResource("order:create");
orderCreateRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
orderCreateRule.setCount(1000);
orderCreateRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
orderCreateRule.setWarmUpPeriodSec(10); // 预热10秒
rules.add(orderCreateRule);
// 规则2:秒杀接口,QPS限制100
FlowRule seckillRule = new FlowRule();
seckillRule.setResource("seckill:submit");
seckillRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
seckillRule.setCount(100);
seckillRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
seckillRule.setMaxQueueingTimeMs(500); // 排队等待500ms
rules.add(seckillRule);
// 规则3:用户维度限流(热点参数限流)
ParamFlowRule hotSpotRule = new ParamFlowRule();
hotSpotRule.setResource("user:profile");
hotSpotRule.setParamIdx(0); // 第0个参数(userId)
hotSpotRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
hotSpotRule.setCount(100); // 默认每个用户100 QPS
// 特定用户限制
hotSpotRule.setParamFlowItemList(Arrays.asList(
new ParamFlowItem().setObject("10086").setCount(10), // VIP用户限制更严
new ParamFlowItem().setObject("10010").setCount(10)
));
FlowRuleManager.loadRules(rules);
}
/**
* 熔断规则
*/
private void initDegradeRules() {
List<DegradeRule> rules = new ArrayList<>();
// 规则1:库存服务,慢调用比例熔断
DegradeRule stockSlowRule = new DegradeRule();
stockSlowRule.setResource("stock:deduct");
stockSlowRule.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType());
stockSlowRule.setCount(0.5); // 慢调用比例阈值50%
stockSlowRule.setSlowRatioThreshold(0.5); // 慢调用比例
stockSlowRule.setTimeWindow(10); // 熔断时长10秒
stockSlowRule.setMinRequestAmount(10); // 最小请求数
stockSlowRule.setStatIntervalMs(1000); // 统计时长1秒
rules.add(stockSlowRule);
// 规则2:支付服务,异常比例熔断
DegradeRule payErrorRule = new DegradeRule();
payErrorRule.setResource("payment:pay");
payErrorRule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());
payErrorRule.setCount(0.5); // 异常比例阈值50%
payErrorRule.setTimeWindow(30); // 熔断时长30秒
payErrorRule.setMinRequestAmount(10);
payErrorRule.setStatIntervalMs(1000);
rules.add(payErrorRule);
// 规则3:异常数熔断
DegradeRule errorCountRule = new DegradeRule();
errorCountRule.setResource("coupon:grant");
errorCountRule.setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType());
errorCountRule.setCount(10); // 异常数阈值10
errorCountRule.setTimeWindow(60); // 熔断时长60秒
rules.add(errorCountRule);
DegradeRuleManager.loadRules(rules);
}
/**
* 系统保护规则
*/
private void initSystemRules() {
List<SystemRule> rules = new ArrayList<>();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(10); // 最大负载
rule.setHighestCpuUsage(0.8); // CPU使用率80%
rule.setQps(10000); // 最大QPS
rule.setAvgRt(100); // 平均响应时间100ms
rule.setMaxThread(500); // 最大线程数
rules.add(rule);
SystemRuleManager.loadRules(rules);
}
}
3.2 Sentinel注解方式使用
java
/**
* 订单服务(Sentinel限流熔断)
*/
@Service
@Slf4j
public class OrderService {
@Autowired
private StockService stockService;
@Autowired
private PaymentService paymentService;
/**
* 创建订单(限流保护)
*/
@SentinelResource(
value = "order:create",
blockHandler = "createOrderBlockHandler",
fallback = "createOrderFallback"
)
public OrderVO createOrder(CreateOrderDTO dto) {
log.info("📝 创建订单: userId={}, skuId={}", dto.getUserId(), dto.getSkuId());
// 1. 扣减库存(熔断保护)
boolean stockResult = deductStock(dto.getSkuId(), dto.getQuantity());
if (!stockResult) {
throw new BizException("库存不足");
}
// 2. 创建订单
Order order = new Order();
order.setUserId(dto.getUserId());
order.setSkuId(dto.getSkuId());
order.setQuantity(dto.getQuantity());
order.setStatus(OrderStatus.PENDING_PAYMENT);
// ...
// 3. 调用支付(熔断保护)
PaymentResult paymentResult = processPayment(order);
return convertToVO(order);
}
/**
* 限流降级处理
*/
public OrderVO createOrderBlockHandler(CreateOrderDTO dto, BlockException ex) {
log.warn("🚫 创建订单被限流: userId={}, reason={}", dto.getUserId(), ex.getMessage());
// 返回降级结果
OrderVO fallbackOrder = new OrderVO();
fallbackOrder.setStatus(OrderStatus.LIMITED);
fallbackOrder.setMessage("系统繁忙,请稍后重试");
return fallbackOrder;
}
/**
* 异常降级处理
*/
public OrderVO createOrderFallback(CreateOrderDTO dto, Throwable ex) {
log.error("❌ 创建订单异常: userId={}", dto.getUserId(), ex);
OrderVO fallbackOrder = new OrderVO();
fallbackOrder.setStatus(OrderStatus.ERROR);
fallbackOrder.setMessage("服务异常,请稍后重试");
return fallbackOrder;
}
/**
* 扣减库存(熔断保护)
*/
@SentinelResource(
value = "stock:deduct",
blockHandler = "deductStockBlockHandler",
fallback = "deductStockFallback"
)
public boolean deductStock(Long skuId, Integer quantity) {
return stockService.deduct(skuId, quantity);
}
public boolean deductStockBlockHandler(Long skuId, Integer quantity, BlockException ex) {
log.warn("🚫 库存扣减被熔断: skuId={}", skuId);
return false; // 熔断时返回库存不足
}
public boolean deductStockFallback(Long skuId, Integer quantity, Throwable ex) {
log.error("❌ 库存扣减异常: skuId={}", skuId, ex);
return false;
}
/**
* 支付处理(熔断保护)
*/
@SentinelResource(
value = "payment:pay",
blockHandler = "paymentBlockHandler",
fallback = "paymentFallback"
)
public PaymentResult processPayment(Order order) {
return paymentService.pay(order);
}
public PaymentResult paymentBlockHandler(Order order, BlockException ex) {
log.warn("🚫 支付服务被熔断: orderId={}", order.getId());
// 返回待支付状态,后续异步处理
PaymentResult result = new PaymentResult();
result.setSuccess(false);
result.setStatus(PaymentStatus.PENDING);
return result;
}
public PaymentResult paymentFallback(Order order, Throwable ex) {
log.error("❌ 支付服务异常: orderId={}", order.getId(), ex);
PaymentResult result = new PaymentResult();
result.setSuccess(false);
result.setStatus(PaymentStatus.FAILED);
return result;
}
}
3.3 热点参数限流
java
/**
* 热点参数限流示例
*/
@RestController
@Slf4j
public class UserController {
/**
* 获取用户信息(热点参数限流)
* 对userId进行限流,防止单个用户刷接口
*/
@GetMapping("/user/{userId}")
@SentinelResource(
value = "user:profile",
blockHandler = "getUserBlockHandler"
)
public Result<UserVO> getUserProfile(
@PathVariable @RequestParam(value = "userId", required = false) String userId) {
// 这里userId会被Sentinel识别为热点参数
UserVO user = getUserFromDB(userId);
return Result.success(user);
}
public Result<UserVO> getUserBlockHandler(String userId, BlockException ex) {
log.warn("🚫 用户接口被限流: userId={}", userId);
return Result.fail("请求过于频繁,请稍后重试");
}
}
四、实战代码:手动实现限流算法
4.1 令牌桶算法
java
/**
* 令牌桶限流器
* 允许一定突发流量,平均速率稳定
*/
@Component
@Slf4j
public class TokenBucketRateLimiter {
// 令牌桶:key -> 桶
private final ConcurrentHashMap<String, TokenBucket> buckets = new ConcurrentHashMap<>();
/**
* 尝试获取许可
*
* @param key 限流key
* @param permits 需要的许可数
* @param capacity 桶容量
* @param rate 每秒产生令牌数
* @return true-获取成功,false-被限流
*/
public boolean tryAcquire(String key, int permits, int capacity, double rate) {
TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(capacity, rate));
return bucket.tryAcquire(permits);
}
/**
* 令牌桶
*/
private static class TokenBucket {
// 桶容量
private final int capacity;
// 每秒产生令牌数
private final double rate;
// 当前令牌数
private double tokens;
// 上次更新时间
private long lastUpdateTime;
public TokenBucket(int capacity, double rate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = capacity; // 初始满桶
this.lastUpdateTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire(int permits) {
long now = System.currentTimeMillis();
// 计算经过时间产生的令牌
double elapsedTime = (now - lastUpdateTime) / 1000.0;
tokens = Math.min(capacity, tokens + elapsedTime * rate);
lastUpdateTime = now;
// 检查令牌是否足够
if (tokens >= permits) {
tokens -= permits;
return true;
}
return false;
}
}
}
/**
* 令牌桶使用示例
*/
@Service
@Slf4j
public class TokenBucketDemoService {
@Autowired
private TokenBucketRateLimiter rateLimiter;
/**
* 秒杀接口(令牌桶限流)
* 容量100,每秒产生10个令牌
* 允许突发100请求,之后平均10 QPS
*/
public SeckillResult seckillWithTokenBucket(Long userId, Long skuId) {
String key = "seckill:" + skuId;
if (!rateLimiter.tryAcquire(key, 1, 100, 10)) {
log.warn("🚫 秒杀被限流: userId={}, skuId={}", userId, skuId);
return SeckillResult.fail("太火爆了,请稍后重试");
}
// 执行业务逻辑...
return doSeckill(userId, skuId);
}
private SeckillResult doSeckill(Long userId, Long skuId) {
// 秒杀逻辑...
return SeckillResult.success(null);
}
}
4.2 滑动窗口算法
java
/**
* 滑动窗口限流器
* 精度高,无临界突发问题
*/
@Component
@Slf4j
public class SlidingWindowRateLimiter {
// 滑动窗口:key -> 窗口
private final ConcurrentHashMap<String, SlidingWindow> windows = new ConcurrentHashMap<>();
/**
* 尝试获取许可
*
* @param key 限流key
* @param limit 窗口内最大请求数
* @param windowSizeMs 窗口大小(毫秒)
* @return true-获取成功,false-被限流
*/
public boolean tryAcquire(String key, int limit, long windowSizeMs) {
SlidingWindow window = windows.computeIfAbsent(key, k -> new SlidingWindow(windowSizeMs));
return window.tryAcquire(limit);
}
/**
* 滑动窗口
*/
private static class SlidingWindow {
// 窗口大小(毫秒)
private final long windowSizeMs;
// 请求时间队列
private final Queue<Long> requestTimes;
public SlidingWindow(long windowSizeMs) {
this.windowSizeMs = windowSizeMs;
this.requestTimes = new ConcurrentLinkedQueue<>();
}
public synchronized boolean tryAcquire(int limit) {
long now = System.currentTimeMillis();
long windowStart = now - windowSizeMs;
// 移除窗口外的请求记录
while (!requestTimes.isEmpty() && requestTimes.peek() < windowStart) {
requestTimes.poll();
}
// 检查当前窗口内请求数
if (requestTimes.size() < limit) {
requestTimes.offer(now);
return true;
}
return false;
}
}
}
/**
* 滑动窗口使用示例
*/
@Service
@Slf4j
public class SlidingWindowDemoService {
@Autowired
private SlidingWindowRateLimiter rateLimiter;
/**
* API接口(滑动窗口限流)
* 每10秒最多100次请求
*/
public Result<String> apiWithSlidingWindow(String apiKey) {
if (!rateLimiter.tryAcquire(apiKey, 100, 10000)) {
log.warn("🚫 API被限流: apiKey={}", apiKey);
return Result.fail("请求过于频繁");
}
// 执行业务逻辑...
return Result.success("success");
}
}
4.3 漏桶算法
java
/**
* 漏桶限流器
* 流量绝对平滑,无突发
*/
@Component
@Slf4j
public class LeakyBucketRateLimiter {
// 漏桶:key -> 桶
private final ConcurrentHashMap<String, LeakyBucket> buckets = new ConcurrentHashMap<>();
/**
* 尝试获取许可
*
* @param key 限流key
* @param capacity 桶容量
* @param rate 每秒漏出速率
* @return true-获取成功,false-被限流
*/
public boolean tryAcquire(String key, int capacity, double rate) {
LeakyBucket bucket = buckets.computeIfAbsent(key, k -> new LeakyBucket(capacity, rate));
return bucket.tryAcquire();
}
/**
* 漏桶
*/
private static class LeakyBucket {
// 桶容量
private final int capacity;
// 每秒漏出速率
private final double rate;
// 当前水量
private double water;
// 上次更新时间
private long lastUpdateTime;
public LeakyBucket(int capacity, double rate) {
this.capacity = capacity;
this.rate = rate;
this.water = 0;
this.lastUpdateTime = System.currentTimeMillis();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 计算漏出的水量
double elapsedTime = (now - lastUpdateTime) / 1000.0;
water = Math.max(0, water - elapsedTime * rate);
lastUpdateTime = now;
// 检查是否还有容量
if (water < capacity) {
water += 1;
return true;
}
return false;
}
}
}
五、高级进阶:网关层统一限流
5.1 Spring Cloud Gateway + Sentinel
yaml
# Gateway配置
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
filters:
- name: SentinelGatewayFilter
args:
resource: order-service
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8858
datasource:
flow:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow
java
/**
* Gateway限流配置
*/
@Configuration
public class GatewaySentinelConfig {
@PostConstruct
public void init() {
// 自定义限流异常处理
GatewayCallbackManager.setBlockHandler((exchange, ex) -> {
return Mono.defer(() -> {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":429,\"message\":\"系统繁忙,请稍后重试\"}";
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
});
});
}
}
5.2 Nginx层限流
nginx
# Nginx限流配置
http {
# 定义限流区域
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
limit_req_zone $server_name zone=server_limit:10m rate=1000r/s;
# 定义连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
server_name api.example.com;
location /api/ {
# IP限流:每秒10个请求,突发20个
limit_req zone=ip_limit burst=20 nodelay;
# 连接数限制:单个IP最多10个连接
limit_conn conn_limit 10;
# 限流响应码
limit_req_status 429;
limit_conn_status 503;
proxy_pass http://backend;
}
location /seckill/ {
# 秒杀接口更严格的限流
limit_req zone=ip_limit burst=5 nodelay;
proxy_pass http://backend;
}
}
}
六、预判问题与解答
Q1:四种限流算法怎么选?
A:
| 场景 | 推荐算法 | 说明 |
|---|---|---|
| 简单计数 | 固定窗口 | 实现简单,精度要求低 |
| 高精度限流 | 滑动窗口 | 无临界问题 |
| 允许突发 | 令牌桶 | 适合有突发特征的业务 |
| 严格平滑 | 漏桶 | 适合需要绝对匀速的场景 |
| 通用场景 | Sentinel | 功能全面,开箱即用 |
Q2:Sentinel和Hystrix有什么区别?
A:
| 特性 | Sentinel | Hystrix |
|---|---|---|
| 限流 | 支持(多种算法) | 不支持 |
| 熔断 | 支持 | 支持 |
| 热点限流 | 支持 | 不支持 |
| 系统保护 | 支持 | 不支持 |
| 控制台 | 完善 | 简单 |
| 活跃度 | 活跃维护 | 已停止维护 |
| 性能 | 高 | 中 |
建议:新项目直接用Sentinel,老项目可逐步迁移。
Q3:限流阈值怎么设置?
A:
设置原则:
1. 基于压测数据:
- 单机压测得到最大QPS
- 设置阈值为最大QPS的70-80%
2. 基于业务容忍度:
- 核心接口:阈值较低,保证稳定性
- 非核心接口:阈值较高,提升吞吐量
3. 动态调整:
- 低峰期提高阈值
- 高峰期降低阈值
- 大促前预热
4. 分级限流:
- 网关层:总流量控制
- 服务层:接口级别控制
- 数据库层:连接池控制
Q4:熔断后怎么恢复?
A:
熔断恢复机制:
1. Sentinel的恢复:
- OPEN状态持续一段时间后进入HALF-OPEN
- 允许少量请求通过试探
- 如果成功则关闭熔断器
- 如果失败则重新打开
2. 手动恢复:
- 通过控制台手动关闭熔断
- 适用于紧急恢复场景
3. 自动恢复策略:
- 指数退避:熔断时间逐渐延长
- 渐进恢复:逐步增加流量
Q5:限流和熔断有什么区别?
A:
限流(Rate Limiting):
- 目的:防止流量过大打垮服务
- 触发:请求量超过阈值
- 处理:拒绝多余请求
- 比喻:高速收费站,控制车流量
熔断(Circuit Breaker):
- 目的:防止故障扩散
- 触发:错误率/慢调用率超过阈值
- 处理:暂时停止调用下游服务
- 比喻:电路保险丝,过载时断开
关系:
- 限流是预防性的
- 熔断是反应性的
- 两者结合使用效果更好
七、面试高频考点
考点1:Sentinel的限流算法有哪些?
参考答案:
Sentinel限流算法:
1. 快速失败(Direct):
- 直接拒绝多余请求
- 返回BlockException
2. 预热(Warm Up):
- 冷启动时逐步放开流量
- 防止突发流量打垮服务
3. 匀速排队(Rate Limiter):
- 请求均匀排队处理
- 基于漏桶算法
4. 热点参数限流:
- 对特定参数值限流
- 如:对特定用户ID限流
考点2:令牌桶和漏桶有什么区别?
参考答案:
令牌桶:
- 以固定速率产生令牌
- 请求需要获取令牌才能执行
- 桶满时令牌丢弃
- 允许一定突发流量(桶内令牌)
- 适合有突发特征的业务
漏桶:
- 以固定速率漏出水
- 请求进入桶中等待处理
- 桶满时请求丢弃
- 流量绝对平滑,无突发
- 适合需要严格限速的场景
核心区别:
- 令牌桶:允许突发,限制平均速率
- 漏桶:不允许突发,限制流出速率
考点3:滑动窗口和固定窗口有什么区别?
参考答案:
固定窗口:
- 将时间划分为固定区间
- 每个区间独立计数
- 存在临界突发问题
如:窗口边界前后各发一半请求,实际速率翻倍
滑动窗口:
- 窗口随时间滑动
- 统计最近一个窗口内的请求
- 无临界问题
- 内存占用较大(需要记录每个请求时间)
对比:
- 固定窗口:简单,有临界问题
- 滑动窗口:复杂,精度高
考点4:熔断器的三种状态及转换条件?
参考答案:
熔断器三种状态:
1. CLOSED(关闭):
- 正常状态,请求正常通过
- 统计错误率/慢调用率
- 超过阈值则转为OPEN
2. OPEN(打开):
- 熔断状态,请求直接拒绝
- 经过超时时间后转为HALF-OPEN
3. HALF-OPEN(半开):
- 试探状态,允许少量请求通过
- 如果成功则转为CLOSED
- 如果失败则转为OPEN
转换条件:
- CLOSED → OPEN:错误率/慢调用率超过阈值
- OPEN → HALF-OPEN:超时时间到达
- HALF-OPEN → CLOSED:试探请求成功
- HALF-OPEN → OPEN:试探请求失败
八、总结与最佳实践
8.1 核心要点回顾
服务防护体系:
┌─────────────────────────────────────────────────────────────┐
│ 1. 多层限流 │
│ ├── Nginx层:IP限流、连接数限制 │
│ ├── 网关层:路由级别限流 │
│ ├── 服务层:接口级别限流(Sentinel) │
│ └── 数据库层:连接池限制 │
│ │
│ 2. 熔断降级 │
│ ├── 慢调用熔断 │
│ ├── 异常比例熔断 │
│ └── 异常数熔断 │
│ │
│ 3. 限流算法选择 │
│ ├── 简单场景:固定窗口 │
│ ├── 高精度:滑动窗口 │
│ ├── 允许突发:令牌桶 │
│ └── 严格平滑:漏桶 │
│ │
│ 4. 监控告警 │
│ ├── 限流触发次数 │
│ ├── 熔断触发次数 │
│ └── 降级响应时间 │
└─────────────────────────────────────────────────────────────┘
8.2 适用场景
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 网关统一入口 | Nginx + Gateway | 第一层防护 |
| 微服务接口 | Sentinel | 功能全面 |
| 秒杀活动 | 令牌桶 + Sentinel | 允许突发 |
| 支付接口 | 漏桶 + 熔断 | 严格限速 |
| 热点用户 | Sentinel热点限流 | 针对特定用户 |
8.3 性能数据
某电商系统实测:
| 指标 | 无限流 | 限流后 | 提升 |
|---|---|---|---|
| 服务可用性 | 95% | 99.99% | 4.99%↑ |
| P99延迟 | 5000ms | 200ms | 96%↓ |
| 错误率 | 10% | 0.1% | 99%↓ |
| 雪崩次数 | 5次/月 | 0次/月 | 100%↓ |
九、参考与拓展
互动讨论:你在项目中是怎么做服务限流熔断的?有没有遇到过雪崩事故?欢迎在评论区分享!
如果本文对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,持续获取更多Java后端技术干货!