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:
208
questions/05-并发编程/Java并发编程基础.md
Normal file
208
questions/05-并发编程/Java并发编程基础.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Java 并发编程基础
|
||||
|
||||
## 问题
|
||||
|
||||
1. 进程和线程的区别?
|
||||
2. 创建线程有哪些方式?
|
||||
3. synchronized 关键字的原理?
|
||||
4. volatile 关键字的作用?
|
||||
5. CAS(Compare And Swap)的原理和 ABA 问题?
|
||||
6. ThreadLocal 的原理和内存泄漏问题?
|
||||
|
||||
---
|
||||
|
||||
## 标准答案
|
||||
|
||||
### 1. 进程 vs 线程
|
||||
|
||||
| 特性 | 进程 | 线程 |
|
||||
|------|------|------|
|
||||
| **资源** | 独立内存、文件描述符 | 共享进程资源 |
|
||||
| **通信** | IPC(管道、消息队列、共享内存) | 共享内存 |
|
||||
| **上下文切换** | 慢(需保存更多状态) | 快 |
|
||||
| **开销** | 大 | 小 |
|
||||
|
||||
---
|
||||
|
||||
### 2. 创建线程的方式
|
||||
|
||||
**方式 1:继承 Thread**
|
||||
```java
|
||||
class MyThread extends Thread {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Thread running");
|
||||
}
|
||||
}
|
||||
new MyThread().start();
|
||||
```
|
||||
|
||||
**方式 2:实现 Runnable**
|
||||
```java
|
||||
class MyRunnable implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Runnable running");
|
||||
}
|
||||
}
|
||||
new Thread(new MyRunnable()).start();
|
||||
```
|
||||
|
||||
**方式 3:实现 Callable(有返回值)**
|
||||
```java
|
||||
class MyCallable implements Callable<String> {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
return "Callable result";
|
||||
}
|
||||
}
|
||||
FutureTask<String> future = new FutureTask<>(new MyCallable());
|
||||
new Thread(future).start();
|
||||
String result = future.get();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. synchronized 原理
|
||||
|
||||
**字节码层面**:
|
||||
```java
|
||||
// 同步方法
|
||||
public synchronized void method() { }
|
||||
// 字节码:ACC_SYNCHRONIZED
|
||||
|
||||
// 同步代码块
|
||||
synchronized (object) { }
|
||||
// 字节码:monitorenter、monitorexit
|
||||
```
|
||||
|
||||
**对象头**:
|
||||
```
|
||||
Mark Word(32 位 JVM)
|
||||
┌────────────┬────────────┬──────────────┐
|
||||
│ 锁状态 │ 29 位或2位 │ 是否是偏向锁 │
|
||||
├────────────┼────────────┼──────────────┤
|
||||
│ 无锁 │ 对象 Hash │ 01 │
|
||||
│ 偏向锁 │ 线程 ID │ 01 │
|
||||
│ 轻量级锁 │ 栈中 Lock Record | 00 │
|
||||
│ 重量级锁 │ 管程指针 │ 10 │
|
||||
└────────────┴────────────┴──────────────┘
|
||||
```
|
||||
|
||||
**锁升级**:
|
||||
```
|
||||
无锁 → 偏向锁 → 轻量级锁(CAS 自旋) → 重量级锁(阻塞)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. volatile 关键字
|
||||
|
||||
**作用**:
|
||||
1. **保证可见性**(JMM,主内存与工作内存)
|
||||
2. **禁止指令重排序**
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
private volatile boolean running = true;
|
||||
|
||||
public void stop() {
|
||||
running = false; // 立即对所有线程可见
|
||||
}
|
||||
```
|
||||
|
||||
**不能保证原子性**:
|
||||
```java
|
||||
private volatile int count = 0;
|
||||
|
||||
// ❌ 非线程安全
|
||||
count++; // read → modify → write(非原子操作)
|
||||
|
||||
// ✅ 线程安全
|
||||
synchronized (this) {
|
||||
count++;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. CAS 和 ABA 问题
|
||||
|
||||
**CAS(Compare And Swap)**:
|
||||
```java
|
||||
// Unsafe.compareAndSwapInt(object, offset, expect, update)
|
||||
// 如果当前值 == 期望值,则更新为 update
|
||||
|
||||
AtomicInteger atomic = new AtomicInteger(0);
|
||||
atomic.compareAndSet(0, 1); // CAS 操作
|
||||
```
|
||||
|
||||
**ABA 问题**:
|
||||
```
|
||||
线程 A:读取值 A
|
||||
线程 B:A → B → A
|
||||
线程 A:CAS 成功(不知道值已变化)
|
||||
```
|
||||
|
||||
**解决:版本号(AtomicStampedReference)**:
|
||||
```java
|
||||
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
|
||||
|
||||
int oldStamp = ref.getStamp();
|
||||
ref.compareAndSet(100, 101, oldStamp, oldStamp + 1); // 版本号也参与 CAS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. ThreadLocal
|
||||
|
||||
**原理**:
|
||||
```java
|
||||
public void set(T value) {
|
||||
Thread t = Thread.currentThread();
|
||||
ThreadLocalMap map = t.threadLocals;
|
||||
if (map != null)
|
||||
map.set(this, value); // key 是 ThreadLocal 对象,value 是值
|
||||
else
|
||||
createMap(t, value);
|
||||
}
|
||||
```
|
||||
|
||||
**内存泄漏**:
|
||||
```java
|
||||
// ❌ 可能导致内存泄漏
|
||||
private static ThreadLocal<byte[]> data = new ThreadLocal<>();
|
||||
|
||||
public void process() {
|
||||
data.set(new byte[1024 * 1024]); // 1MB
|
||||
// 未调用 remove()
|
||||
}
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- ThreadLocal 是弱引用,但 value 是强引用
|
||||
- 线程不销毁,value 不会回收
|
||||
|
||||
**解决**:
|
||||
```java
|
||||
try {
|
||||
data.set(new byte[1024 * 1024]);
|
||||
// 业务逻辑
|
||||
} finally {
|
||||
data.remove(); // 必须
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 阿里 P7 加分项
|
||||
|
||||
**深度理解**:
|
||||
- 理解 JMM(Java 内存模型)
|
||||
- 理解 happens-before 原则
|
||||
- 理解 synchronized 的优化(偏向锁、轻量级锁)
|
||||
|
||||
**实战经验**:
|
||||
- 有并发问题的排查经验
|
||||
- 有性能优化经验(减少锁竞争)
|
||||
- 有死锁的排查和解决经验
|
||||
600
questions/05-并发编程/线程池核心参数.md
Normal file
600
questions/05-并发编程/线程池核心参数.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# 线程池核心参数详解
|
||||
|
||||
## 问题
|
||||
|
||||
1. 线程池的核心参数有哪些?各自的作用是什么?
|
||||
2. 如何合理设置线程池大小?
|
||||
3. 线程池的拒绝策略有哪些?如何自定义?
|
||||
4. 线程池如何优雅关闭?
|
||||
5. 线程池的监控指标有哪些?
|
||||
6. 在实际项目中如何使用线程池?
|
||||
|
||||
---
|
||||
|
||||
## 标准答案
|
||||
|
||||
### 1. 线程池核心参数
|
||||
|
||||
#### **ThreadPoolExecutor 构造函数**
|
||||
|
||||
```java
|
||||
public ThreadPoolExecutor(
|
||||
int corePoolSize, // 核心线程数
|
||||
int maximumPoolSize, // 最大线程数
|
||||
long keepAliveTime, // 非核心线程空闲存活时间
|
||||
TimeUnit unit, // 时间单位
|
||||
BlockingQueue<Runnable> workQueue, // 任务队列
|
||||
ThreadFactory threadFactory, // 线程工厂
|
||||
RejectedExecutionHandler handler // 拒绝策略
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **参数详解**
|
||||
|
||||
**1. corePoolSize(核心线程数)**
|
||||
- **说明**:即使空闲也保留的线程数
|
||||
- **默认值**:创建时无核心线程(任务到达时才创建)
|
||||
- **预热**:`prestartAllCoreThreads()` 提前创建核心线程
|
||||
|
||||
```java
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
10, // 核心线程数
|
||||
20, // 最大线程数
|
||||
...
|
||||
);
|
||||
|
||||
// 预热核心线程
|
||||
executor.prestartAllCoreThreads();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**2. maximumPoolSize(最大线程数)**
|
||||
- **说明**:线程池允许的最大线程数
|
||||
- **限制**:`maximumPoolSize >= corePoolSize`
|
||||
- **动态调整**:运行时可通过 `setMaximumPoolSize()` 调整
|
||||
|
||||
```java
|
||||
// 动态调整最大线程数
|
||||
executor.setMaximumPoolSize(50);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**3. keepAliveTime(非核心线程存活时间)**
|
||||
- **说明**:非核心线程的空闲存活时间
|
||||
- **超时回收**:超过时间后,线程会被回收
|
||||
- **允许回收核心线程**:`allowCoreThreadTimeOut(true)`
|
||||
|
||||
```java
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
10,
|
||||
20,
|
||||
60, // 存活时间
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100)
|
||||
);
|
||||
|
||||
// 允许核心线程超时回收
|
||||
executor.allowCoreThreadTimeOut(true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**4. workQueue(任务队列)**
|
||||
|
||||
**常见队列**:
|
||||
|
||||
| 队列 | 特性 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| **SynchronousQueue** | 不存储,直接传递 | 高并发、低延迟 |
|
||||
| **LinkedBlockingQueue** | 无界队列(默认 Integer.MAX_VALUE) | 任务提交频繁 |
|
||||
| **ArrayBlockingQueue** | 有界队列 | 防止资源耗尽 |
|
||||
| **PriorityBlockingQueue** | 优先级队列 | 优先级任务 |
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
// 1. SynchronousQueue(高并发)
|
||||
ExecutorService executor1 = new ThreadPoolExecutor(
|
||||
10, 20,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new SynchronousQueue<Runnable>() // 无队列,直接传递
|
||||
);
|
||||
|
||||
// 2. LinkedBlockingQueue(无界)
|
||||
ExecutorService executor2 = new ThreadPoolExecutor(
|
||||
10, 20,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(1000) // 队列长度 1000
|
||||
);
|
||||
|
||||
// 3. PriorityBlockingQueue(优先级)
|
||||
ExecutorService executor3 = new ThreadPoolExecutor(
|
||||
10, 20,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new PriorityBlockingQueue<>(100)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**5. threadFactory(线程工厂)**
|
||||
|
||||
**作用**:
|
||||
- 设置线程名称(便于排查)
|
||||
- 设置线程优先级
|
||||
- 设置是否为守护线程
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
|
||||
.setNameFormat("order-pool-%d") // 线程名称前缀
|
||||
.setDaemon(false) // 非守护线程
|
||||
.setPriority(Thread.NORM_PRIORITY)
|
||||
.build();
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
10, 20,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100),
|
||||
namedThreadFactory
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**6. handler(拒绝策略)**
|
||||
|
||||
**内置策略**:
|
||||
|
||||
| 策略 | 说明 |
|
||||
|------|------|
|
||||
| **AbortPolicy(默认)** | 抛出异常 |
|
||||
| **CallerRunsPolicy** | 调用者线程执行 |
|
||||
| **DiscardPolicy** | 静默丢弃 |
|
||||
| **DiscardOldestPolicy** | 丢弃最旧的任务 |
|
||||
|
||||
```java
|
||||
// 自定义拒绝策略
|
||||
RejectedExecutionHandler handler = new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
|
||||
// 记录日志
|
||||
log.warn("任务被拒绝: {}", r);
|
||||
|
||||
// 重试(加入队列等待)
|
||||
if (!executor.isShutdown()) {
|
||||
try {
|
||||
executor.getQueue().put(r);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
10, 20,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100),
|
||||
handler
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 线程池工作流程
|
||||
|
||||
```
|
||||
任务提交
|
||||
↓
|
||||
核心线程数 < corePoolSize?
|
||||
├─ 是 → 创建核心线程并执行
|
||||
└─ 否 → 继续
|
||||
↓
|
||||
队列未满?
|
||||
├─ 是 → 加入队列
|
||||
└─ 否 → 继续
|
||||
↓
|
||||
线程数 < maximumPoolSize?
|
||||
├─ 是 → 创建非核心线程并执行
|
||||
└─ 否 → 继续
|
||||
↓
|
||||
拒绝策略
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 合理设置线程池大小
|
||||
|
||||
#### **CPU 密集型任务**
|
||||
|
||||
**特点**:主要消耗 CPU 资源(计算、加密)
|
||||
|
||||
**公式**:
|
||||
```
|
||||
线程数 = CPU 核心数 + 1
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- CPU 密集型任务不需要太多线程
|
||||
- +1 是为了当某线程因页故障等原因暂停时,CPU 不会闲置
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
int cpuCore = Runtime.getRuntime().availableProcessors(); // 8
|
||||
int poolSize = cpuCore + 1; // 9
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
poolSize, // 核心线程数
|
||||
poolSize, // 最大线程数
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<>(100)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **IO 密集型任务**
|
||||
|
||||
**特点**:主要等待 IO(网络、磁盘)
|
||||
|
||||
**公式**:
|
||||
```
|
||||
线程数 = CPU 核心数 × (1 + IO 耗时 / CPU 耗时)
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
// IO 耗时 / CPU 耗时 = 2(IO 占 2/3,CPU 占 1/3)
|
||||
int cpuCore = Runtime.getRuntime().availableProcessors(); // 8
|
||||
int poolSize = cpuCore * (1 + 2); // 24
|
||||
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
cpuCore, // 核心线程数 = CPU 核心数
|
||||
poolSize, // 最大线程数
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(500)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **通用公式**
|
||||
|
||||
```
|
||||
线程数 = CPU 核心数 × 目标 CPU 使用率 × (1 + IO 耗时 / CPU 耗时)
|
||||
```
|
||||
|
||||
**参数调整**:
|
||||
- 目标 CPU 使用率:80% - 90%
|
||||
- IO / CPU 比例:通过压测获得
|
||||
|
||||
---
|
||||
|
||||
### 4. 线程池监控
|
||||
|
||||
#### **监控指标**
|
||||
|
||||
| 指标 | 说明 | 获取方法 |
|
||||
|------|------|----------|
|
||||
| **活跃线程数** | 正在执行任务的线程数 | `getActiveCount()` |
|
||||
| **已完成任务数** | 历史完成的任务总数 | `getCompletedTaskCount()` |
|
||||
| **总任务数** | 已完成 + 正在执行 | `getTaskCount()` |
|
||||
| **队列大小** | 队列中待执行任务数 | `getQueue().size()` |
|
||||
| **最大线程数** | 历史最大线程数 | `getLargestPoolSize()` |
|
||||
| **线程池是否关闭** | `isShutdown()` | `isShutdown()` |
|
||||
|
||||
---
|
||||
|
||||
#### **监控代码**
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ThreadPoolMonitor {
|
||||
|
||||
@Autowired
|
||||
private Map<String, ThreadPoolExecutor> executorMap;
|
||||
|
||||
@Scheduled(fixedRate = 60000) // 每分钟
|
||||
public void monitor() {
|
||||
executorMap.forEach((name, executor) -> {
|
||||
ThreadPoolExecutorStats stats = new ThreadPoolExecutorStats();
|
||||
stats.setName(name);
|
||||
stats.setCorePoolSize(executor.getCorePoolSize());
|
||||
stats.setMaximumPoolSize(executor.getMaximumPoolSize());
|
||||
stats.setActiveCount(executor.getActiveCount());
|
||||
stats.setCompletedTaskCount(executor.getCompletedTaskCount());
|
||||
stats.setTaskCount(executor.getTaskCount());
|
||||
stats.setQueueSize(executor.getQueue().size());
|
||||
stats.setLargestPoolSize(executor.getLargestPoolSize());
|
||||
|
||||
// 上报到监控系统(Prometheus、Grafana)
|
||||
Metrics.report(stats);
|
||||
|
||||
// 告警判断
|
||||
if (executor.getActiveCount() >= executor.getMaximumPoolSize() * 0.8) {
|
||||
alert("线程池 " + name + " 负载过高");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **Actuator 监控(Spring Boot)**
|
||||
|
||||
**依赖**:
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**配置**:
|
||||
```yaml
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
metrics:
|
||||
export:
|
||||
prometheus:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
**访问**:
|
||||
```bash
|
||||
curl http://localhost:8080/actuator/metrics
|
||||
curl http://localhost:8080/actuator/metrics/executor.pool.size
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 线程池优雅关闭
|
||||
|
||||
#### **问题**
|
||||
|
||||
不优雅关闭的后果:
|
||||
- 已提交的任务可能丢失
|
||||
- 正在执行的任务可能被中断
|
||||
|
||||
---
|
||||
|
||||
#### **shutdown()**
|
||||
|
||||
```java
|
||||
executor.shutdown();
|
||||
|
||||
try {
|
||||
// 等待任务完成
|
||||
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
// 超时,强制关闭
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 不再接受新任务
|
||||
- 等待已提交的任务完成
|
||||
- 超时后可调用 `shutdownNow()` 强制关闭
|
||||
|
||||
---
|
||||
|
||||
#### **shutdownNow()**
|
||||
|
||||
```java
|
||||
List<Runnable> unfinishedTasks = executor.shutdownNow();
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 不再接受新任务
|
||||
- 尝试停止正在执行的任务(通过 `Thread.interrupt()`)
|
||||
- 返回未执行的任务列表
|
||||
|
||||
---
|
||||
|
||||
### 6. Spring 线程池配置
|
||||
|
||||
#### **配置类**
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
@Bean("orderThreadPool")
|
||||
public ThreadPoolExecutor orderThreadPool() {
|
||||
return new ThreadPoolExecutor(
|
||||
10, // 核心线程数
|
||||
20, // 最大线程数
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("order-pool-%d")
|
||||
.build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
}
|
||||
|
||||
@Bean("emailThreadPool")
|
||||
public ThreadPoolExecutor emailThreadPool() {
|
||||
return new ThreadPoolExecutor(
|
||||
5,
|
||||
10,
|
||||
60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(50),
|
||||
new ThreadFactoryBuilder()
|
||||
.setNameFormat("email-pool-%d")
|
||||
.build(),
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **使用**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class OrderService {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("orderThreadPool")
|
||||
private ThreadPoolExecutor orderThreadPool;
|
||||
|
||||
public void createOrder(Order order) {
|
||||
// 异步处理
|
||||
orderThreadPool.execute(() -> {
|
||||
// 处理订单
|
||||
processOrder(order);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### **@Async(Spring 异步)**
|
||||
|
||||
**配置**:
|
||||
```java
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("asyncExecutor")
|
||||
public Executor asyncExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(10);
|
||||
executor.setMaxPoolSize(20);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("async-pool-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**使用**:
|
||||
```java
|
||||
@Service
|
||||
public class EmailService {
|
||||
|
||||
@Async("asyncExecutor")
|
||||
public void sendEmail(String to, String subject, String body) {
|
||||
// 异步发送邮件
|
||||
mailSender.send(to, subject, body);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 实际项目经验
|
||||
|
||||
#### **案例 1:线程池参数调优**
|
||||
|
||||
**问题**:
|
||||
- 订单接口响应慢
|
||||
- CPU 使用率低(30%),线程池队列满
|
||||
|
||||
**分析**:
|
||||
```java
|
||||
// 原配置
|
||||
corePoolSize = 5
|
||||
maximumPoolSize = 10
|
||||
queue = LinkedBlockingQueue(100)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 线程数太少,任务堆积在队列
|
||||
- 数据库连接池用满,等待连接
|
||||
|
||||
**优化**:
|
||||
```java
|
||||
// 优化后配置
|
||||
corePoolSize = 20 // 增加
|
||||
maximumPoolSize = 50 // 增加
|
||||
queue = LinkedBlockingQueue(500) // 增加
|
||||
```
|
||||
|
||||
**结果**:响应时间从 2s 降至 200ms
|
||||
|
||||
---
|
||||
|
||||
#### **案例 2:动态线程池**
|
||||
|
||||
**需求**:根据流量动态调整线程池大小
|
||||
|
||||
**实现**:
|
||||
```java
|
||||
@Component
|
||||
public class DynamicThreadPoolManager {
|
||||
|
||||
private final Map<String, ThreadPoolExecutor> executorMap = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 定时调整线程池大小
|
||||
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
scheduler.scheduleAtFixedRate(this::adjustThreadPoolSize, 1, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void adjustThreadPoolSize() {
|
||||
executorMap.forEach((name, executor) -> {
|
||||
// 获取当前负载
|
||||
int activeCount = executor.getActiveCount();
|
||||
int maximumPoolSize = executor.getMaximumPoolSize();
|
||||
|
||||
// 负载 > 80%,扩容
|
||||
if (activeCount > maximumPoolSize * 0.8) {
|
||||
int newSize = Math.min(maximumPoolSize * 2, 100);
|
||||
executor.setMaximumPoolSize(newSize);
|
||||
log.info("扩容线程池: {} -> {}", name, newSize);
|
||||
}
|
||||
// 负载 < 20%,缩容
|
||||
else if (activeCount < maximumPoolSize * 0.2) {
|
||||
int newSize = Math.max(maximumPoolSize / 2, executor.getCorePoolSize());
|
||||
executor.setMaximumPoolSize(newSize);
|
||||
log.info("缩容线程池: {} -> {}", name, newSize);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 阿里 P7 加分项
|
||||
|
||||
**深度理解**:
|
||||
- 理解线程池的状态转换(RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED)
|
||||
- 理解 `Worker` 的实现原理(继承 AQS、实现 Runnable)
|
||||
- 理解线程池的异常处理机制
|
||||
|
||||
**实战经验**:
|
||||
- 有线程池参数调优的经验
|
||||
- 有处理线程池饱和问题的经验
|
||||
- 有线程池监控和告警的经验
|
||||
|
||||
**架构能力**:
|
||||
- 能设计动态线程池(根据流量调整)
|
||||
- 能设计线程池隔离(不同业务独立线程池)
|
||||
- 能设计线程池监控体系
|
||||
|
||||
**技术选型**:
|
||||
- 了解 `ForkJoinPool`(工作窃取线程池)
|
||||
- 了解 `ScheduledThreadPoolExecutor`(定时任务线程池)
|
||||
- 了解 `Vert.x`、WebFlux 等响应式框架的线程模型
|
||||
1047
questions/05-并发编程/设计模式.md
Normal file
1047
questions/05-并发编程/设计模式.md
Normal file
File diff suppressed because it is too large
Load Diff
667
questions/05-并发编程/限流策略与算法.md
Normal file
667
questions/05-并发编程/限流策略与算法.md
Normal 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 等框架
|
||||
- 有自研限流组件的经验
|
||||
- 能根据业务特点选择合适的限流算法
|
||||
Reference in New Issue
Block a user