我正在参加「掘金·启航计划」
来源:公众号「冒泡的肥皂」
转载请联系授权(微信ID:wxid_yhudfewbgu6c21)
1.延时任务
简单的说就是某个时间点延后多久执行任务。
例如:微波炉加热两分钟关闭。
夏天吹空调的时候想在30分钟后让其自动关闭。
2.一些实现方式
2.1 redis的zSet
2.2 Java的Timer
2.3 java的ScheduledExecutorService
2.4 java的DelayQueue
2.5 时间轮方式都是根据时间差来的
3.时间轮
3.1时间轮的结构
private long tickMs;//一个槽的时间间隔(时间轮最小刻度)
private int wheelSize;//时间轮大小(槽的个数)
private long interval;//一轮的时间跨度
private long currentTime;//当前时间戳
private TaskList[] buckets;//槽 刻度表 数据是个双向链表
private Wheel overflowWheel;//上层时间轮
时间轮像钟表一样,每层都有刻度间隔大小,刻度多少是固定的。
数据存储格式是个拉链(HashMap类似)。
超出的本层处理则上层处理或者本层添加圈数(每次-1 0的时候执行)
3.2任务添加
3.3时间戳推进
3.4 外部执行
推进时间戳运行,;
该槽位的任务清除掉、重新添加任务(时间到期的则会执行的,没到期的降级重新添加到槽位中);
是否有过期的作用则是为了减少时间戳推进的空操作
4.部分代码
//任务添加
public boolean add(TimerTaskEntry entry) {
long expiration = entry.getExpireMs();
if (expiration < tickMs + currentTime) {
// 到期了
return false;
} else if (expiration < currentTime + interval) {
// 扔进当前时间轮的某个槽里,只有时间大于某个槽,才会放进去
long virtualId = (expiration / tickMs);
int index = (int) (virtualId % wheelSize);
TimerTaskList bucket = buckets[index];
bucket.addTask(entry);
// 设置bucket 过期时间
if (bucket.setExpiration(virtualId * tickMs)) {
// 设好过期时间的bucket需要入队
delayQueue.offer(bucket);
return true;
}
} else {
// 当前轮不能满足,需要扔到上一轮
TimeWheel timeWheel = getOverflowWheel();
return timeWheel.add(entry);
}
return false;
}
//时间戳推进
public void advanceLock(long timestamp) {
if (timestamp > currentTime + tickMs) {
currentTime = timestamp - (timestamp % tickMs);
if (overflowWheel != null) {
this.getOverflowWheel().advanceLock(timestamp);
}
}
}
//推进
this.bossThreadPool = Executors.newFixedThreadPool(1);
// 20ms推动一次时间轮运转
this.bossThreadPool.submit(() -> {
for (;;) {
this.advanceClock(20);
}
});
public synchronized void advanceClock(long timeout) {
try {
TimerTaskList bucket = delayQueue.poll(timeout, TimeUnit.MILLISECONDS);
if (bucket != null) {
// 推进时间
timeWheel.advanceLock(bucket.getExpiration());
// 执行过期任务(包含降级)
bucket.clear(this::addTimerTaskEntry);
}
} catch (InterruptedException e) {
log.error("advanceClock error");
}
}
//任务添加
public void addTimerTaskEntry(TimerTaskEntry entry) {
if (!timeWheel.add(entry)) {
// 已经过期了
TimerTask timerTask = entry.getTimerTask();
log.info("=====任务:{} 已到期,准备执行============", timerTask.getDesc());
workerThreadPool.submit(timerTask);
} else {
System.out.println(entry.getTimerTask().getDesc());
}
}