netty和dubbo的hashwheel时间轮定时器原理和源码解析

时间轮定时器

时间轮定时器的论文ppt参考:论文ppt:http://www.cse.wustl.edu/~cdgill/courses/cs6874/TimingWheels.ppt

hash时间轮结构

时间轮结构

基本过程跟时钟类似,秒针每间隔一段时间跳一格,跳到哪格就遍历该格中存储的任务列表判断每个的任务是否到了时间。涉及的两个重要概念:
1.Tick Duration

时间轮跳动的时间间隔,默认是100ms,不是精确的时间。

2.Ticks per Wheel (Wheel Size)
时间轮的格数默认是 512.

时间轮处理过程

理解时间轮的工作原理需要理解这几个过程:

  1. 时间指针的跳动

  2. 指针跳动对应到时间轮上桶的位置

  3. 桶内部如何增加移除任务

  4. 桶内部任务如何进行超时判断

  5. 任务需要等待多少圈数及圈数的维护

Worker & WorkerThread

时间轮的核心逻辑都在workerThread的Worker中执行, 创建timer时创建worker线程执行worker任务,死循环。包括:

  1. 时间指针的跳动
    1. waitForNextTick
  2. 指针跳动对应到时间轮上桶的位置
    1. int stopIndex = (int) (ticks & mask);
  3. 任务需要等待多少圈数及圈数的维护
    1.

private final class Worker implements Runnable {
        private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();

        private long tick;

        @Override
        public void run() {
            // Initialize the startTime.
            startTime = System.nanoTime();
            if (startTime == 0) {
                // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
                startTime = 1;
            }

            // Notify the other threads waiting for the initialization at start().
            startTimeInitialized.countDown();

            do {
                 //计算下次跳动的起始时间,为当前桶任务最大的截止时间
                final long deadline = waitForNextTick();
                if (deadline > 0) {
                    int idx = (int) (tick & mask);
                    processCancelledTasks();
                    HashedWheelBucket bucket =
                            wheel[idx];
                    transferTimeoutsToBuckets();
                    bucket.expireTimeouts(deadline);
                    tick++;
                }
            } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
			//timer被stop了
            // Fill the unprocessedTimeouts so we can return them from stop() method.
            for (HashedWheelBucket bucket: wheel) {
                bucket.clearTimeouts(unprocessedTimeouts);
            }
            for (;;) {
                HashedWheelTimeout timeout = timeouts.poll();
                if (timeout == null) {
                    break;
                }
                if (!timeout.isCancelled()) {
                    unprocessedTimeouts.add(timeout);
                }
            }
            processCancelledTasks();
        }
    
    
		//从timeouts队列添加到时间轮的桶中,定位到桶,然后插入到链表
        private void transferTimeoutsToBuckets() {
            // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
            // adds new timeouts in a loop.
            for (int i = 0; i < 100000; i++) {
                HashedWheelTimeout timeout = timeouts.poll();
                if (timeout == null) {
                    // all processed
                    break;
                }
                if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
                    // Was cancelled in the meantime.
                    continue;
                }
				//根据超时时间计算需要经过多少轮过期
                long calculated = timeout.deadline / tickDuration;
                timeout.remainingRounds = (calculated - tick) / wheel.length;

                final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
                //定位到桶的index,ticks mod 时间轮桶个数 mask=buckets.size-1
                int stopIndex = (int) (ticks & mask);

                HashedWheelBucket bucket = wheel[stopIndex];
                bucket.addTimeout(timeout);
            }
        }

        private void processCancelledTasks() {
            for (;;) {
                HashedWheelTimeout timeout = cancelledTimeouts.poll();
                if (timeout == null) {
                    // all processed
                    break;
                }
                try {
                    timeout.remove();
                } catch (Throwable t) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("An exception was thrown while process a cancellation task", t);
                    }
                }
            }
        }

        /**
         * calculate goal nanoTime from startTime and current tick number,
         * then wait until that goal has been reached.
         * @return Long.MIN_VALUE if received a shutdown request,
         * current time otherwise (with Long.MIN_VALUE changed by +1)
         等待下次跳动指针的时间,时间间隔
         */
        private long waitForNextTick() {
           // 计算下次跳动的时间,为当前桶任务最大的截止时间
            long deadline = tickDuration * (tick + 1);

            for (;;) {
                final long currentTime = System.nanoTime() - startTime;
                // (deadline-currenttime)ms + 1ms
                long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
				//到时间了返回,tick+1
                if (sleepTimeMs <= 0) {
                    if (currentTime == Long.MIN_VALUE) {
                        return -Long.MAX_VALUE;
                    } else {
                        //curenttime>=deadline
                        return currentTime;
                    }
                }

                // Check if we run on windows, as if thats the case we will need
                // to round the sleepTime as workaround for a bug that only affect
                // the JVM if it runs on windows.
                //
                // See https://github.com/netty/netty/issues/356
                if (PlatformDependent.isWindows()) {
                    sleepTimeMs = sleepTimeMs / 10 * 10;
                }

                try {
                    //没到时间睡眠一会
                    Thread.sleep(sleepTimeMs);
                } catch (InterruptedException ignored) {
                    if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
                        return Long.MIN_VALUE;
                    }
                }
            }
        }

        public Set<Timeout> unprocessedTimeouts() {
            return Collections.unmodifiableSet(unprocessedTimeouts);
        }
    }

HashedWheelBucket && Timeout

Timeout是任务的抽象,bucket是任务的容器。
HashedWheelBucket 类似链表,保存每个刻度的任务,expireTimeouts方法移除过期任务列表

  1. 桶内部如何增加移除任务
  2. 桶内部任务如何进行超时判断操作
    1. 任务的轮数维护也在这里处理

两个过程在桶内部实现,见expireTimeouts代码

public void expireTimeouts(long deadline) {
    HashedWheelTimeout timeout = head;

    // process all timeouts
    while (timeout != null) {
        HashedWheelTimeout next = timeout.next;
        //如果任务是到期的任务,直接移除,remaininrounds指任务还需要等几轮才到期
        if (timeout.remainingRounds <= 0) {
            next = remove(timeout);
            if (timeout.deadline <= deadline) {
                timeout.expire();
            } else {
                // The timeout was placed into a wrong slot. This should never happen.
                throw new IllegalStateException(String.format(
                        "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
            }
        } else if (timeout.isCancelled()) {
            next = remove(timeout);
        } else {
            //每轮经过一轮,等待轮数减1
            timeout.remainingRounds --;
        }
        timeout = next;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值