多层时间轮实现延迟消息

26 阅读3分钟

单层时间轮的弊端

对于单层时间轮来说,如果要拉长延迟的时间,要么增加时间轮中槽的数量,要么增大前进指针的时间间隔。但是这两种方式都有弊端

  1. 增加槽的数量:占用内存较高
  2. 增大前进指针的时间间隔:延迟时间不精确 为了解决上面的问题,我们采用钟表中的秒针、分针、时针一样,通过多层时间轮来挂载延迟任务,这样就可以用少量的内存实现超长的延迟时间。

多层时间轮的结构

时间槽:每个时间轮包含若干时间槽,每个槽代表一个时间片段。 时间轮:多层时间轮通常包含多个时间轮,每一层的精度和范围不同。例如,第一层(细粒度)可以管理毫秒级任务,第二层(中等粒度)可以管理秒级任务,第三层(粗粒度)可以管理分钟级任务。 指针:每个时间轮都有一个指针,用于指向当前时间槽,随着时间的推移指针向前移动。 任务链表:每个时间槽中包含一个任务链表,用于存储定时任务。

代码实战

任务

public class RecursiveTimeWheel {
    private static final int MS_PER_TICK = 10;
    private static final int[] WHEEL_SIZES = {100, 60, 60}; // 毫秒、秒、分钟
    private static final String[] WHEEL_NAMES = {"毫秒", "秒", "分钟"};

    private final List<TimeWheel> wheels = new ArrayList<>();
    private final ExecutorService executor = Executors.newFixedThreadPool(4);
    private final AtomicInteger taskCounter = new AtomicInteger(0);

    public RecursiveTimeWheel() {
        // 递归创建多级时间轮
        TimeWheel prevWheel = null;
        for (int i = 0; i < WHEEL_SIZES.length; i++) {
            final int level = i;
            TimeWheel wheel = new TimeWheel(
                    WHEEL_SIZES[i],
                    prevWheel,
                    tasks -> advanceCallback(level, tasks)
            );
            wheels.add(wheel);
            prevWheel = wheel;
        }

        new Thread(this::drive, "TimeWheel-Driver").start();
    }

    public void schedule(long delayMs, Runnable task) {
        long delayTicks = delayMs / MS_PER_TICK;
        if (delayTicks <= 0) {
            executor.execute(task);
            return;
        }

        // 递归添加任务
        addTaskRecursively(delayTicks, task, wheels.size() - 1);

        System.out.printf("Task %d scheduled with delay %dms%n",
                taskCounter.incrementAndGet(), delayMs);
    }

    // 递归添加任务
    private void addTaskRecursively(long remainingTicks, Runnable task, int wheelLevel) {
        if (wheelLevel < 0) {
            executor.execute(task);
            return;
        }

        TimeWheel wheel = wheels.get(wheelLevel);
        int wheelSize = wheel.size;
        long wheelTicks = calculateWheelTicks(wheelLevel);

        if (remainingTicks < wheelTicks || wheelLevel == 0) {
            // 当前是最低级时间轮或者剩余时间可以在当前轮处理
            int ticks = (int) remainingTicks;
            wheel.addTask(ticks, () -> {
                if (wheelLevel > 0) {
                    // 还有下级轮,继续递归
                    addTaskRecursively(remainingTicks - ticks, task, wheelLevel - 1);
                } else {
                    task.run();
                }
            });
        } else {
            // 需要更高级时间轮处理
            int currentWheelTicks = (int) (remainingTicks / wheelTicks);
            long nextRemainingTicks = remainingTicks % wheelTicks;
            // 这里对多级时间轮的理解很关键:如果有余数,则需要对当前任务再包装个任务,当执行到当前层级的任务时,其实是执行了addTaskRecursively方法,而该方法又把元任务根据nextRemainingTicks放置在下一层级的某个预期的槽里,当时间真正到的时候才执行到元任务。以这样的方式实现了任务下放。
            wheel.addTask(currentWheelTicks, () ->
                    addTaskRecursively(nextRemainingTicks, task, wheelLevel)
            );
        }
    }

    // 计算当前级别时间轮的总ticks
    private long calculateWheelTicks(int level) {
        long ticks = 1;
        for (int i = 0; i < level; i++) {
            ticks *= WHEEL_SIZES[i];
        }
        return ticks;
    }

    private void advanceCallback(int level, List<Runnable> tasks) {
        if (!tasks.isEmpty()) {
            String wheelName = WHEEL_NAMES[level];
            if (level == 0) {
                System.out.println("执行 " + tasks.size() + " 个" + wheelName + "级任务");
                tasks.forEach(executor::execute);
            } else {
                System.out.println("将 " + tasks.size() + " 个任务从" + wheelName + "轮降级");
                tasks.forEach(Runnable::run);
            }
        }
    }

    private void drive() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                Thread.sleep(MS_PER_TICK);
                wheels.get(0).advance(); // 驱动最底层时间轮
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private static class TimeWheel {
        private final int size;
        private final TimeWheel overflowWheel;
        private final java.util.function.Consumer<List<Runnable>> onTick;
        private final List<Runnable>[] buckets;
        private int currentTick;

        @SuppressWarnings("unchecked")
        TimeWheel(int size, TimeWheel overflowWheel, java.util.function.Consumer<List<Runnable>> onTick) {
            this.size = size;
            this.overflowWheel = overflowWheel;
            this.onTick = onTick;
            this.buckets = new List[size];
            for (int i = 0; i < size; i++) {
                buckets[i] = new ArrayList<>();
            }
            this.currentTick = 0;
        }

        void addTask(int ticks, Runnable task) {
            int bucket = (currentTick + ticks) % size;
            buckets[bucket].add(task);
        }

        void advance() {
            currentTick = (currentTick + 1) % size;
            List<Runnable> tasks = new ArrayList<>(buckets[currentTick]);
            buckets[currentTick].clear();
            onTick.accept(tasks);

            if (currentTick == 0 && overflowWheel != null) {
                overflowWheel.advance();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        RecursiveTimeWheel timer = new RecursiveTimeWheel();

        // 测试不同级别的定时任务
        long startTime = System.currentTimeMillis();
        timer.schedule(500, () -> System.out.println("500ms任务执行 at: " + (System.currentTimeMillis() - startTime)));
        timer.schedule(1500, () -> System.out.println("1.5s任务执行 at: " + (System.currentTimeMillis() - startTime)));
        timer.schedule(30_500, () -> System.out.println("30.5s任务执行 at: " + (System.currentTimeMillis() - startTime)));
        timer.schedule(90_500, () -> System.out.println("1分30.5秒任务执行 at: " + (System.currentTimeMillis() - startTime)));

        Thread.sleep(120_000);
        System.out.println("测试完成");
    }
}

以上是通过递归的形式优雅地实现了多级时间轮的关键逻辑,有助于读者更深刻地理解多级时间轮的实现方式。