Pulsar源码解析-服务端-消费者消息拉取的底层实现

本文详细剖析了Pulsar服务端如何处理客户端的消费请求,从Consumer请求拉取、Subscription分发策略、到ManagedCursor读取,直至Broker端缓存和协议层发送,展示了消息消费的完整流程。

上一篇介绍了服务端消费者的创建,本章介绍消息拉取请求实现

一、消息拉取请求入口

public class ServerCnx {
   
   

    protected void handleFlow(CommandFlow flow) {
   
   
        checkArgument(state == State.Connected);

        CompletableFuture<Consumer> consumerFuture = consumers.get(flow.getConsumerId());

        if (consumerFuture != null && consumerFuture.isDone() && !consumerFuture.isCompletedExceptionally()) {
   
   
            Consumer consumer = consumerFuture.getNow(null);
            if (consumer != null) {
   
   
            	// 传入客户端配置的默认1000
                consumer.flowPermits(flow.getMessagePermits());
            } else {
   
   
            }
        }
    }
}

继续consumer.flowPermits(...)

二、Consumer层请求拉取

public class Consumer {
   
   

    public void flowPermits(int additionalNumberOfMessages) {
   
   
        checkArgument(additionalNumberOfMessages > 0);
		// 超过限制不拉取
        if (shouldBlockConsumerOnUnackMsgs() && unackedMessages >= maxUnackedMessages) {
   
   
            blockedConsumerOnUnackedMsgs = true;
        }
        int oldPermits;
        if (!blockedConsumerOnUnackedMsgs) {
   
   
            oldPermits = MESSAGE_PERMITS_UPDATER.getAndAdd(this, additionalNumberOfMessages);
            // 通过订阅
            subscription.consumerFlow(this, additionalNumberOfMessages);
        } else {
   
   
            oldPermits = PERMITS_RECEIVED_WHILE_CONSUMER_BLOCKED_UPDATER.getAndAdd(this, additionalNumberOfMessages);
        }
    }
}

上一章介绍过subscription是最外层,subscription关联的每个consumer持有subscription的引用,具体看上一章的consumer创建的构造传参。多个消费者相同的订阅名称配置都是调用subscription.consumerFlow(...);
继续subscription.consumerFlow(...);

三、Consumer所属的Subscription层消息拉取请求

public class PersistentSubscription {
   
   

    public void consumerFlow(Consumer consumer, int additionalNumberOfMessages) {
   
   
    	// 更新拉取时间
        this.lastConsumedFlowTimestamp = System.currentTimeMillis();
        // 根据具体的订阅类型创建的dispatcher进行拉取
        dispatcher.consumerFlow(consumer, additionalNumberOfMessages);
    }
}

继续dispatcher.consumerFlow(...)
dispatcher实现有两个:
1、PersistentDispatcherSingleActiveConsumer
订阅模式:Exclusive、Failover
作用:组装从bk读出entry,组装数据,发送到请求的consumer

2、PersistentDispatcherMultipleConsumers
订阅模式:Shared、Key_Shared是PersistentStickyKeyDispatcherMultipleConsumers继承PersistentDispatcherMultipleConsumers

Shared作用:组装从bk读出entry,组装数据,轮询发送到已存在的consumer,如果消费者客户端配置了优先级相当于加权轮询

Key_Shared作用:组装从bk读出entry,组装数据,根据客户端配置的key的hash % consumer数量得出consumer(大概这意思,实际还是有些细节优化的)

核心都差不多,区别就是上面加粗的黑色。所以我们只看PersistentDispatcherMultipleConsumers的实现

四、Subscription对应的分发策略-PersistentDispatcherMultipleConsumers第1层回调

public class PersistentDispatcherMultipleConsumers {
   
   

    public synchronized void consumerFlow(Consumer consumer, int additionalNumberOfMessages) {
   
   
        if (!consumerSet.contains(consumer)) {
   
   
            return;
        }
        totalAvailablePermits += additionalNumberOfMessages;
        readMoreEntries();
    }
}

继续readMoreEntries();

public class PersistentDispatcherMultipleConsumers {
   
   

public synchronized void readMoreEntries() {
   
   
		// 获取一个consumer的availablePermits
        int firstAvailableConsumerPermits = getFirstAvailableConsumerPermits();
        // 与总availablePermits比较
        int currentTotalAvailablePermits = Math.max(totalAvailablePermits, firstAvailableConsumerPermits);
        if (currentTotalAvailablePermits > 0 && firstAvailableConsumerPermits > 0) {
   
   
            // 通过限流配置计算出 1:读的数量 2:读的字节大小
            Pair<Integer, Long> calculateResult = calculateToRead(currentTotalAvailablePermits);
            // 读的数量
            int messagesToRead = calculateResult.getLeft();
            // 读的大小
            long bytesToRead = calculateResult.getRight();

            if (messagesToRead == -1 || bytesToRead == -1) {
   
   
                return;
            }
			// 获取重新投递的列表或者可发送的延迟列表数据
            Set<PositionImpl> messagesToReplayNow = getMessagesToReplayNow(messagesToRead);
            if (!messagesToReplayNow.isEmpty()) {
   
   

                havePendingReplayRead = true;
                // 如果topic开启延迟投递
                // 1、过滤已经标记删除的数据 2、剩余从bk读出发给客户端
                // 返回的是已经标记删除的
                Set<? extends Position> deletedMessages = topic.isDelayedDeliveryEnabled()
                        ? asyncReplayEntriesInOrder(messagesToReplayNow) : asyncReplayEntries(messagesToReplayNow);
				// 从重新投递队列移除删除的
                deletedMessages.forEach(position -> redeliveryMessages.remove(((PositionImpl) position).getLedgerId(),
                        ((PositionImpl) position).getEntryId()));
                        //  
                if ((messagesToReplayNow.size() - deletedMessages.size()) == 0) {
   
   
                    havePendingReplayRead = false;
                    // 重新读
                    topic.getBrokerService().executor().execute(() -> readMoreEntries());
                }
            }
            // 超过最大未ack的数量阻塞消息分发 
            else if (BLOCKED_DISPATCHER_ON_UNACKMSG_UPDATER.get(this) == TRUE) {
   
   

            }
            // 没有正在读的请求
            else if (!havePendingRead) {
   
   
                havePendingRead = true;
                // 读取 this是第一层回调
                cursor.asyncReadEntriesOrWait(messagesToRead, bytesToRead, this,
            
### 使用 `winfun-pulsar-spring-boot-starter` 进行消息消费 为了在Spring Boot应用程序中使用 `winfun-pulsar-spring-boot-starter` 实现消息消费,需遵循特定配置和编码实践。 #### 添加依赖项 首先,在项目的 `pom.xml` 文件中引入必要的Maven依赖: ```xml <dependency> <groupId>com.github.winfun</groupId> <artifactId>winfun-pulsar-spring-boot-starter</artifactId> <version>${latest.version}</version> </dependency> ``` 此处 `${latest.version}` 应替换为实际版本号[^1]。 #### 配置Pulsar连接属性 编辑 `application.yml` 或者 `application.properties` 来指定 Pulsar 的服务URL和其他必要参数: ```yaml winfun: pulsar: service-url: "pulsar://localhost:6650" topic-name: "persistent://public/default/my-topic" subscription-name: "my-subscription" ``` 这些设置定义了目标主题以及订阅名称等重要细节[^2]。 #### 创建消费者监听器 编写一个带有 `@PulsarListener` 注解的方法来处理接收到的消息。该方法可以位于任何被 Spring 所管理的 Bean 中: ```java import com.github.winfun.pulsar.annotation.PulsarListener; import org.springframework.stereotype.Component; @Component public class MessageConsumer { @PulsarListener(topic = "${winfun.pulsar.topic-name}", subscriptionName = "${winfun.pulsar.subscription-name}") public void consumeMessage(String message){ System.out.println("Received message:" + message); } } ``` 上述代码片段展示了如何通过简单的字符串参数接收并打印来自指定主题的消息内容[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值