你说这是冷知识?Netty时间轮调度算法原理分析,再不了解你就out啦

一、时间轮介绍

之前公司内部搭建的延迟队列服务有用到时间轮,但是一直没有了解过它的实现原理。

最近有个和支付宝对接的项目,支付宝接口有流量控制,一定的时间内只允许 N 次接口调用,针对一些业务我们需要频繁调用支付宝开放平台接口,如果不对请求做限制,很容易触发流控告警。

为了避免这个问题,我们按照一定延迟规则将任务加载进时间轮内,通过时间轮的调度来实现接口异步调用。

很多开源框架都实现了时间轮算法,这里以 Netty 为例,看下 Netty 中时间轮是怎么实现的。

1.1 快速入门

下面是一个 API 使用例子。

public class WheelTimerSamples {

    private static final HashedWheelTimerInstance INSTANCE = HashedWheelTimerInstance.INSTANCE;

    public static void main(String[] args) throws IOException {

        INSTANCE.getWheelTimer().newTimeout(new PrintTimerTask(), 3, TimeUnit.SECONDS);
        System.in.read();
    }

    static class PrintTimerTask implements TimerTask {
        @Override
        public void run(Timeout timeout) {
            System.out.println("Hello world");
        }
    }

    enum HashedWheelTimerInstance {
        INSTANCE;
        private final HashedWheelTimer wheelTimer;

        HashedWheelTimerInstance() {
            wheelTimer = new HashedWheelTimer(r -> {
                Thread t = new Thread(r);
                t.setUncaughtExceptionHandler((t1, e) -> System.out.println(t1.getName() + e.getMessage()));
                t.setName("-HashedTimerWheelInstance-");
                return t;
            }, 100, TimeUnit.MILLISECONDS, 64);
        }

        public HashedWheelTimer getWheelTimer() {
            return wheelTimer;
        }
    }
}

上面的例子中我们自定义了一个 HashedWheelTimer,然后自定义了一个 TimerTask,将一个任务加载进时间轮,3s 后执行这个任务,怎么样是不是很简单。

在定义时间轮时建议按照业务类型进行区分,将时间轮定义为多个单例对象。

PS:因为时间轮是异步执行的,在任务执行之前 JVM 不能退出,所以 System.in.read(); 这一行代码不能删除。

1.2 原理图解

二、原理分析

2.1 时间轮状态

时间轮有以下三种状态:

  • WORKER_STATE_INIT:初始化状态,此时时间轮内的工作线程还没有开启
  • WORKER_STATE_STARTED:运行状态,时间轮内的工作线程已经开启
  • WORKER_STATE_SHUTDOWN:终止状态,时间轮停止工作

状态转换如下,转换原理会在下面讲到:

2.2 构造函数

    public HashedWheelTimer(
            ThreadFactory threadFactory,
            long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,
            long maxPendingTimeouts) {

        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        if (unit == null) {
            throw new NullPointerException("unit");
        }
        if (tickDuration <= 0) {
            throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
        }
        if (ticksPerWheel <= 0) {
            throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
        }

        // 初始化时间轮数组,时间轮大小为大于等于 ticksPerWheel 的第一个 2 的幂,和 HashMap 类似
        wheel = createWheel(ticksPerWheel);
        // 取模用,用来定位数组中的槽
        mask = wheel.length - 1;

        // 为了保证精度,时间轮内的时间单位为纳秒
        long duration = unit.toNanos(tickDuration);

        // 时间轮内的时钟拨动频率不宜太大也不宜太小
        if (duration >= Long.MAX_VALUE / wheel.length) {
            throw new IllegalArgumentException(String.format(
                    "tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
                    tickDuration, Long.MAX_VALUE / wheel.length));
        }

        if (duration < MILLISECOND_NANOS) {
            logger.warn("Configured tickDuration {} smaller then {}, using 1ms.",
                        tickDuration, MILLISECOND_NANOS);
            this.tickDuration = MILLISECOND_NANOS;
        } else {
            this.tickDuration = duration;
        }

        // 创建工作线程
        workerThread = threadFactory.newThread(worker);

        // 非守护线程且 leakDetection 为 true 时检测内存是否泄漏
        leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null;

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值