JAVA数据结构与算法之延迟队列(二):HashedWheelTimer

深入解析Netty中的HashedWheelTimer类,基于时间轮算法实现高效定时任务调度。探讨其成员变量、构造函数、核心逻辑及关键方法,如newTimeout、waitForNextTick等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashedWheelTimer

NettyHashedWheelTimer类基于时间轮算法实现

主要成员变量
private final Worker worker = new Worker(); // 任务类,继承Runnable接口
private final Thread workerThread; // 工作线程,用来执行worker任务
private volatile int workerState; // 任务状态,cas自旋和volatile保证原子操作,0-初始化中, 1-开启, 2-关闭
private final long tickDuration; // wheel上每个时间片执行的时间间隔
private final HashedWheelBucket[] wheel; // 时间轮,HashedWheelBucket内部由双向队列实现
private final int mask;
private final CountDownLatch startTimeInitialized = new CountDownLatch(1); // 用于等待任务启动完成
private final Queue<HashedWheelTimeout> timeouts = PlatformDependent.newMpscQueue(); // 添加的任务
private final Queue<HashedWheelTimeout> cancelledTimeouts = PlatformDependent.newMpscQueue(); // 取消的任务
private final AtomicLong pendingTimeouts = new AtomicLong(0); // 等待的任务数
private final long maxPendingTimeouts; // 最大等待任务数

private volatile long startTime; // 延迟任务的开始时间
构造函数
	public HashedWheelTimer(
            ThreadFactory threadFactory,
            long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,
            long maxPendingTimeouts) {
        // 线程工厂,用来创建worker线程
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        // 每个tick运行的时间间隔单位
        if (unit == null) {
            throw new NullPointerException("unit");
        }
        // 每个tick运行的时间间隔
        if (tickDuration <= 0) {
            throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
        }
        // wheel数组即时间轮的大小
        if (ticksPerWheel <= 0) {
            throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
        }
        // 将wheel数组大小标准化为2的n次方,同时初始化wheel数组
        wheel = createWheel(ticksPerWheel);
        // 掩码
        mask = wheel.length - 1;

        // Convert tickDuration to nanos.
        this.tickDuration = unit.toNanos(tickDuration);

        // 防止溢出
        if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
            throw new IllegalArgumentException(String.format(
                    "tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
                    tickDuration, Long.MAX_VALUE / wheel.length));
        }
        // 创建工作线程
        workerThread = threadFactory.newThread(worker);
        // 是否泄漏监控
        leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null;
        // 最大等待任务数
        this.maxPendingTimeouts = maxPendingTimeouts;
        // 警告,限制HashedWheelTimer实例数为INSTANCE_COUNT_LIMIT个即64个,预防过多的线程影响性能
        if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
            WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
            reportTooManyInstances();
        }
    }
创建时间轮函数createWheel()
	private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
        if (ticksPerWheel <= 0) {
            throw new IllegalArgumentException(
                    "ticksPerWheel must be greater than 0: " + ticksPerWheel);
        }
        if (ticksPerWheel > 1073741824) {
            throw new IllegalArgumentException(
                    "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel);
        }
        // 严格控制时间轮的大小为2的n次方
        ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);
        // 初始化时间轮数组并赋值
        HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
        for (int i = 0; i < wheel.length; i ++) {
            wheel[i] = new HashedWheelBucket();
        }
        return wheel;
    }
    // 大于指定值的最小2的n次方
    private static int normalizeTicksPerWheel(int ticksPerWheel) {
        int normalizedTicksPerWheel = 1;
        while (normalizedTicksPerWheel < ticksPerWheel) {
            normalizedTicksPerWheel <<= 1;
        }
        return normalizedTicksPerWheel;
    }
添加任务函数newTimeout()

添加任务时,如果worker线程没启动则启动,同时向timeouts队列中生产timeout任务

	public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
        if (task == null) {
            throw new NullPointerException("task");
        }
        if (unit == null) {
            throw new NullPointerException("unit");
        }
        // 等待任务数+1
        long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
        // 超过最大等待任务数,拒绝加入
        if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
            pendingTimeouts.decrementAndGet();
            throw new RejectedExecutionException("Number of pending timeouts ("
                + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
                + "timeouts (" + maxPendingTimeouts + ")");
        }
        // 开始任务
        start();
        // 延迟执行的时间
        long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
        if (delay > 0 && deadline < 0) {
            deadline = Long.MAX_VALUE;
        }
        // 添加到timeouts队列,这是一个MPSC队列,MPSC队列是多生产者单消费者无锁的并发队列,worker线程会对队列进行消费
        HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
        timeouts.add(timeout);
        return timeout;
    }

这里看下start()函数如何保证启动一次

	public void start() {
		// 这里使用CAS算法确保原子操作,因此多个线程并发执行只有一次会执行成功
        switch (WORKER_STATE_UPDATER.get(this)) {
            case WORKER_STATE_INIT:
                if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {
                    workerThread.start();
                }
                break;
            case WORKER_STATE_STARTED:
                break;
            case WORKER_STATE_SHUTDOWN:
                throw new IllegalStateException("cannot be started once stopped");
            default:
                throw new Error("Invalid WorkerState");
        }

        // 等待worker线程初始化一些启动工作
        while (startTime == 0) {
            try {
                startTimeInitialized.await();
            } catch (InterruptedException ignore) {
                // Ignore - it will be ready very soon.
            }
        }
    }

核心逻辑在Worker内部类中

private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
private long tick; // tick计数
run方法

核心逻辑

	public void run() {
        // 初始化startTime变量
        startTime = System.nanoTime();
        if (startTime == 0) {
            // 初始化失败时的值
            startTime = 1;
        }
        // 通知其他线程继续执行
        startTimeInitialized.countDown();
        do {
        	// 等待直到当前tick时间点到
            final long deadline = waitForNextTick();
            // 当前tick时间点到了
            if (deadline > 0) {
          		// 对应时间轮中的索引
                int idx = (int) (tick & mask);
                // 处理取消的任务
                processCancelledTasks();
                // 当前tick需要处理的bucket
                HashedWheelBucket bucket =
                        wheel[idx];
                // 将timeouts队列移到bucket中
                transferTimeoutsToBuckets();
                // 处理掉当前bucket时间到或过期的任务
                bucket.expireTimeouts(deadline);
                // tick数+1,继续等待执行下一个tick时间点
                tick++;
            }
        } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);

        // 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();
    }
waitForNextTick()

等待下一个tick时间点到来

	private long waitForNextTick() {
// 当前tick需要等待时间
        long deadline = tickDuration * (tick + 1);
        for (;;) {
        	// 当前时间与开始时间的时间间隔
            final long currentTime = System.nanoTime() - startTime;
            // 剩余需要等待的时间
            long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
            // 当前时间已经超过当前tick执行所需等待的时间了,则返回,否则休眠后再判断
            if (sleepTimeMs <= 0) {
                if (currentTime == Long.MIN_VALUE) {
                    return -Long.MAX_VALUE;
                } else {
                    return currentTime;
                }
            }
            // windows系统bug
            if (PlatformDependent.isWindows()) {
                sleepTimeMs = sleepTimeMs / 10 * 10;
            }
            // 休眠
            try {
                Thread.sleep(sleepTimeMs);
            } catch (InterruptedException ignored) {
            	// HashedWheelTimer shutdown导致的线程中断
                if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
                    return Long.MIN_VALUE;
                }
            }
        }
    }
transferTimeoutsToBuckets()

每次提取最多10000个任务,避免过多的时间消耗在这里影响定时任务,然后将任务放到对应的tick所在的wheel数组中

	private void transferTimeoutsToBuckets() {
        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;
            }
            // 需要的tick数
            long calculated = timeout.deadline / tickDuration;
            // 执行该任务的时间轮数
            timeout.remainingRounds = (calculated - tick) / wheel.length;
            // 如果需要的tick数小于当前tick,说明时间点已经过了,直接在当前tick执行
            final long ticks = Math.max(calculated, tick);
            // 对应的wheel位置 
            int stopIndex = (int) (ticks & mask);
            // 加入到双向队列
            HashedWheelBucket bucket = wheel[stopIndex];
            bucket.addTimeout(timeout);
        }
    }
HashedWheelBucket.expireTimeouts()

执行过期任务

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

        // process all timeouts
        while (timeout != null) {
            HashedWheelTimeout next = timeout.next;
            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 {
                timeout.remainingRounds --;
            }
            timeout = next;
        }
    }
processCancelledTasks()

移除取消的任务

	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);
                }
            }
        }
    }
    void remove() {
        HashedWheelBucket bucket = this.bucket;
        if (bucket != null) {
            bucket.remove(this);
        } else {
            timer.pendingTimeouts.decrementAndGet();
        }
    }
        
<think>嗯,用户问的是关于仿RabbitMQ实现消息队列的项目用了哪些技术。首先,我得先回想一下RabbitMQ的基本架构和核心功能。RabbitMQ是基于AMQP协议的,所以可能需要实现类似的协议或者至少支持消息的路由、队列、交换器等概念。 接下来,用户可能是一个开发者,想要自己动手实现一个简化版的消息队列,了解需要用哪些技术栈。这时候需要分层次来思考,比如网络通信、协议处理、持久化存储、高可用性、集群管理等等。 网络通信方面,RabbitMQ使用Erlang的OTP框架,但如果是仿照实现,可能会选择其他语言,比如Java的Netty或者Go的goroutine来处理高并发连接。需要确定项目使用的编程语言,比如Java、Go或者Python,不同语言对应的技术选型会不同。 协议处理部分,AMQP是一个复杂的协议,仿制的话可能需要简化,或者选择实现一个自定义的进制协议,或者使用HTTP/REST API,但这样可能不符合消息队列的高效要求。或者使用更简单的协议比如STOMP,或者直接基于TCP自定义协议。 存储方面,内存存储可能不够,需要持久化,所以可能会用到数据库,比如MySQL、PostgreSQL,或者更高效的存储系统如RocksDB、LevelDB。另外,消息的持久化可能需要考虑写入磁盘的方式,比如文件系统或者WAL(Write-Ahead Logging)。 高可用和集群方面,可能需要用到Raft或者Paxos共识算法来实现分布式一致性,或者使用ZooKeeper、etcd来做服务发现和配置管理。这部分比较复杂,如果是简化版可能暂时不考虑,但用户可能想知道如何扩展。 消息路由和交换器部分,需要实现不同的交换类型,比如直连、主题、扇出等,这涉及到路由算法的设计和数据结构的选择,比如哈希表、Trie树来高效匹配路由键。 消费者管理方面,需要处理消息的确认机制(ACK/NACK),可能还需要死信队列来处理失败的消息。这部分需要设计消息重发和超时机制。 监控和管理,可能需要提供管理界面或者API,用Web框架比如Spring Boot、Gin或者Echo来暴露管理接口,同时记录日志和指标,使用Prometheus和Grafana进行监控。 测试和性能优化,可能需要压测工具如JMeter,或者自己写基准测试,优化网络和存储性能,比如使用零拷贝技术、批量处理等。 然后,还要考虑项目的范围,是单机版还是分布式,是否需要事务支持、优先级队列、延迟队列等高级功能。用户可能希望了解核心技术点,而不是所有细节,所以需要分模块列举,每个模块用到的技术栈。 可能的误区是用户可能混淆了消息队列的不同实现技术,比如Kafka和RabbitMQ的区别,但这里明确是仿RabbitMQ,所以需要强调AMQP相关技术和交换器、队列绑定等概念。 总结下来,技术选型可能包括:网络框架(Netty、Go net)、协议实现(自定义或简化AMQP)、存储引擎(RocksDB、文件系统)、集群协调(Raft、etcd)、路由算法、Web管理(Spring MVC、Gin)、监控(Prometheus)、测试工具等。需要分点说明,用中文清晰解释每个部分的作用和可选技术,同时给出可能的组合示例,比如Java+Netty+RocksDB+Raft,或者Go+goroutine+etcd+LevelDB等。</think>一个仿RabbitMQ实现消息队列的项目通常会涉及以下核心技术模块(以典型Java技术栈为例): --- ### **1. 通信协议层** - **自定义TCP协议**:实现类AMQP的进制协议帧解析 - **Netty框架**:处理高并发网络I/O,实现连接管理、编解码器 - **心跳机制**:通过`IdleStateHandler`实现连接保活检测 - **SSL/TLS**:可选实现安全通信 --- ### **2. 核心消息处理** - **Exchange路由引擎**: - 实现`direct/topic/fanout/headers`四种交换机类型 - 使用Trie树加速topic匹配(如`a.*.c`模式匹配) - **队列管理**: - 内存队列(ConcurrentLinkedQueue) - 持久化队列(基于RocksDB实现磁盘存储) - **消息确认**: - 实现ACK/NACK机制 - 死信队列(DLX)处理未确认消息 --- ### **3. 持久化存储** - **WAL日志**:通过顺序写日志(如Apache BookKeeper)保证消息不丢失 - **消息存储**: - 内存映射文件(MappedByteBuffer)加速磁盘读写 - 消息索引使用跳跃表(ConcurrentSkipListMap)维护 - **元数据存储**: - 使用嵌入式数据库H2存储队列/交换机绑定关系 - 或使用ZooKeeper实现分布式元数据管理 --- ### **4. 高可用设计** - **镜像队列**:基于Raft协议实现多节点数据同步 - **集群管理**: - 服务发现:集成Consul/Etcd - 负载均衡:一致性哈希算法分配队列 - **脑裂防护**:通过Quorum算法保证多数派写入 --- ### **5. 管理控制** - **HTTP API**:使用Spring Boot实现REST管理接口 - **监控系统**: - 埋点指标通过Micrometer对接Prometheus - 日志追踪集成ELK栈 - **Web控制台**:Vue+ElementUI实现管理界面 --- ### **6. 扩展功能** - **延迟队列**:基于时间轮算法HashedWheelTimer)实现 - **事务消息**:两阶段提交(2PC)实现分布式事务 - **流控机制**:令牌桶算法实现QPS限制 --- ### **典型技术组合示例** ```java // 伪代码示例:Netty处理消息入队 public class MessageHandler extends ChannelInboundHandlerAdapter { private final PersistentQueue storage = RocksDBQueue.getInstance(); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { Message message = (Message) msg; if(message.isPersistent()) { storage.append(message); // 持久化存储 } MemoryQueueRegistry.getQueue(message.getRouteKey()) .offer(message); // 内存队列写入 } } ``` --- ### **实现难点** 1. 网络层背压控制(防止生产者压垮消费者) 2. 内存磁盘数据的协同管理 3. 集群状态下的数据一致性保证 4. 百万级长连接管理优化 实际开发中建议参考RabbitMQ的[核心设计文档](https://www.rabbitmq.com/resources.html),先实现单机版再逐步扩展分布式能力。可以通过WireShark抓包分析AMQP协议交互流程,这对理解消息队列底层机制非常有帮助。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值