Apache Pulsar延迟队列实现:定时消息与延迟处理

Apache Pulsar延迟队列实现:定时消息与延迟处理

【免费下载链接】pulsar Apache Pulsar - distributed pub-sub messaging system 【免费下载链接】pulsar 项目地址: https://gitcode.com/gh_mirrors/pulsar24/pulsar

延迟队列概述

在分布式系统中,延迟队列(Delayed Queue)是一种特殊的消息队列,它允许消息在指定时间后才被消费者处理。Apache Pulsar作为分布式发布订阅消息系统,提供了强大的延迟消息处理能力,支持定时消息(Scheduled Message)和延迟消息(Delayed Message)两种模式。

延迟队列的典型应用场景包括:

  • 订单超时未支付自动取消
  • 定时任务调度
  • 消息重试机制
  • 基于时间的业务流程编排

Pulsar的延迟队列实现基于Bucket(桶)机制,通过BucketDelayedDeliveryTracker.java类实现消息的定时存储与触发,确保高吞吐量和低延迟的定时消息处理。

Pulsar延迟队列核心组件

DelayedDeliveryTracker接口

Pulsar延迟队列的核心接口是DelayedDeliveryTracker.java,它定义了延迟消息跟踪的基本操作:

public interface DelayedDeliveryTracker extends AutoCloseable {
    // 添加消息到延迟队列
    boolean addMessage(long ledgerId, long entryId, long deliveryAt);
    
    // 检查是否有可投递的消息
    boolean hasMessageAvailable();
    
    // 获取延迟消息数量
    long getNumberOfDelayedMessages();
    
    // 获取内存使用量
    long getBufferMemoryUsage();
    
    // 获取可投递的消息位置集合
    NavigableSet<Position> getScheduledMessages(int maxMessages);
    
    // 清除所有延迟消息
    CompletableFuture<Void> clear();
}

该接口有两个主要实现类:

  • InMemoryDelayedDeliveryTracker:纯内存实现,适合轻量级场景
  • BucketDelayedDeliveryTracker:基于Bucket的持久化实现,支持大规模延迟消息

Bucket机制详解

Pulsar采用Bucket(桶)机制管理延迟消息,将消息按照时间范围分组存储,每个Bucket负责一定时间窗口内的延迟消息。这种设计可以有效减少内存占用,并支持消息的持久化存储。

Bucket机制的核心配置参数在ServiceConfiguration.java中定义:

// 每个Bucket的最小索引数量
private long delayedMessageIndexBucketMinIndexCount = 1000;

// 每个Bucket快照段的时间步长(秒)
private long delayedMessageIndexTimeStepInSeconds = 3600;

// 每个Bucket快照段的最大索引数
private int delayedMessageIndexMaxPerSegment = 10000;

// 最大Bucket数量
private int delayedMessageIndexMaxBuckets = 100;

Bucket的工作流程如下:

  1. 消息到达时,根据deliveryAt时间戳分配到相应的Bucket
  2. Bucket达到配置的大小或时间阈值后,会被持久化到存储系统
  3. 定时任务扫描Bucket,将达到投递时间的消息推送至消费者

消息存储与索引结构

Pulsar使用分层存储结构管理延迟消息:

  • 内存索引:使用TripleLongPriorityQueue维护消息的投递顺序
  • 持久化存储:Bucket内容定期快照到持久化存储,实现故障恢复

BucketDelayedDeliveryTracker.java中,我们可以看到Bucket的创建和合并过程:

// 创建ImmutableBucket并持久化
Pair<ImmutableBucket, DelayedIndex> immutableBucketDelayedIndexPair =
    lastMutableBucket.sealBucketAndAsyncPersistent(
        this.timeStepPerBucketSnapshotSegmentInMillis,
        this.maxIndexesPerBucketSnapshotSegment,
        this.sharedBucketPriorityQueue);

延迟消息的生命周期

消息生产过程

当生产者发送延迟消息时,需要设置deliverAtdeliverAfter属性:

Producer<String> producer = client.newProducer(Schema.STRING)
    .topic("delayed-topic")
    .create();

// 发送延迟10秒的消息
producer.newMessage()
    .deliverAfter(10, TimeUnit.SECONDS)
    .value("Hello Pulsar Delayed Message")
    .send();

Broker收到消息后,通过addMessage方法将其添加到延迟队列:

// BucketDelayedDeliveryTracker.java
public synchronized boolean addMessage(long ledgerId, long entryId, long deliverAt) {
    // 检查是否需要立即投递
    if (deliverAt < 0 || deliverAt <= getCutoffTime()) {
        return false; // 立即投递
    }
    
    // 根据ledgerId分配到相应的Bucket
    if (ledgerId < lastMutableBucket.startLedgerId || existBucket) {
        // 添加到共享优先级队列
        sharedBucketPriorityQueue.add(deliverAt, ledgerId, entryId);
    } else {
        // 添加到当前Bucket
        lastMutableBucket.addMessage(ledgerId, entryId, deliverAt);
    }
    
    numberDelayedMessages++;
    updateTimer();
    return true;
}

消息存储与索引

Pulsar使用RoaringBitmap优化消息索引存储,减少内存占用:

// BucketDelayedDeliveryTracker.java
private synchronized void putAndCleanOverlapRange(Range<Long> range, ImmutableBucket immutableBucket,
                                                Map<Range<Long>, ImmutableBucket> toBeDeletedBucketMap) {
    // 处理Bucket范围重叠
    RangeMap<Long, ImmutableBucket> subRangeMap = immutableBuckets.subRangeMap(range);
    if (!subRangeMap.asMapOfRanges().isEmpty()) {
        for (Map.Entry<Range<Long>, ImmutableBucket> rangeEntry : subRangeMap.asMapOfRanges().entrySet()) {
            if (range.encloses(rangeEntry.getKey())) {
                toBeDeletedBucketMap.put(rangeEntry.getKey(), rangeEntry.getValue());
            }
        }
    }
    immutableBuckets.put(range, immutableBucket);
}

消息投递触发

Pulsar通过定时任务检查并投递到期消息:

// BucketDelayedDeliveryTracker.java
@Override
public void run(Timeout timeout) throws Exception {
    synchronized (this) {
        if (timeout == null || timeout.isCancelled()) {
            return;
        }
        // 将到期消息移动到共享队列
        lastMutableBucket.moveScheduledMessageToSharedQueue(getCutoffTime(), sharedBucketPriorityQueue);
    }
    super.run(timeout);
}

配置与调优

核心配置参数

Pulsar延迟队列的主要配置在broker.conf中设置:

# 启用延迟消息功能
delayedDeliveryEnabled=true

# 每个Bucket的最小索引数量
delayedMessageIndexBucketMinIndexCount=1000

# 每个Bucket快照段的时间步长(秒)
delayedMessageIndexTimeStepInSeconds=3600

# 每个Bucket快照段的最大索引数
delayedMessageIndexMaxPerSegment=10000

# 最大Bucket数量
delayedMessageIndexMaxBuckets=100

性能调优建议

  1. 内存优化

    • 根据消息量调整delayedMessageIndexBucketMinIndexCount
    • 合理设置delayedMessageIndexMaxBuckets避免内存溢出
  2. 持久化调优

    • 对于长时间延迟消息,适当增大delayedMessageIndexTimeStepInSeconds
    • 高吞吐场景可增加delayedMessageIndexMaxPerSegment
  3. 监控指标

    • 关注delayedMessages指标了解延迟消息数量
    • 监控delayedMessageIndexSize跟踪索引大小变化

故障恢复与高可用

快照与恢复机制

Pulsar定期对Bucket进行快照,确保延迟消息不会因Broker重启而丢失:

// BucketDelayedDeliveryTracker.java
private synchronized long recoverBucketSnapshot() throws RecoverDelayedDeliveryTrackerException {
    // 从持久化存储恢复Bucket
    Map<String, String> cursorProperties = cursor.getCursorProperties();
    if (MapUtils.isEmpty(cursorProperties)) {
        log.info("[{}] Recover delayed message index bucket snapshot finish, don't find bucket snapshot",
                dispatcher.getName());
        return 0;
    }
    
    // 恢复每个Bucket的消息索引
    cursorProperties.keySet().forEach(key -> {
        if (key.startsWith(DELAYED_BUCKET_KEY_PREFIX)) {
            // 解析并恢复Bucket
            String[] keys = key.split(DELIMITER);
            ImmutableBucket immutableBucket = new ImmutableBucket(...);
            // 添加到Bucket映射
            putAndCleanOverlapRange(Range.closed(...), immutableBucket, toBeDeletedBucketMap);
        }
    });
    
    // 恢复消息到优先级队列
    for (DelayedIndex index : indexList) {
        this.sharedBucketPriorityQueue.add(index.getTimestamp(), index.getLedgerId(), index.getEntryId());
    }
    
    return numberDelayedMessages.getValue();
}

数据一致性保障

Pulsar通过以下机制确保延迟消息的一致性:

  • 使用BookKeeper存储消息内容,确保持久化可靠性
  • Bucket快照采用事务方式写入,保证数据完整性
  • 定期校验Bucket数据,自动修复损坏的索引

使用示例

基本使用示例

以下是使用Pulsar延迟队列的简单示例:

// 生产者代码
Producer<String> producer = client.newProducer(Schema.STRING)
    .topic("persistent://public/default/delay-topic")
    .create();

// 发送10秒后投递的消息
producer.newMessage()
    .deliverAfter(10, TimeUnit.SECONDS)
    .value("Delayed message content")
    .send();

// 消费者代码
Consumer<String> consumer = client.newConsumer(Schema.STRING)
    .topic("persistent://public/default/delay-topic")
    .subscriptionName("delay-subscription")
    .subscriptionType(SubscriptionType.Exclusive)
    .subscribe();

while (true) {
    Message<String> msg = consumer.receive();
    System.out.println("Received message: " + msg.getValue());
    consumer.acknowledge(msg);
}

高级特性:定时消息

Pulsar还支持指定精确时间戳的定时消息:

// 发送在指定时间点投递的消息
long deliverAtTimestamp = LocalDateTime.of(2025, 1, 1, 0, 0).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();

producer.newMessage()
    .deliverAt(deliverAtTimestamp)
    .value("Happy New Year 2025!")
    .send();

总结与最佳实践

Apache Pulsar的延迟队列实现基于Bucket机制,结合内存索引和持久化存储,提供了高效、可靠的延迟消息处理能力。在实际应用中,建议:

  1. 合理规划延迟时间

    • 短延迟(秒级)消息可直接使用内存模式
    • 长延迟(天级)消息建议使用Bucket持久化模式
  2. 监控与调优

    • 定期监控延迟消息数量和Bucket大小
    • 根据业务增长调整Bucket配置参数
  3. 故障处理

    • 了解延迟消息的恢复机制
    • 制定Bucket快照备份策略

通过合理配置和使用Pulsar延迟队列,可以轻松实现定时任务、消息重试、业务流程编排等场景需求,为分布式系统提供可靠的时间驱动能力。

更多详细信息可参考:

【免费下载链接】pulsar Apache Pulsar - distributed pub-sub messaging system 【免费下载链接】pulsar 项目地址: https://gitcode.com/gh_mirrors/pulsar24/pulsar

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值