How Dubbo Implements Efficient Timers with a Hashed Timing Wheel

This article explains the need for high‑performance scheduling in Java, introduces the hashed timing wheel algorithm, and details Dubbo's implementation—including core interfaces, data structures, worker logic, and practical use cases such as retries and heartbeat handling.

JavaEdge
JavaEdge
JavaEdge
How Dubbo Implements Efficient Timers with a Hashed Timing Wheel

Why a specialized timer is needed

JDK's java.util.Timer and DelayQueue use a binary heap, giving O(log n) insertion and removal. When millions of tasks are scheduled, this overhead becomes unacceptable. A hashed timing wheel provides O(1) insertion, cancellation and expiration by mapping future timestamps to slots in a circular buffer.

Time‑wheel model

A wheel is a ring of N slots, where N is a power of two. Each slot holds a doubly‑linked list of HashedWheelTimeout objects. A dedicated worker thread advances a pointer ( tick) every tickDuration nanoseconds. When the pointer lands on a slot, the worker iterates the list: if remainingRounds == 0 the associated TimerTask is executed; otherwise remainingRounds is decremented.

Applicable scenarios

Fault recovery

Traffic control

Scheduling algorithms

Managing packet lifecycles in networks

Dubbo time‑wheel structure

Dubbo implements its timer in the org.apache.dubbo.common.timer package. The core interfaces are:

TimerTask : user‑defined task with a single method void run(Timeout timeout).

Timeout : handle returned by Timer.newTimeout(); provides isCancelled(), isExpired(), cancel(), etc.

Timer : defines

Timeout newTimeout(TimerTask task, long delay, TimeUnit unit)

and void stop().

HashedWheelTimeout

Implements Timeout and acts as a node in the wheel’s doubly‑linked list. prev / next: links for traversal (accessed only by the worker thread, no synchronization required). task: the associated TimerTask. deadline: calculated as currentTime + delay - startTime (nanoseconds). state: managed with AtomicIntegerFieldUpdater; possible states are INIT, CANCELLED, EXPIRED. remainingRounds: number of full wheel rotations left before the timeout becomes eligible.

HashedWheelBucket

Represents a single slot and stores HashedWheelTimeout objects in a doubly‑linked list (head and tail). Key operations:

addTimeout(HashedWheelTimeout timeout)
removeTimeout(HashedWheelTimeout timeout)
expireTimeouts(long deadline)

– iterates the list, executes tasks whose remainingRounds == 0, and decrements the counter for the others.

HashedWheelTimer core fields

workerState

: atomic state of the worker (INIT, STARTED, SHUTDOWN). startTime: timestamp when the wheel started; all deadlines are relative to this value. wheel: array of HashedWheelBucket slots; size is the nearest power‑of‑two greater than the configured number of ticks. timeouts and cancelledTimeouts: queues that buffer newly submitted tasks and tasks cancelled before they reach a bucket. tick: monotonically increasing counter used by the worker thread. mask = wheel.length - 1: enables fast slot index calculation via (tick & mask). tickDuration: duration of one tick in nanoseconds (e.g., 100 ms). pendingTimeouts: total number of tasks still pending execution. workerThread and worker: the dedicated thread and its Runnable that drive the wheel.

Creating and scheduling a task

Calling Timer.newTimeout(task, delay, unit) creates a HashedWheelTimeout and enqueues it into timeouts. If the timer has not been started, the following steps are performed:

Determine startTime = System.nanoTime() (or the first tick time).

Launch workerThread which runs HashedWheelTimer$Worker.run().

When the worker processes a tick, each pending timeout is transferred to its target bucket:

long calculated = timeout.deadline - startTime;
long ticks = calculated / tickDuration;
int stopIndex = (int)(ticks & mask);
timeout.remainingRounds = ticks / wheel.length;
wheel[stopIndex].addTimeout(timeout);

Worker execution flow ( HashedWheelTimer$Worker.run() )

Advance tick and compute the deadline for the current tick.

Remove and discard all entries in cancelledTimeouts.

Transfer pending entries from timeouts to their target buckets.

Fetch the bucket for tick & mask and invoke expireTimeouts(deadline).

If workerState indicates shutdown, drain remaining timeouts via unprocessedTimeouts and clear them.

Repeat until the timer is stopped.

Practical usage in Dubbo

Failure retry (e.g., registration or subscription retries).

Periodic tasks such as heartbeat sending, request timeout handling, and reconnection logic.

Reference

https://zhuanlan.zhihu.com/p/32906730

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaSchedulerDubbotiming wheelHashedWheelTimer
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.