Kafka 延迟操作模块(二):Timer定时器

    上面介绍的 TimingWheel 提供了添加延时任务和推进时间轮指针的操作,而具体执行延时任务的操作则交由定时器 SystemTimer 完成。SystemTimer 类实现了 Timer 特质,该特质描绘了定时器应该具备的基本方法。Timer 接口定义了管理延迟操作的方法,而 SystemTimer 是实现延迟操作的关键代码。

Timer 接口类

Timer 接口定义如下:

trait Timer {
  /**
    * Add a new task to this executor. It will be executed after the task's delay
    * (beginning from the time of submission)
    * @param timerTask the task to add
    */
  // 将给定的定时任务插入到时间轮上,等待后续延迟执行
  def add(timerTask: TimerTask): Unit

  /**
    * Advance the internal clock, executing any tasks whose expiration has been
    * reached within the duration of the passed timeout.
    * @param timeoutMs
    * @return whether or not any tasks were executed
    */
  // 向前推进时钟,执行已达过期时间的延迟任务
  def advanceClock(timeoutMs: Long): Boolean

  /**
    * Get the number of tasks pending execution
    * @return the number of tasks
    */
  // 获取时间轮上总的定时任务数
  def size: Int

  /**
 
RabbitMQ 的延迟消息插件 `rabbitmq_delayed_message_exchange` 是一个非常实用的扩展,它允许你发送消息时指定任意延迟时间(通过 `x-delay` 头部),并在该时间过后将消息投递给消费者。 尽管 RabbitMQ 原生不支持延迟队列,但这个插件通过**自定义交换机类型 + 内部调度机制**巧妙地实现了这一功能。 --- ## ✅ 一、延迟消息插件的工作原理 ### 🔌 插件名称: ``` rabbitmq_delayed_message_exchange ``` ### 🧩 类型: 它实现了一个新的交换机类型:`x-delayed-message` 当你声明一个交换机为这种类型时,RabbitMQ 就会使用插件提供的逻辑来处理消息路由。 --- ### 🔄 核心工作流程 1. **生产者发送消息到 `x-delayed-message` 类型的交换机** - 消息必须包含头信息:`x-delay`(单位:毫秒) - 示例:`x-delay: 5000` 表示延迟 5 秒 2. **插件拦截消息并“暂存”** - 消息不会立即被路由 - 插件内部将消息标记为“延迟中”,并记录其到期时间(当前时间 + delay) 3. **消息进入内部等待区(Erlang 进程或 Mnesia 表)** - 实际上是存储在内存中的有序结构(类似优先队列 / 时间轮) - 所有延迟消息按“到期时间”排序 4. **后台定时任务轮询到期消息** - 插件启动一个 Erlang 调度器,周期性检查是否有消息已到期 - 到期后,插件自动将消息重新发布到原交换机的绑定队列中(即正常投递) 5. **消费者从队列中消费消息** - 整个过程对消费者透明 —— 它不知道这是延迟消息 --- ### 📦 数据流向图解: ``` [Producer] ↓ (send to x-delayed-message exchange, with x-delay=5000) [Custom Exchange (x-delayed-message)] ↓ (hold for 5s) [Internal Delay Store (in-memory sorted by deadline)] ↓ (after 5s, scheduler triggers) [Re-publish to same exchange → route to queue] ↓ [Queue] ↓ [Consumer] ``` > ⚠️ 注意:消息在“暂存”期间并不是真正“消失”,而是被插件控制不进行投递。 --- ## 🔍 、它是如何实现“精准延迟”的? 虽然不能做到微秒级精确,但在大多数业务场景下足够准确。其实现机制如下: ### 1. **基于 Erlang 的轻量级定时器Timers)** - RabbitMQ 使用 Erlang 开发,Erlang 提供高效的并发和定时器支持 - 插件为每条延迟消息创建一个 **Erlang timeout timer** - 当 timer 触发时,执行回调函数:将消息重新入队 ```erlang % 伪代码示意 erlang:send_after(DelayMs, self(), {timeout, Message}) ``` ### 2. **批量扫描 + 最小堆优化(避免大量定时器开销)** 对于高吞吐场景,插件不会为每条消息都创建独立定时器,而是采用: - 将所有延迟消息按到期时间维护在一个 **最小堆(Min-Heap)** 或有序表中 - 启动一个全局扫描线程(比如每 500ms 扫一次) - 检查堆顶消息是否到期,若到期则投递,并继续处理下一个 这样可以显著减少系统资源消耗。 ### 3. **持久化支持(可选)** 如果消息设置了 `delivery_mode=2`(持久化),且队列也持久化: - 即使 RabbitMQ 重启,延迟中的消息也会恢复(前提是插件正确保存状态) - 但实际上:**默认情况下,延迟中的消息是存在内存里的,重启会丢失!** > ✅ 解决方案:建议结合数据库或外部调度系统做补偿。 --- ## 💡 三、关键特性与限制 | 特性 | 说明 | |------|------| | ✅ 支持任意延迟时间 | 每条消息可设置不同的 `x-delay` | | ✅ 路由规则灵活 | 支持 binding key、多种交换机底层类型(如 direct) | | ❌ 不支持事务中的延迟消息 | 在事务中发送的消息可能无法被正确延迟 | | ⚠️ 内存占用 | 大量延迟消息会占用内存,需监控 | | ⚠️ 精度误差 | 一般在几十到几百毫秒内,取决于扫描间隔 | | ❌ 默认不持久化延迟状态 | 服务重启后未到期的消息**会丢失** | --- ## 🛠️ 四、源码层面简析(GitHub 开源) 插件开源地址: 👉 https://github.com/rabbitmq/rabbitmq-delayed-message-exchange 核心文件: - `delayed_message_exchange.erl`:主模块,定义了交换机行为 - `dlx_queue_backend.erl`:管理延迟消息的存储与调度 - 使用 `mnesia` 或 `ETS`(Erlang Term Storage)作为内存表存储待处理消息 部分关键逻辑伪代码: ```erlang % 接收消息 route(#exchange{name = Name}, #delivery{message = Msg} = Delivery) -> case get_delay_from_header(Msg#basic_message.properties) of undefined -> % 没有延迟,直接转发 amqp_router:route(Name, Delivery); Delay when is_integer(Delay), Delay >= 0 -> % 计算到期时间 ExpireAt = os:system_time(millisecond) + Delay, % 存入延迟表 ets:insert(delay_table, {ExpireAt, Name, Delivery}), % 设置唤醒任务 erlang:send_after(Delay, ?MODULE, flush), ok end. ``` --- ## ✅ 五、最佳实践建议 | 场景 | 建议 | |------|------| | 小规模延迟任务 | 可直接使用插件 | | 高可靠性要求 | 结合 DB 记录延迟任务,宕机后恢复 | | 超长延迟(如几天) | 不推荐用此插件,应使用定时任务(Quartz/ElasticJob) | | 海量延迟消息 | 考虑 Kafka + 时间轮 或 Redis ZSet 方案 | | 需要持久化延迟状态 | 自研或使用外部调度中间件 | --- ## ✅ 六、替代方案对比 | 方案 | 精确性 | 可靠性 | 复杂度 | 适用场景 | |------|--------|--------|--------|-----------| | RabbitMQ 延迟插件 | 中 | 中(内存存储) | 低 | 中短延迟、中小流量 | | 死信队列 + TTL | 低(只能统一TTL) | 高(持久化支持好) | 中 | 固定延迟任务 | | Redis ZSet + 定时轮询 | 高 | 高 | 中 | 动态延迟、高性能 | | Kafka + 时间轮 | 极高 | 高 | 高 | 超大规模系统 | | 专用调度框架(ElasticJob) | 高 | 高 | 高 | 复杂定时任务 | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值