延时任务时间轮的简单说明

180 阅读2分钟

我正在参加「掘金·启航计划」

来源:公众号「冒泡的肥皂

转载请联系授权(微信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());
		}
	}