refactor: rename files to Chinese and organize by category

Organized 50 interview questions into 12 categories:
- 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc.
- 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理
- 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc.
- 04-消息队列 (1 file): RocketMQ/Kafka
- 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc.
- 06-JVM (1 file): JVM和垃圾回收
- 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc.
- 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮
- 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化
- 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx
- 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性
- 12-面试技巧 (1 file): 面试技巧和职业规划

All files renamed to Chinese for better accessibility and
organized into categorized folders for easier navigation.

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
yasinshaw
2026-03-01 00:10:53 +08:00
parent fe2e6dc2f2
commit 0e46a367c4
47 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,667 @@
# 限流策略与算法
## 问题
1. 为什么需要限流?常见的限流场景有哪些?
2. 有哪些常见的限流算法?各自的原理和优缺点是什么?
3. 固定窗口算法有什么问题?如何优化?
4. 滑动窗口算法是如何实现的?
5. 令牌桶和漏桶算法的区别是什么?
6. 分布式限流如何实现Redis、Sentinel
7. 在实际项目中,你是如何设计限流策略的?
---
## 标准答案
### 1. 限流的目的和场景
#### **为什么需要限流?**
**保护系统**
- 防止系统过载CPU、内存、数据库
- 防止雪崩效应(服务级联失败)
- 保护核心资源数据库连接数、API 配额)
**保证服务质量**
- 保证大部分用户的正常使用
- 防止恶意攻击爬虫、DDoS
- 实现公平性(防止单个用户占用资源)
---
#### **常见限流场景**
| 场景 | 限流对象 | 目的 |
|------|---------|------|
| **API 接口** | QPS、TPS | 保护后端服务 |
| **数据库** | 连接数、QPS | 防止数据库打挂 |
| **第三方接口** | 调用次数 | 控制成本(如短信接口) |
| **用户行为** | 操作次数 | 防止刷单、恶意抢购 |
| **爬虫防护** | IP 请求频率 | 保护数据 |
---
### 2. 限流算法对比
| 算法 | 原理 | 优点 | 缺点 | 适用场景 |
|------|------|------|------|----------|
| **固定窗口** | 固定时间窗口计数 | 简单 | 临界突变、不精确 | 低要求场景 |
| **滑动窗口** | 滑动时间窗口计数 | 精确 | 内存占用大 | 高精度要求 |
| **漏桶** | 恒定速率流出 | 平滑流量 | 无法应对突发 | 恒定速率场景 |
| **令牌桶** | 恒定速率放入令牌 | 允许突发 | 实现复杂 | 通用场景 |
---
### 3. 固定窗口算法
#### **原理**
将时间划分为固定窗口,每个窗口内计数,超过阈值则拒绝。
**示例**
```
窗口大小1 分钟
阈值100 次请求
10:00:00 - 10:00:59 → 100 次请求
10:01:00 - 10:01:59 → 重置计数器,重新开始
```
---
#### **Java 实现**
```java
public class FixedWindowRateLimiter {
private final int limit; // 阈值
private final long windowSizeMs; // 窗口大小(毫秒)
private int count; // 当前计数
private long windowStart; // 窗口开始时间
public FixedWindowRateLimiter(int limit, long windowSizeMs) {
this.limit = limit;
this.windowSizeMs = windowSizeMs;
this.windowStart = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 超出窗口,重置
if (now - windowStart >= windowSizeMs) {
windowStart = now;
count = 0;
}
// 检查是否超限
if (count < limit) {
count++;
return true;
}
return false;
}
}
```
**使用示例**
```java
// 限制:每分钟 100 次请求
FixedWindowRateLimiter limiter = new FixedWindowRateLimiter(100, 60 * 1000);
for (int i = 0; i < 150; i++) {
boolean allowed = limiter.allowRequest();
System.out.println("请求 " + i + ": " + (allowed ? "通过" : "限流"));
}
```
---
#### **问题:临界突变**
**场景**
```
阈值100 / 分钟
10:00:59 → 100 次请求(窗口 1 满)
10:01:00 → 100 次请求(窗口 2 满)
10:01:00 前后 1 秒内,实际处理了 200 次请求!
```
**图解**
```
时间 10:00:59 10:01:01
↓ ↓
窗口1 █████████████████ (100 请求)
窗口2 █████████████████ (100 请求)
临界点突变
```
---
### 4. 滑动窗口算法
#### **原理**
将时间窗口划分为多个小窗口,滑动计数。
**示例**
```
大窗口1 分钟,阈值 100
小窗口10 秒
10:00:00 - 10:00:10 → 10 次
10:00:10 - 10:00:20 → 15 次
10:00:20 - 10:00:30 → 20 次
10:00:30 - 10:00:40 → 25 次
10:00:50 - 10:01:00 → 20 次
10:00:35 时,统计最近 1 分钟:
10:00:00 - 10:00:10 → 10 次
10:00:10 - 10:00:20 → 15 次
10:00:20 - 10:00:30 → 20 次
10:00:30 - 10:00:35 → 12.5 次(估算)
总计57.5 次 < 100通过
```
---
#### **Java 实现(环形数组)**
```java
public class SlidingWindowRateLimiter {
private final int limit; // 阈值
private final int slotCount; // 槽位数量
private final long slotSizeMs; // 槽位大小(毫秒)
private final int[] counters; // 计数器数组
private long lastSlotTime; // 上次槽位时间
public SlidingWindowRateLimiter(int limit, long windowSizeMs, int slotCount) {
this.limit = limit;
this.slotCount = slotCount;
this.slotSizeMs = windowSizeMs / slotCount;
this.counters = new int[slotCount];
this.lastSlotTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 计算当前槽位索引
int currentSlot = (int) ((now / slotSizeMs) % slotCount);
// 清理过期槽位
int slotsToClear = (int) ((now - lastSlotTime) / slotSizeMs);
if (slotsToClear >= slotCount) {
// 全部过期,清空所有槽位
Arrays.fill(counters, 0);
} else {
// 部分过期,清理过期槽位
for (int i = 0; i < slotsToClear; i++) {
int slotToClear = (currentSlot - i + slotCount) % slotCount;
counters[slotToClear] = 0;
}
}
lastSlotTime = now;
// 计算当前窗口内总请求数
int totalCount = 0;
for (int count : counters) {
totalCount += count;
}
// 检查是否超限
if (totalCount < limit) {
counters[currentSlot]++;
return true;
}
return false;
}
}
```
**使用示例**
```java
// 限制:每分钟 100 次请求,分为 6 个槽位(每 10 秒一个)
SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(100, 60 * 1000, 6);
```
---
#### **Redis 实现Redisson 的 RRateLimiter**
```java
@Autowired
private RedissonClient redisson;
public boolean allowRequest(String key, int rate, RateIntervalUnit interval) {
RRateLimiter rateLimiter = redisson.getRateLimiter(key);
// 初始化:每分钟 100 次
rateLimiter.trySetRate(RateType.OVERALL, rate, interval);
// 尝试获取许可
return rateLimiter.tryAcquire(1);
}
```
---
### 5. 漏桶算法
#### **原理**
想象一个底部有孔的桶:
- 请求像水一样流入桶
- 桶底以恒定速率漏水
- 桶满时拒绝请求
**图解**
```
请求流入
┌───┐
│ ███│ ← 桶(容量 = C
│ ███│
└───┘ ↓
恒定速率R流出
```
**特点**
- **恒定速率**:无论请求多快,流出速率固定
- **平滑流量**:削峰填谷
---
#### **Java 实现**
```java
public class LeakyBucketRateLimiter {
private final int capacity; // 桶容量
private final double leakRate; // 漏水速率(请求/毫秒)
private double currentWater; // 当前水量
private long lastLeakTime; // 上次漏水时间
public LeakyBucketRateLimiter(int capacity, double leakRatePerSec) {
this.capacity = capacity;
this.leakRate = leakRatePerSec / 1000.0;
this.lastLeakTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 漏水
double leaked = (now - lastLeakTime) * leakRate;
currentWater = Math.max(0, currentWater - leaked);
lastLeakTime = now;
// 检查是否超限
if (currentWater < capacity) {
currentWater += 1;
return true;
}
return false;
}
}
```
**使用示例**
```java
// 容量100漏水速率10 请求/秒
LeakyBucketRateLimiter limiter = new LeakyBucketRateLimiter(100, 10);
```
---
#### **优缺点**
**优点**
- 平滑流量,恒定速率
- 保护下游系统
**缺点**
- 无法应对突发流量
- 参数调整困难
---
### 6. 令牌桶算法
#### **原理**
系统以恒定速率向桶中放入令牌:
- 请求到达时,从桶中获取令牌
- 有令牌则通过,无令牌则拒绝
- 桶满时,令牌溢出
**图解**
```
恒定速率放入令牌
┌───┐
│ ○○○│ ← 令牌桶(容量 = C
│ ○○○│
└───┘ ↓
请求获取令牌
```
**特点**
- **允许突发**:桶中有令牌时可突发处理
- **恒定平均速率**:长期平均速率恒定
---
#### **Java 实现**
```java
public class TokenBucketRateLimiter {
private final int capacity; // 桶容量
private final double refillRate; // 放入速率(令牌/毫秒)
private double currentTokens; // 当前令牌数
private long lastRefillTime; // 上次放入时间
public TokenBucketRateLimiter(int capacity, double refillRatePerSec) {
this.capacity = capacity;
this.refillRate = refillRatePerSec / 1000.0;
this.currentTokens = capacity;
this.lastRefillTime = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
// 放入令牌
double refillTokens = (now - lastRefillTime) * refillRate;
currentTokens = Math.min(capacity, currentTokens + refillTokens);
lastRefillTime = now;
// 检查是否有令牌
if (currentTokens >= 1) {
currentTokens -= 1;
return true;
}
return false;
}
}
```
**使用示例**
```java
// 容量100放入速率10 令牌/秒
TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(100, 10);
```
---
#### **Guava RateLimiter令牌桶实现**
```java
import com.google.common.util.concurrent.RateLimiter;
// 创建限流器:每秒 100 个 permits
RateLimiter rateLimiter = RateLimiter.create(100.0);
// 尝试获取 permit
if (rateLimiter.tryAcquire()) {
// 通过
processRequest();
} else {
// 被限流
rejectRequest();
}
// 阻塞式获取(会等待)
rateLimiter.acquire(); // 获取 1 个 permit
rateLimiter.acquire(5); // 获取 5 个 permits
```
---
#### **优缺点**
**优点**
- 允许突发流量
- 灵活配置
**缺点**
- 实现复杂
- 突发流量可能影响下游
---
### 7. 漏桶 vs 令牌桶
| 特性 | 漏桶 | 令牌桶 |
|------|------|--------|
| **速率** | 恒定流出 | 恒定放入 |
| **突发** | 不允许突发 | 允许突发 |
| **适用** | 保护下游系统 | 通用场景 |
| **平滑性** | 高 | 中 |
**选择建议**
- 保护数据库等脆弱系统 → **漏桶**
- API 接口限流 → **令牌桶**
---
### 8. 分布式限流
#### **基于 Redis滑动窗口**
```java
@Service
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean allowRequest(String key, int limit, int windowSizeSec) {
long now = System.currentTimeMillis();
long windowStart = now - windowSizeSec * 1000;
// Lua 脚本(原子操作)
String luaScript =
"local key = KEYS[1]\n" +
"local now = tonumber(ARGV[1])\n" +
"local windowStart = tonumber(ARGV[2])\n" +
"local limit = tonumber(ARGV[3])\n" +
// 删除过期记录
"redis.call('zremrangebyscore', key, '-inf', windowStart)\n" +
// 获取当前窗口内计数
"local count = redis.call('zcard', key)\n" +
// 检查是否超限
"if count < limit then\n" +
" redis.call('zadd', key, now, now)\n" +
" redis.call('expire', key, windowStart)\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
// 执行 Lua 脚本
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key),
String.valueOf(now), String.valueOf(windowStart), String.valueOf(limit));
return result == 1;
}
}
```
**使用示例**
```java
// 限制:每个 IP 每分钟 100 次请求
boolean allowed = redisRateLimiter.allowRequest("rate:limit:ip:" + ip, 100, 60);
```
---
#### **基于 Sentinel阿里巴巴**
**引入依赖**
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
```
**配置限流规则**
```java
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
// 定义规则QPS 限制 1000
FlowRule rule = new FlowRule();
rule.setResource("api"); // 资源名
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 限流阈值类型
rule.setCount(1000); // 阈值
rule.setStrategy(RuleConstant.STRATEGY_DIRECT); // 流控策略
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
```
**使用注解**
```java
@RestController
public class ApiController {
@GetMapping("/api")
@SentinelResource(value = "api", blockHandler = "handleBlock")
public String api() {
return "success";
}
// 限流降级
public String handleBlock(BlockException ex) {
return "Too many requests";
}
}
```
**配置文件(动态规则)**
```yaml
# application.yml
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel Dashboard
datasource:
flow:
nacos:
server-addr: localhost:8848
data-id: ${spring.application.name}-flow-rules
rule-type: flow
```
---
### 9. 实际项目应用
#### **多级限流策略**
```
用户级限流(单用户 QPS = 10
接口级限流(总 QPS = 10000
应用级限流CPU < 80%
数据库级限流(连接数 < 500
```
---
#### **用户级限流(防刷)**
```java
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedisTemplate redisTemplate;
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String key = "rate:limit:user:" + getCurrentUserId();
int limit = rateLimit.limit();
int duration = rateLimit.duration();
// Redis + Lua 限流
boolean allowed = allowRequest(key, limit, duration);
if (!allowed) {
throw new RateLimitException("请求过于频繁,请稍后再试");
}
return joinPoint.proceed();
}
}
```
---
#### **接口级限流**
```java
// Sentinel 配置不同接口的限流规则
FlowRule apiRule = new FlowRule();
apiRule.setResource("userApi");
apiRule.setCount(1000);
FlowRule orderRule = new FlowRule();
orderRule.setResource("orderApi");
orderRule.setCount(500);
```
---
### 10. 阿里 P7 加分项
**深度理解**
- 理解各种限流算法的适用场景和权衡
- 理解分布式限流的一致性问题
**实战经验**
- 有处理线上突发流量导致系统崩溃的经验
- 有设计多级限流策略的经验
- 有限流参数调优的经验(如何确定限流阈值)
**架构能力**
- 能设计支持动态调整的限流系统
- 能设计限流的监控和告警体系
- 有灰度发布和降级预案
**技术选型**
- 了解 Sentinel、Hystrix、Resilience4j 等框架
- 有自研限流组件的经验
- 能根据业务特点选择合适的限流算法