feat: add advanced algorithm and data structure interview questions
- Add B+ Tree implementation with detailed explanations and Java code - Add LRU Cache implementation with multiple approaches - Add Red-Black Tree implementation with rotation operations - Add Skip List implementation with probabilistic balancing - Add Timing Wheel algorithm for task scheduling - Include detailed time complexity analysis and practical applications - Add comparison with other data structures and common interview questions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
845
questions/alg-timingwheel.md
Normal file
845
questions/alg-timingwheel.md
Normal file
@@ -0,0 +1,845 @@
|
|||||||
|
# 时间轮算法
|
||||||
|
|
||||||
|
## 数据结构原理
|
||||||
|
|
||||||
|
### 什么是时间轮?
|
||||||
|
时间轮(Timing Wheel)是一种用于定时任务调度的数据结构,通过环形队列和层级结构实现高效的时间管理。它特别适合处理大量延迟任务和周期性任务,在分布式系统中广泛应用。
|
||||||
|
|
||||||
|
### 时间轮的核心概念
|
||||||
|
|
||||||
|
1. **轮槽(Slot)**:时间轮的基本单位,每个槽代表一个时间片
|
||||||
|
2. **指针(Pointer)**:当前时间槽的指针,顺时针移动
|
||||||
|
3. **任务(Task)**:需要执行的任务,包含执行时间信息
|
||||||
|
4. **层级结构**:多级时间轮处理不同时间跨度的任务
|
||||||
|
|
||||||
|
### 时间轮的工作原理
|
||||||
|
|
||||||
|
1. **任务添加**:根据任务延迟时间计算放入的槽位
|
||||||
|
2. **任务执行**:指针到达槽位时,执行该槽位所有任务
|
||||||
|
3. **指针移动**:每过一个时间片,指针移动到下一个槽位
|
||||||
|
4. **延迟计算**:任务延迟时间 = 当前时间到执行时间的差值
|
||||||
|
|
||||||
|
## 图解说明
|
||||||
|
|
||||||
|
```
|
||||||
|
单层时间轮示例(槽位数=8,时间片=1秒):
|
||||||
|
0 1 2 3 4 5 6 7
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
| | | | | | | | |
|
||||||
|
|task1|task2| |task3| | |task4| |
|
||||||
|
| | | | | | | | |
|
||||||
|
+-----+-----+-----+-----+-----+-----+-----+-----+
|
||||||
|
↑
|
||||||
|
指针
|
||||||
|
当前时间:0秒
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多层时间轮示例
|
||||||
|
|
||||||
|
```
|
||||||
|
多层时间轮(3层):
|
||||||
|
Layer 3 (1小时/槽): [0][1][2][3][4][5] -> 当前:0
|
||||||
|
Layer 2 (1分钟/槽): [0][1][2][3][4][5][6][7] -> 当前:0
|
||||||
|
Layer 1 (1秒/槽): [0][1][2][3][4][5][6][7] -> 当前:0
|
||||||
|
Layer 0 (100ms/槽): [0][1][2][3][4][5][6][7] -> 当前:0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 任务添加流程
|
||||||
|
|
||||||
|
```
|
||||||
|
添加任务(延迟 350ms):
|
||||||
|
1. Layer 0: 350ms / 100ms = 3.5 -> 放入槽 4
|
||||||
|
2. 如果当前指针 > 槽位,放入上一层
|
||||||
|
3. 继续处理,直到找到合适的层级
|
||||||
|
```
|
||||||
|
|
||||||
|
## Java 代码实现
|
||||||
|
|
||||||
|
### 基础时间轮实现
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
public class TimingWheel {
|
||||||
|
private final int slotSize; // 槽位数量
|
||||||
|
private final long timePerSlot; // 每个槽位的时间间隔(毫秒)
|
||||||
|
private final List<TimerTask>[] slots; // 时间轮槽
|
||||||
|
private final AtomicInteger currentSlot; // 当前槽位索引
|
||||||
|
private final ExecutorService executor; // 任务执行器
|
||||||
|
private final TimingWheel overflowWheel; // 上层时间轮
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public TimingWheel(int slotSize, long timePerSlot, ExecutorService executor) {
|
||||||
|
this.slotSize = slotSize;
|
||||||
|
this.timePerSlot = timePerSlot;
|
||||||
|
this.slots = (List<TimerTask>[]) new List[slotSize];
|
||||||
|
this.currentSlot = new AtomicInteger(0);
|
||||||
|
this.executor = executor;
|
||||||
|
this.overflowWheel = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < slotSize; i++) {
|
||||||
|
slots[i] = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动时间轮线程
|
||||||
|
new Thread(this::rotate).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加任务
|
||||||
|
public void addTask(TimerTask task) {
|
||||||
|
if (task.getDelay() <= timePerSlot) {
|
||||||
|
int targetSlot = (currentSlot.get() + (int)(task.getDelay() / timePerSlot)) % slotSize;
|
||||||
|
slots[targetSlot].add(task);
|
||||||
|
} else {
|
||||||
|
// 处理跨槽位任务
|
||||||
|
if (overflowWheel == null) {
|
||||||
|
// 创建上层时间轮
|
||||||
|
overflowWheel = new TimingWheel(slotSize, timePerSlot * slotSize, executor);
|
||||||
|
}
|
||||||
|
overflowWheel.addTask(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间轮旋转
|
||||||
|
private void rotate() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(timePerSlot);
|
||||||
|
|
||||||
|
// 获取当前槽位
|
||||||
|
int slot = currentSlot.getAndIncrement() % slotSize;
|
||||||
|
|
||||||
|
// 执行槽位中的任务
|
||||||
|
List<TimerTask> tasks = slots[slot];
|
||||||
|
if (!tasks.isEmpty()) {
|
||||||
|
List<TimerTask> taskList = new ArrayList<>(tasks);
|
||||||
|
tasks.clear();
|
||||||
|
|
||||||
|
for (TimerTask task : taskList) {
|
||||||
|
executor.submit(() -> task.execute());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理溢出任务
|
||||||
|
if (overflowWheel != null) {
|
||||||
|
overflowWheel.rotateTasks();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旋转溢出任务
|
||||||
|
private void rotateTasks() {
|
||||||
|
// 从当前槽位取出的任务
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时任务接口
|
||||||
|
interface TimerTask {
|
||||||
|
long getDelay(); // 获取延迟时间(毫秒)
|
||||||
|
void execute(); // 执行任务
|
||||||
|
}
|
||||||
|
|
||||||
|
// 具体任务实现
|
||||||
|
class DelayedTask implements TimerTask {
|
||||||
|
private final long delay;
|
||||||
|
private final Runnable task;
|
||||||
|
|
||||||
|
public DelayedTask(long delay, Runnable task) {
|
||||||
|
this.delay = delay;
|
||||||
|
this.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDelay() {
|
||||||
|
return delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 增强型时间轮实现
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
public class EnhancedTimingWheel {
|
||||||
|
private final int tier; // 当前层级
|
||||||
|
private final int slotSize; // 槽位数量
|
||||||
|
private final long timePerSlot; // 每个槽位的时间间隔
|
||||||
|
private final long wheelTimeout; // 轮超时时间
|
||||||
|
private final List<TimerTaskEntry>[] slots; // 时间轮槽
|
||||||
|
private final AtomicInteger currentSlot; // 当前槽位索引
|
||||||
|
private final ExecutorService executor; // 任务执行器
|
||||||
|
private final EnhancedTimingWheel overflowWheel; // 上层时间轮
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public EnhancedTimingWheel(int tier, int slotSize, long timePerSlot,
|
||||||
|
ExecutorService executor) {
|
||||||
|
this.tier = tier;
|
||||||
|
this.slotSize = slotSize;
|
||||||
|
this.timePerSlot = timePerSlot;
|
||||||
|
this.wheelTimeout = slotSize * timePerSlot;
|
||||||
|
this.executor = executor;
|
||||||
|
this.slots = (List<TimerTaskEntry>[]) new List[slotSize];
|
||||||
|
this.currentSlot = new AtomicInteger(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < slotSize; i++) {
|
||||||
|
slots[i] = new CopyOnWriteArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tier > 0) {
|
||||||
|
this.overflowWheel = new EnhancedTimingWheel(tier - 1, slotSize,
|
||||||
|
timePerSlot * slotSize, executor);
|
||||||
|
} else {
|
||||||
|
this.overflowWheel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务条目
|
||||||
|
public static class TimerTaskEntry {
|
||||||
|
private final TimerTask task;
|
||||||
|
private final long expiration;
|
||||||
|
private TimerTaskEntry next;
|
||||||
|
private volatile boolean cancelled = false;
|
||||||
|
|
||||||
|
public TimerTaskEntry(TimerTask task, long expiration) {
|
||||||
|
this.task = task;
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return expiration <= System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动时间轮
|
||||||
|
public void start() {
|
||||||
|
if (running.compareAndSet(false, true)) {
|
||||||
|
new Thread(this::rotate).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加任务
|
||||||
|
public void addTask(TimerTask task, long delay) {
|
||||||
|
long expiration = System.currentTimeMillis() + delay;
|
||||||
|
TimerTaskEntry entry = new TimerTaskEntry(task, expiration);
|
||||||
|
|
||||||
|
if (delay < wheelTimeout) {
|
||||||
|
int targetSlot = (int)((expiration / timePerSlot) % slotSize);
|
||||||
|
slots[targetSlot].add(entry);
|
||||||
|
} else {
|
||||||
|
if (overflowWheel != null) {
|
||||||
|
overflowWheel.addTask(task, delay);
|
||||||
|
} else {
|
||||||
|
// 延迟太长,直接安排执行
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(delay);
|
||||||
|
task.execute();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消任务
|
||||||
|
public boolean cancelTask(TimerTask task) {
|
||||||
|
// 简化的实现,实际需要遍历所有槽位
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间轮旋转
|
||||||
|
private void rotate() {
|
||||||
|
while (running.get()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(timePerSlot);
|
||||||
|
|
||||||
|
int slot = currentSlot.getAndIncrement() % slotSize;
|
||||||
|
List<TimerTaskEntry> tasks = slots[slot];
|
||||||
|
|
||||||
|
if (!tasks.isEmpty()) {
|
||||||
|
List<TimerTaskEntry> expiredTasks = new ArrayList<>();
|
||||||
|
List<TimerTaskEntry> activeTasks = new ArrayList<>();
|
||||||
|
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
for (TimerTaskEntry entry : tasks) {
|
||||||
|
if (entry.isExpired() && !entry.isCancelled()) {
|
||||||
|
expiredTasks.add(entry);
|
||||||
|
} else {
|
||||||
|
activeTasks.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空当前槽位
|
||||||
|
tasks.clear();
|
||||||
|
tasks.addAll(activeTasks);
|
||||||
|
|
||||||
|
// 执行过期任务
|
||||||
|
for (TimerTaskEntry entry : expiredTasks) {
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
entry.task.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 处理异常
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理溢出任务
|
||||||
|
if (overflowWheel != null) {
|
||||||
|
overflowWheel.rotateExpiredTasks();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旋转过期任务
|
||||||
|
private void rotateExpiredTasks() {
|
||||||
|
// 实现溢出任务的处理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止时间轮
|
||||||
|
public void stop() {
|
||||||
|
running.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取统计信息
|
||||||
|
public TimingWheelStats getStats() {
|
||||||
|
TimingWheelStats stats = new TimingWheelStats();
|
||||||
|
stats.tier = tier;
|
||||||
|
stats.slotSize = slotSize;
|
||||||
|
stats.currentSlot = currentSlot.get();
|
||||||
|
stats.totalTasks = Arrays.stream(slots).mapToInt(List::size).sum();
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TimingWheelStats {
|
||||||
|
public int tier;
|
||||||
|
public int slotSize;
|
||||||
|
public int currentSlot;
|
||||||
|
public int totalTasks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分布式时间轮实现
|
||||||
|
|
||||||
|
```java
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.*;
|
||||||
|
|
||||||
|
public class DistributedTimingWheel {
|
||||||
|
private final String nodeId;
|
||||||
|
private final int slotSize;
|
||||||
|
private final long timePerSlot;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final Map<String, TimerTaskEntry> taskMap = new ConcurrentHashMap<>();
|
||||||
|
private final TimerWheel[] wheels;
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public DistributedTimingWheel(String nodeId, int slotSize, long timePerSlot,
|
||||||
|
ExecutorService executor) {
|
||||||
|
this.nodeId = nodeId;
|
||||||
|
this.slotSize = slotSize;
|
||||||
|
this.timePerSlot = timePerSlot;
|
||||||
|
this.executor = executor;
|
||||||
|
this.wheels = new TimerWheel[3]; // 3层时间轮
|
||||||
|
|
||||||
|
// 初始化时间轮
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
long slotTime = timePerSlot * (long) Math.pow(slotSize, i);
|
||||||
|
wheels[i] = new TimerWheel(i, slotSize, slotTime, executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分布式任务
|
||||||
|
public void addDistributedTask(String taskId, TimerTask task, long delay) {
|
||||||
|
String taskKey = nodeId + ":" + taskId;
|
||||||
|
TimerTaskEntry entry = new TimerTaskEntry(task, System.currentTimeMillis() + delay);
|
||||||
|
taskMap.put(taskKey, entry);
|
||||||
|
|
||||||
|
// 添加到最合适的时间轮
|
||||||
|
for (int i = wheels.length - 1; i >= 0; i--) {
|
||||||
|
if (delay <= wheels[i].getWheelTimeout()) {
|
||||||
|
wheels[i].addTask(entry);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消任务
|
||||||
|
public boolean cancelTask(String taskId) {
|
||||||
|
String taskKey = nodeId + ":" + taskId;
|
||||||
|
TimerTaskEntry entry = taskMap.remove(taskKey);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.cancel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动时间轮
|
||||||
|
public void start() {
|
||||||
|
if (running.compareAndSet(false, true)) {
|
||||||
|
for (TimerWheel wheel : wheels) {
|
||||||
|
wheel.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止时间轮
|
||||||
|
public void stop() {
|
||||||
|
if (running.compareAndSet(true, false)) {
|
||||||
|
for (TimerWheel wheel : wheels) {
|
||||||
|
wheel.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部时间轮类
|
||||||
|
private class TimerWheel {
|
||||||
|
private final int tier;
|
||||||
|
private final int slotSize;
|
||||||
|
private final long timePerSlot;
|
||||||
|
private final long wheelTimeout;
|
||||||
|
private final List<TimerTaskEntry>[] slots;
|
||||||
|
private final AtomicInteger currentSlot;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public TimerWheel(int tier, int slotSize, long timePerSlot,
|
||||||
|
ExecutorService executor) {
|
||||||
|
this.tier = tier;
|
||||||
|
this.slotSize = slotSize;
|
||||||
|
this.timePerSlot = timePerSlot;
|
||||||
|
this.wheelTimeout = slotSize * timePerSlot;
|
||||||
|
this.executor = executor;
|
||||||
|
this.slots = (List<TimerTaskEntry>[]) new List[slotSize];
|
||||||
|
this.currentSlot = new AtomicInteger(0);
|
||||||
|
|
||||||
|
for (int i = 0; i < slotSize; i++) {
|
||||||
|
slots[i] = new CopyOnWriteArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTask(TimerTaskEntry entry) {
|
||||||
|
long remainingDelay = entry.getExpiration() - System.currentTimeMillis();
|
||||||
|
if (remainingDelay <= timePerSlot) {
|
||||||
|
int targetSlot = (int)((entry.getExpiration() / timePerSlot) % slotSize);
|
||||||
|
slots[targetSlot].add(entry);
|
||||||
|
} else {
|
||||||
|
// 转发到更高层级的时间轮
|
||||||
|
if (tier < wheels.length - 1) {
|
||||||
|
wheels[tier + 1].addTask(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
if (running.compareAndSet(false, true)) {
|
||||||
|
new Thread(this::rotate).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
running.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rotate() {
|
||||||
|
while (running.get()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(timePerSlot);
|
||||||
|
int slot = currentSlot.getAndIncrement() % slotSize;
|
||||||
|
|
||||||
|
List<TimerTaskEntry> tasks = slots[slot];
|
||||||
|
if (!tasks.isEmpty()) {
|
||||||
|
List<TimerTaskEntry> expiredTasks = new ArrayList<>();
|
||||||
|
List<TimerTaskEntry> activeTasks = new ArrayList<>();
|
||||||
|
|
||||||
|
for (TimerTaskEntry entry : tasks) {
|
||||||
|
if (entry.isExpired()) {
|
||||||
|
expiredTasks.add(entry);
|
||||||
|
} else {
|
||||||
|
activeTasks.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.clear();
|
||||||
|
tasks.addAll(activeTasks);
|
||||||
|
|
||||||
|
for (TimerTaskEntry entry : expiredTasks) {
|
||||||
|
executor.submit(() -> {
|
||||||
|
try {
|
||||||
|
entry.getTask().execute();
|
||||||
|
// 从任务映射中移除
|
||||||
|
taskMap.remove(entry.getTaskId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 处理异常
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWheelTimeout() {
|
||||||
|
return wheelTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 时间复杂度分析
|
||||||
|
|
||||||
|
### 操作时间复杂度
|
||||||
|
|
||||||
|
| 操作 | 时间复杂度 | 说明 |
|
||||||
|
|------|------------|------|
|
||||||
|
| 添加任务 | O(1) | 直接计算槽位并添加 |
|
||||||
|
| 取消任务 | O(1) | 标记任务为取消状态 |
|
||||||
|
| 执行任务 | O(1) | 指针到达槽位时执行 |
|
||||||
|
| 旋转时间轮 | O(1) | 移动指针,检查槽位 |
|
||||||
|
|
||||||
|
### 空间复杂度
|
||||||
|
|
||||||
|
- O(n) - 存储 n 个任务
|
||||||
|
- 时间轮槽位使用固定空间 O(slotSize)
|
||||||
|
|
||||||
|
### 性能特点
|
||||||
|
|
||||||
|
1. **时间轮旋转**:每次旋转 O(1) 时间复杂度
|
||||||
|
2. **任务执行**:平均每个任务执行 O(1) 时间
|
||||||
|
3. **内存使用**:固定大小的时间轮,内存可控
|
||||||
|
4. **并发性能**:使用 CopyOnWriteArrayList 保证线程安全
|
||||||
|
|
||||||
|
## 实际应用场景
|
||||||
|
|
||||||
|
### 1. 分布式任务调度
|
||||||
|
- **定时任务**:周期性任务执行
|
||||||
|
- **延迟任务**:延迟执行的任务
|
||||||
|
- **任务重试**:失败任务的重试机制
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 分布式任务调度示例
|
||||||
|
public class TaskScheduler {
|
||||||
|
private final DistributedTimingWheel timingWheel;
|
||||||
|
private final ExecutorService executor;
|
||||||
|
|
||||||
|
public void scheduleTask(String taskId, Runnable task, long delay) {
|
||||||
|
timingWheel.addDistributedTask(taskId, () -> {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 任务执行失败,可以重试
|
||||||
|
scheduleRetry(taskId, task, delay);
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleRetry(String taskId, Runnable task, long delay) {
|
||||||
|
// 实现重试逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 消息队列超时处理
|
||||||
|
- **消息超时**:处理超时的消息
|
||||||
|
- **死信队列**:移除超时未消费的消息
|
||||||
|
- **重试机制**:消息重试调度
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 消息队列超时处理
|
||||||
|
public class MessageQueueTimeoutHandler {
|
||||||
|
private final EnhancedTimingWheel timingWheel;
|
||||||
|
|
||||||
|
public void handleMessageTimeout(String messageId, long timeout) {
|
||||||
|
timingWheel.addTask(() -> {
|
||||||
|
// 处理超时消息
|
||||||
|
handleTimeoutMessage(messageId);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTimeoutMessage(String messageId) {
|
||||||
|
// 将消息移到死信队列
|
||||||
|
System.out.println("Message " + messageId + " timed out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 缓存过期清理
|
||||||
|
- **缓存过期**:清理过期的缓存数据
|
||||||
|
- **惰性删除**:被动删除过期数据
|
||||||
|
- **定时删除**:主动删除过期数据
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 缓存过期清理
|
||||||
|
public class CacheManager {
|
||||||
|
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
|
||||||
|
private final EnhancedTimingWheel timingWheel;
|
||||||
|
|
||||||
|
public void put(String key, Object value, long ttl) {
|
||||||
|
CacheEntry entry = new CacheEntry(value, System.currentTimeMillis() + ttl);
|
||||||
|
cache.put(key, entry);
|
||||||
|
|
||||||
|
// 添加到时间轮
|
||||||
|
timingWheel.addTask(() -> {
|
||||||
|
cache.remove(key);
|
||||||
|
}, ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CacheEntry {
|
||||||
|
private final Object value;
|
||||||
|
private final long expiration;
|
||||||
|
|
||||||
|
public CacheEntry(Object value, long expiration) {
|
||||||
|
this.value = value;
|
||||||
|
this.expiration = expiration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 连接池管理
|
||||||
|
- **连接超时**:检测和关闭超时连接
|
||||||
|
- **空闲连接**:清理长时间空闲的连接
|
||||||
|
- **连接保活**:定期检查连接状态
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 连接池管理
|
||||||
|
public class ConnectionPool {
|
||||||
|
private final Map<String, Connection> connections = new ConcurrentHashMap<>();
|
||||||
|
private final EnhancedTimingWheel timingWheel;
|
||||||
|
|
||||||
|
public void addConnection(String id, Connection connection, long timeout) {
|
||||||
|
connections.put(id, connection);
|
||||||
|
|
||||||
|
// 添加超时检测
|
||||||
|
timingWheel.addTask(() -> {
|
||||||
|
Connection conn = connections.get(id);
|
||||||
|
if (conn != null && conn.isIdle()) {
|
||||||
|
conn.close();
|
||||||
|
connections.remove(id);
|
||||||
|
}
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. API 限流
|
||||||
|
- **限流窗口**:控制 API 调用频率
|
||||||
|
- **时间窗口**:基于时间窗口的限流
|
||||||
|
- **滑动窗口**:实现滑动限流算法
|
||||||
|
|
||||||
|
```java
|
||||||
|
// API 限流实现
|
||||||
|
public class RateLimiter {
|
||||||
|
private final EnhancedTimingWheel timingWheel;
|
||||||
|
private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public boolean allowRequest(String userId, long windowSize, int limit) {
|
||||||
|
String counterKey = userId + ":" + System.currentTimeMillis() / windowSize;
|
||||||
|
AtomicInteger counter = counters.computeIfAbsent(counterKey, k -> new AtomicInteger(0));
|
||||||
|
|
||||||
|
timingWheel.addTask(() -> counters.remove(counterKey), windowSize);
|
||||||
|
|
||||||
|
return counter.incrementAndGet() <= limit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与其他调度方式的对比
|
||||||
|
|
||||||
|
| 调度方式 | 时间复杂度 | 内存使用 | 适用场景 | 优点 | 缺点 |
|
||||||
|
|----------|------------|----------|----------|------|------|
|
||||||
|
| 时间轮 | O(1) | O(n) | 大量延迟任务 | 高效、内存可控 | 实现复杂 |
|
||||||
|
| 优先队列 | O(log n) | O(n) | 任务数量较少 | 实现简单 | 性能较差 |
|
||||||
|
| 线程池 | O(1) | O(n) | 短期任务 | 使用简单 | 资源消耗大 |
|
||||||
|
| 定时器 | O(1) | O(1) | 少量任务 | API 简单 | 不适合大量任务 |
|
||||||
|
| NIO | O(1) | O(n) | 高并发 | 性能极高 | 实现复杂 |
|
||||||
|
|
||||||
|
### 时间轮的优势
|
||||||
|
|
||||||
|
1. **高效性**:O(1) 时间复杂度的任务调度
|
||||||
|
2. **内存可控**:固定大小的时间轮结构
|
||||||
|
3. **实时性**:精确的任务执行时间控制
|
||||||
|
4. **扩展性**:支持多层级处理复杂任务
|
||||||
|
5. **并发友好**:线程安全的实现
|
||||||
|
|
||||||
|
### 时间轮的劣势
|
||||||
|
|
||||||
|
1. **实现复杂**:相比其他方式实现较复杂
|
||||||
|
2. **精度限制**:受时间片大小限制
|
||||||
|
3. **内存开销**:需要维护多个时间轮
|
||||||
|
4. **任务取消**:取消任务需要额外处理
|
||||||
|
|
||||||
|
## 常见面试问题
|
||||||
|
|
||||||
|
### Q1: 时间轮和优先队列有什么区别?为什么选择时间轮?
|
||||||
|
**答**:
|
||||||
|
**主要区别**:
|
||||||
|
1. **时间复杂度**:时间轮 O(1),优先队列 O(log n)
|
||||||
|
2. **内存使用**:时间轮固定大小,优先队列动态增长
|
||||||
|
3. **任务处理**:时间轮轮询,优先队列堆排序
|
||||||
|
4. **实现复杂度**:时间轮较复杂,优先队列简单
|
||||||
|
|
||||||
|
**选择时间轮的原因**:
|
||||||
|
- 任务数量大时性能更好
|
||||||
|
- 内存使用更可控
|
||||||
|
- 适合处理大量延迟任务
|
||||||
|
- 支持高并发场景
|
||||||
|
|
||||||
|
### Q2: 如何处理时间轮的精度问题?
|
||||||
|
**答**:
|
||||||
|
**精度优化策略**:
|
||||||
|
1. **调整时间片**:根据需求选择合适的时间片大小
|
||||||
|
2. **多层级时间轮**:小时间片处理短期任务,大时间片处理长期任务
|
||||||
|
3. **实时校准**:定期校准系统时间
|
||||||
|
4. **任务优先级**:高优先级任务单独处理
|
||||||
|
5. **补偿机制**:记录实际执行时间进行补偿
|
||||||
|
|
||||||
|
### Q3: 如何处理时间轮中的任务取消?
|
||||||
|
**答**:
|
||||||
|
**取消机制实现**:
|
||||||
|
1. **标记机制**:在任务条目中设置取消标志
|
||||||
|
2. **垃圾回收**:定期清理已取消的任务
|
||||||
|
3. **主动查询**:提供取消任务的接口
|
||||||
|
4. **批量清理**:在时间轮旋转时批量清理
|
||||||
|
5. **超时自动清理**:长时间未执行的任务自动清理
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 任务取消实现
|
||||||
|
public boolean cancelTask(String taskId) {
|
||||||
|
String taskKey = nodeId + ":" + taskId;
|
||||||
|
TimerTaskEntry entry = taskMap.get(taskKey);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.cancel();
|
||||||
|
// 可以选择立即从任务映射中移除
|
||||||
|
taskMap.remove(taskKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 时间轮在分布式环境中如何保证一致性?
|
||||||
|
**答**:
|
||||||
|
**分布式一致性方案**:
|
||||||
|
1. **任务迁移**:任务在节点间迁移时保持一致性
|
||||||
|
2. **时间同步**:所有节点使用同步时钟
|
||||||
|
3. **任务分发**:根据任务类型和节点负载分发任务
|
||||||
|
4. **故障恢复**:节点故障时任务重新分发
|
||||||
|
5. **共识机制**:使用一致性协议保证任务不丢失
|
||||||
|
|
||||||
|
### Q5: 如何优化时间轮的性能?
|
||||||
|
**答**:
|
||||||
|
**性能优化策略**:
|
||||||
|
1. **时间轮层级**:合理设置时间轮层级数量
|
||||||
|
2. **槽位数量**:根据任务分布调整槽位数量
|
||||||
|
3. **任务批处理**:批量处理相似任务
|
||||||
|
4. **线程优化**:使用多线程处理任务执行
|
||||||
|
5. **内存优化**:使用更高效的数据结构
|
||||||
|
6. **缓存优化**:优化热点数据的访问
|
||||||
|
|
||||||
|
### Q6: 时间轮如何处理大量任务?
|
||||||
|
**答**:
|
||||||
|
**大量任务处理方案**:
|
||||||
|
1. **任务分片**:将任务分散到不同时间轮
|
||||||
|
2. **分层处理**:短期任务和长期任务分别处理
|
||||||
|
3. **任务合并**:相似任务合并执行
|
||||||
|
4. **负载均衡**:多个时间轮实例并行工作
|
||||||
|
5. **资源管理**:动态调整时间轮资源分配
|
||||||
|
|
||||||
|
### Q7: 时间轮和事件驱动模式有什么关系?
|
||||||
|
**答**:
|
||||||
|
**关系说明**:
|
||||||
|
1. **结合使用**:时间轮可以作为事件驱动系统的一部分
|
||||||
|
2. **触发机制**:时间轮可以触发事件的执行
|
||||||
|
3. **定时事件**:时间轮专门处理定时事件
|
||||||
|
4. **互补关系**:事件驱动处理即时事件,时间轮处理延迟事件
|
||||||
|
5. **性能协同**:两者结合可以提高系统整体性能
|
||||||
|
|
||||||
|
### Q8: 如何实现任务的优先级处理?
|
||||||
|
**答**:
|
||||||
|
**优先级实现方案**:
|
||||||
|
1. **多个时间轮**:不同优先级使用不同时间轮
|
||||||
|
2. **优先队列**:在时间轮中使用优先队列
|
||||||
|
3. **任务标记**:高优先级任务特殊标记
|
||||||
|
4. **抢占执行**:高优先级任务可以抢占低优先级任务
|
||||||
|
5. **动态调整**:根据任务优先级动态调整执行顺序
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 带优先级的时间轮
|
||||||
|
public class PriorityTimingWheel {
|
||||||
|
private final Map<Integer, EnhancedTimingWheel> priorityWheels = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public void addTask(TimerTask task, long delay, int priority) {
|
||||||
|
EnhancedTimingWheel wheel = priorityWheels.computeIfAbsent(
|
||||||
|
priority,
|
||||||
|
p -> new EnhancedTimingWheel(3, 8, 100, executor)
|
||||||
|
);
|
||||||
|
wheel.addTask(task, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q9: 时间轮的内存如何管理?
|
||||||
|
**答**:
|
||||||
|
**内存管理策略**:
|
||||||
|
1. **固定大小**:时间轮槽位数量固定
|
||||||
|
2. **对象复用**:复用任务对象减少 GC
|
||||||
|
3. **分代管理**:不同生命周期对象分别管理
|
||||||
|
4. **内存监控**:监控内存使用情况
|
||||||
|
5. **自动扩容**:在必要时自动扩展时间轮
|
||||||
|
|
||||||
|
### Q10: 时间轮的适用场景和限制是什么?
|
||||||
|
**答**:
|
||||||
|
**适用场景**:
|
||||||
|
1. **大量延迟任务**:需要处理大量延迟任务
|
||||||
|
2. **高并发环境**:需要高并发处理能力
|
||||||
|
3. **内存受限环境**:内存使用需要可控
|
||||||
|
4. **实时性要求**:需要精确的时间控制
|
||||||
|
5. **分布式系统**:需要分布式任务调度
|
||||||
|
|
||||||
|
**限制**:
|
||||||
|
1. **精度限制**:受时间片大小限制
|
||||||
|
2. **实现复杂**:相比其他实现较复杂
|
||||||
|
3. **单机限制**:单机处理能力有限
|
||||||
|
4. **任务取消**:取消任务处理复杂
|
||||||
|
5. **时间同步**:分布式环境需要时间同步
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
时间轮算法是一种高效的时间调度算法,特别适合处理大量延迟任务和周期性任务。通过合理的设计和优化,可以在各种场景下实现高性能的任务调度。理解时间轮的原理和实现方式,对于设计和实现高性能的分布式系统具有重要意义。
|
||||||
Reference in New Issue
Block a user