2. 消息生产原理

1 PulsarClient

Pulsar 中的 Producer 和 Consumer 都是由数据流(Pulsar中还有管理流-Admin)客户端创建的,它管理了所有 Producer 和 Consumer 的实例,当数据流客户端关闭时,所有由它创建的 Producer 和 Consumer 都会被关闭。

1.1 创建客户端

创建 PulsarClient 对象,如下:

PulsarClient build = PulsarClient.builder()
            .listenerThreads(1)
            .ioThreads(1)
            .serviceUrl("pulsar://127.0.0.1:6650")
            .build();

这是建造者模式,参数较多时使用建造者设计模式可以更直观清晰。这个类的主要方法
PulsarClient中的方法
主要作用

  • 创建producer/consumer/reader/tableview
  • 元数据信息相关:得到topic的分区列表
  • transaction相关

1.2 重要属性&参数

PulsarClient 主要初始化线程池、连接池、权限模型、查询broker分区 topic所属等用的LookupService、流量控制、存放生产消费者的集合。
重要属性

    private LookupService lookup;
    private final ConnectionPool cnxPool;
    @Getter
    private final Timer timer;
    private final ExecutorProvider externalExecutorProvider;
    private final ExecutorProvider internalExecutorProvider;
  • LookupService:
  • ConnectionPool:
  • 时间轮 WheelTimer:
  • 线程池 Executor:

可设置的参数
PulsarClient 上可以设置的参数详见《深入解析 Apache Pulsar》P42
这些参数可以在 org.apache.pulsar.client.impl.conf.ClientConfigurationData 中查看

	// broker地址
    private String serviceUrl;
    private transient ServiceUrlProvider serviceUrlProvider;
    // auth开头都是认证相关的
    private Authentication authentication;
    private String authPluginClassName;
    private String authParams;
    private Map<String, String> authParamMap;
    // 客户端和服务端接口交互的超时时间
    private long operationTimeoutMs = 30000;
    // 多久打印一下数据,比如消费者的消费速度、字节数、消费者总共接收了多少消息等
    private long statsIntervalSeconds = 60;
    // 客户端和服务端接口交互用的线程数
    private int numIoThreads = 1;
    // 消费者的线程数
    private int numListenerThreads = 1;
    // 连接池用的,相当于最大连接数创建
    private int connectionsPerBroker = 1;
    // tcp nagle算法
    private boolean useTcpNoDelay = true;
    // tls协议相关的
    private boolean useTls = false;
    private String tlsTrustCertsFilePath = "";
    private boolean tlsAllowInsecureConnection = false;
    private boolean tlsHostnameVerificationEnable = false;
    // 用于创建Semaphore,控制客户端向服务端请求速率 例如查询topic属于哪个broker,查询分区数
    private int concurrentLookupRequest = 5000;
    private int maxLookupRequest = 50000;
    // 重定向次数 查询topic属于哪个broker时随机选个broker看看是否存在,不存在定向到其他broker
    private int maxLookupRedirects = 20;
    // 当前请求过多,对服务端造成限流,会拒绝客户端请求 场景:还是查询topic属于哪个broker和查询分区数
    private int maxNumberOfRejectedRequestPerConnection = 50;
    // channel建立后,创建一个定时心跳任务保活channel,该参数是多久发一次心跳请求
    private int keepAliveIntervalSeconds = 30;
    // 客户端与服务端的连接超时时间
    private int connectionTimeoutMs = 10000;
    // pulsar有admin web接口,该参数是http请求的超时时间
    private int requestTimeoutMs = 60000;
    // 退避初始时间 例如:连接服务端失败了,是不是要重试,那过多久重试呢,重试后又失败了,再过多久重试呢,比如初始1秒,再失败通过一套计算方式,得出下次重试3秒后,再失败10秒后 就是干这个用的
    private long initialBackoffIntervalNanos = TimeUnit.MILLISECONDS.toNanos(100);
    // 接上面:不能一直重试,因为越来越久,总得有个封顶然后再从初识或某个点开始 这个就是封顶值
    private long maxBackoffIntervalNanos = TimeUnit.SECONDS.toNanos(60);
    // EpollEventLoop调用select时的等待策略
    private boolean enableBusyWait = false;
    private String listenerName;
    private boolean useKeyStoreTls = false;
    private String sslProvider = null;
    private String tlsTrustStoreType = "JKS";
    private String tlsTrustStorePath = null;
    private String tlsTrustStorePassword = null;
    private Set<String> tlsCiphers = Sets.newTreeSet();
    private Set<String> tlsProtocols = Sets.newTreeSet();
    // 发送消息后,再没有收到服务端回调数据还在内存中。用于控制同一时刻内存中存在的消息大小限制
    private long memoryLimitBytes = 0;
    private String proxyServiceUrl;
    private ProxyProtocol proxyProtocol;
    private boolean enableTransaction = false;
    private Clock clock = Clock.systemDefaultZone();

1.3 ConnectionPool

连接池的主要功能包括:

  • 创建并cache连接:getConnection
  • 归还连接:releaseConnection
  • 关闭连接:closeAllConnections

ConnectionPool 构造函数主要是按照 netty 网络客户端方式初始化相关成员变量,如下:

        bootstrap = new Bootstrap();
        // 绑定io线程池
        bootstrap.group(eventLoopGroup);
        // 配置channel类型,如果支持Epoll的话会变成Epoll的channel
        bootstrap.channel(EventLoopUtil.getClientSocketChannelClass(eventLoopGroup));

        // 设置tcp的连接超时时间
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, conf.getConnectionTimeoutMs());
        // 设置tcp no delay
        bootstrap.option(ChannelOption.TCP_NODELAY, conf.isUseTcpNoDelay());
        // 配置allocator
        bootstrap.option(ChannelOption.ALLOCATOR, PulsarByteBufAllocator.DEFAULT);

        try {
            // *** 绑定channelInitializer ***
            channelInitializerHandler = new PulsarChannelInitializer(conf, clientCnxSupplier);
            bootstrap.handler(channelInitializerHandler);
        } catch (Exception e) {
            log.error("Failed to create channel initializer");
            throw new PulsarClientException(e);
        }

PulsarChannelInitializer 用来初始化和 broker 端的连接,如下:

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast("consolidation", new FlushConsolidationHandler(1024, true));

        // Setup channel except for the SsHandler for TLS enabled connections
        ch.pipeline().addLast("ByteBufPairEncoder", tlsEnabled ? ByteBufPair.COPYING_ENCODER : ByteBufPair.ENCODER);

        // 定长解码器
        ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(
                Commands.DEFAULT_MAX_MESSAGE_SIZE + Commands.MESSAGE_SIZE_FRAME_PADDING, 0, 4, 0, 4));
        // 到这里可以拿到 RPC 协议反序列化后的对象,进行客户端逻辑处理
        // 实际在这个类 ClientCnx 里面处理所有逻辑
        ch.pipeline().addLast("handler", clientCnxSupplier.get());
    }

1.4 ClientCnx

主要负责和服务端交互的逻辑,这个类的层次结构如下:

public class ClientCnx extends PulsarHandler;
public abstract class PulsarHandler extends PulsarDecoder;
public abstract class PulsarDecoder extends ChannelInboundHandlerAdapter;

ClientCnx的主要方法/功能:

  • 连接生命周期管理(netty Handler 里面的方法)
    • channelActive:发送一个 ConnectCommand 请求给服务端
    • channelInActive
    • exceptionCaught
  • 发送request:主动发送 RPC 的方法(通过 sendRequestAndHandleTimeout 发送),并按照业务逻辑处理
    • GetLastMessageId
    • GetTopics
    • GetSchema
    • GetOrCreateSchema
    • AckResponse
    • Lookup;
  • 处理response:继承自 PulsarDecoder 的 handleXXXXX RPC 处理逻辑
    • handleConnected
    • handleAckResponse
    • handleLookupResponse
  • 注册/ 删除业务逻辑对象:
    • consumer
    • producer
    • transactionMetaStoreHandler
    • transactionBufferHandler

2 Producer

2.1 创建入口 newProducer

Producer 创建是调用了 PulsarClient.newProducer(),code如下:

    @Override
    public ProducerBuilder<byte[]> newProducer() {
        return new ProducerBuilderImpl<>(this, Schema.BYTES);
    }

    @Override
    public <T> ProducerBuilder<T> newProducer(Schema<T> schema) {
        ProducerBuilderImpl<T> producerBuilder = new ProducerBuilderImpl<>(this, schema);
        if (!memoryLimitController.isMemoryLimited()) {
            // set default limits for producers when memory limit controller is disabled
            producerBuilder.maxPendingMessages(NO_MEMORY_LIMIT_DEFAULT_MAX_PENDING_MESSAGES);
            producerBuilder.maxPendingMessagesAcrossPartitions(
                    NO_MEMORY_LIMIT_DEFAULT_MAX_PENDING_MESSAGES_ACROSS_PARTITIONS);
        }
        return producerBuilder;
    }

依然使用建造者模式,使 API 看起来更清晰直观,和 PulsarClient 一样,Producer 通过 ProducerConfigurationData 收集参数。
最终通过 create() -> createAsync -> client.createProducerAsyn() 创建 Producer,code如下:

    @Override
    public CompletableFuture<Producer<T>> createAsync() {
        // config validation
        checkArgument(!(conf.isBatchingEnabled() && conf.isChunkingEnabled()),
                "Batching and chunking of messages can't be enabled together");
        if (conf.getTopicName() == null) {
            return FutureUtil
                    .failedFuture(new IllegalArgumentException("Topic name must be set on the producer builder"));
        }

        try {
            setMessageRoutingMode();
        } catch (PulsarClientException pce) {
            return FutureUtil.failedFuture(pce);
        }

        return interceptorList == null || interceptorList.size() == 0
                ? client.createProducerAsync(conf, schema, null)
                : client.createProducerAsync(conf, schema, new ProducerInterceptors(interceptorList));
    }

2.2 创建分析 createProducerAsync

    private <T> CompletableFuture<Producer<T>> createProducerAsync(String topic,
                                                                   ProducerConfigurationData conf,
                                                                   Schema<T> schema,
                                                                   ProducerInterceptors interceptors) {
        CompletableFuture<Producer<T>> producerCreatedFuture = new CompletableFuture<>();

        getPartitionedTopicMetadata(topic).thenAccept(metadata -> {
            if (log.isDebugEnabled()) {
                log.debug("[{}] Received topic metadata. partitions: {}", topic, metadata.partitions);
            }

            ProducerBase<T> producer;
            if (metadata.partitions > 0) {
                producer = newPartitionedProducerImpl(topic, conf, schema, interceptors, producerCreatedFuture,
                        metadata);
            } else {
                producer = newProducerImpl(topic, -1, conf, schema, interceptors, producerCreatedFuture,
                        Optional.empty());
            }

            producers.add(producer);
        }).exceptionally(ex -> {
            log.warn("[{}] Failed to get partitioned topic metadata: {}", topic, ex.getMessage());
            producerCreatedFuture.completeExceptionally(ex);
            return null;
        });

        return producerCreatedFuture;
    }

先创建 CompletableFuture,等到真正调用时 get 获取,异步编程将性能压缩到极致,Pulsar中使用了大量的异步编程。
代码中 getPartitionedTopicMetadata(topic) 是获取 topic 的分区数,获取分区数后再决定创建分区 TopicProducer 还是非分区 TopicProducer,创建完成放入producers,PulsarClient时初始化了两个集合将来收集生产者和消费者。
这里仅介绍非分区 TopicProducer 实现,分区 TopicProducer 可以看作是由多个非分区 TopicProducer 组成。(非分区 topic 的生产者只创建一个,分区 Topic 的生产者有多少分区数就创建多少生产者)
请添加图片描述
ProducerImpl:主要是发送消息的api
ProducerBase:定义发送消息api抽象方法以及消息体的构建入口
HandlerState:维护生产者的状态,例如 初始化、准备就绪、连接断开等

2.3 构造函数 ProducerImpl

    public ProducerImpl(PulsarClientImpl client, String topic, ProducerConfigurationData conf,
                        CompletableFuture<Producer<T>> producerCreatedFuture, int partitionIndex, Schema<T> schema,
                        ProducerInterceptors interceptors) {
        super(client, topic, conf, producerCreatedFuture, schema, interceptors);
        // 一个链接下的生产者唯一标识,一个链接可以创建多个生产者
        this.producerId = client.newProducerId();
        this.producerName = conf.getProducerName();
        if (StringUtils.isNotBlank(producerName)) {
            this.userProvidedProducerName = true;
        }
        // 分区索引号,当前介绍的是非分区topic,所以是-1
        this.partitionIndex = partitionIndex;
        // 发送中的消息队列,意思:调用服务端发送前放入队列,服务端响应成功从队列移除
        this.pendingMessages = createPendingMessagesQueue();
        // 如果服务端没有响应,数据全是在客户端,太多对内存有隐患。要限制最大数量
        if (conf.getMaxPendingMessages() > 0) {
            this.semaphore = Optional.of(new Semaphore(conf.getMaxPendingMessages(), true));
        } else {
            this.semaphore = Optional.empty();
        }
		// 消息压缩实现
        this.compressor = CompressionCodecProvider.getCompressionCodec(conf.getCompressionType());
		// 去重用的
        if (conf.getInitialSequenceId() != null) {
            long initialSequenceId = conf.getInitialSequenceId();
            this.lastSequenceIdPublished = initialSequenceId;
            this.lastSequenceIdPushed = initialSequenceId;
            this.msgIdGenerator = initialSequenceId + 1L;
        } else {
            this.lastSequenceIdPublished = -1L;
            this.lastSequenceIdPushed = -1L;
            this.msgIdGenerator = 0L;
        }
		// 为了数据传输安全,加密用的
        if (conf.isEncryptionEnabled()) {
            String logCtx = "[" + topic + "] [" + producerName + "] [" + producerId + "]";
            if (conf.getMessageCrypto() != null) {
                this.msgCrypto = conf.getMessageCrypto();
            } else {
                // default to use MessageCryptoBc;
                MessageCrypto msgCryptoBc;
                try {
                    msgCryptoBc = new MessageCryptoBc(logCtx, true);
                } catch (Exception e) {
                }
                this.msgCrypto = msgCryptoBc;
            }
        } else {
            this.msgCrypto = null;
        }
		// 固定间隔重新生成数据密钥密码
        if (this.msgCrypto != null) {
            // Regenerate data key cipher at fixed interval
            keyGeneratorTask = client.eventLoopGroup().scheduleWithFixedDelay(() -> {
                try {
                    msgCrypto.addPublicKeyCipher(conf.getEncryptionKeys(), conf.getCryptoKeyReader());
                } catch (CryptoException e) {
                }
            }, 0L, 4L, TimeUnit.HOURS);
        }
		// 发送超时控制
        if (conf.getSendTimeoutMs() > 0) {
            sendTimeout = client.timer().newTimeout(this, conf.getSendTimeoutMs(), TimeUnit.MILLISECONDS);
        }

        this.createProducerTimeout = System.currentTimeMillis() + client.getConfiguration().getOperationTimeoutMs();
        // 批量发送
        if (conf.isBatchingEnabled()) {
            BatcherBuilder containerBuilder = conf.getBatcherBuilder();
            if (containerBuilder == null) {
                containerBuilder = BatcherBuilder.DEFAULT;
            }
            this.batchMessageContainer = (BatchMessageContainerBase)containerBuilder.build();
            this.batchMessageContainer.setProducer(this);
        } else {
            this.batchMessageContainer = null;
        }
        // 数据打印频率
        if (client.getConfiguration().getStatsIntervalSeconds() > 0) {
            stats = new ProducerStatsRecorderImpl(client, conf, this);
        } else {
            stats = ProducerStatsDisabled.INSTANCE;
        }

        if (conf.getProperties().isEmpty()) {
            metadata = Collections.emptyMap();
        } else {
            metadata = Collections.unmodifiableMap(new HashMap<>(conf.getProperties()));
        }
		// 当前类引用传入ConnectionHandler
        this.connectionHandler = new ConnectionHandler(this,
        	new BackoffBuilder()
        	    .setInitialTime(client.getConfiguration().getInitialBackoffIntervalNanos(), TimeUnit.NANOSECONDS)
			    .setMax(client.getConfiguration().getMaxBackoffIntervalNanos(), TimeUnit.NANOSECONDS)
			    .setMandatoryStop(Math.max(100, conf.getSendTimeoutMs() - 100), TimeUnit.MILLISECONDS)
			    .create(),
            this);
		// 这个是关键
        grabCnx();
    }

上面介绍了 ProducerImpl 中的关键属性含义,最后构造调用了 grabCnx(),这是创建生产者的核心入口。

    void grabCnx() {
        this.connectionHandler.grabCnx();
    }

2.4 ConnectionHandler实现

ConnectionHandler.grabCnx() 核心代码:

    protected void grabCnx() {
        // 在 ProducerImpl 构造函数里,初始化 ConnectionHandler 时已赋值,默认为 null
        if (CLIENT_CNX_UPDATER.get(this) != null) {
            log.warn("[{}] [{}] Client cnx already set, ignoring reconnection request",
                    state.topic, state.getHandlerName());
            return;
        }
        ......
        
        try {
            // state 和 connection 都是 ProducerImpl 中的对象
            state.client.getConnection(state.topic) // 1. 获取连接
                    .thenAccept(cnx -> connection.connectionOpened(cnx)) // 2.打开连接(包括向broker发送创建producer的请求)
                    .exceptionally(this::handleConnectionError);
        } catch (Throwable t) {
            log.warn("[{}] [{}] Exception thrown while getting connection: ", state.topic, state.getHandlerName(), t);
            reconnectLater(t);
        }
    }

主要包括两部分:1. 获取与 broker 的连接;2. 向 broker 发送创建生产者的请求
1.PulsarClientImpl.getConnection() 核心代码:

    public CompletableFuture<ClientCnx> getConnection(final String topic) {
        TopicName topicName = TopicName.get(topic);
        // lookup对象(即 LookupService) 是 PulsarClient 构造时创建
        return lookup.getBroker(topicName)
                .thenCompose(pair -> getConnection(pair.getLeft(), pair.getRight()));
    }

重点看下 LookupService.getBroker(topicName),即寻找 topic 所属的 broker,大体的逻辑是:

  1. 在配置的 serviceUrl 列表中随机获取一个 Broker 地址,开始寻找 Topic 是否在这个 Broker 中
  2. clientCnx 向 Broker 发送 lookup 请求,如果 Topic 的 owner 不是该 Broker,则需要根据该 Broker 返回的新地址进行重定向请求

2.向 broker 发送创建生产者的请求
当找到 Topic 所属的 Broker 地址后,再回到上面的 grabCnx() 方法,接着会调用 ConnectionHandler.Connection.connectionOpened(cnx),核心代码:

    @Override
    public void connectionOpened(final ClientCnx cnx) {
        ......

        final long epoch;
        cnx.registerProducer(producerId, this);
        long requestId = client.newRequestId();

        SchemaInfo schemaInfo = null;
        if (schema != null) {
            ......
        }

        cnx.sendRequestWithId(
                // 构造创建生产者的命令,携带生产者的配置信息,请求创建
                Commands.newProducer(topic, producerId, requestId, producerName, conf.isEncryptionEnabled(), metadata,
                        schemaInfo, epoch, userProvidedProducerName,
                        conf.getAccessMode(), topicEpoch, client.conf.isEnableTransaction(),
                        conf.getInitialSubscriptionName()),
                requestId).thenAccept(response -> {  // 收到服务端返回的创建结果
                    String producerName = response.getProducerName();
                    long lastSequenceId = response.getLastSequenceId();
                    schemaVersion = Optional.ofNullable(response.getSchemaVersion());
                    schemaVersion.ifPresent(v -> schemaCache.put(SchemaHash.of(schema), v));

                    // We are now reconnected to broker and clear to send messages. Re-send all pending messages and
                    // set the cnx pointer so that new messages will be sent immediately
                    synchronized (ProducerImpl.this) {
                        // 当前生产者关闭
                        if (getState() == State.Closing || getState() == State.Closed) {
                            // Producer was closed while reconnecting, close the connection to make sure the broker
                            // drops the producer on its side
                            cnx.removeProducer(producerId);
                            cnx.channel().close();
                            return;
                        }
                        // 重置
                        resetBackoff();

                        log.info("[{}] [{}] Created producer on cnx {}", topic, producerName, cnx.ctx().channel());
                        connectionId = cnx.ctx().channel().toString();
                        connectedSince = DateFormatter.now();
                        if (conf.getAccessMode() != ProducerAccessMode.Shared && !topicEpoch.isPresent()) {
                            log.info("[{}] [{}] Producer epoch is {}", topic, producerName, response.getTopicEpoch());
                        }
                        topicEpoch = response.getTopicEpoch();

                        if (this.producerName == null) {
                            // 生产者的名字是服务端创建好响应回来的
                            this.producerName = producerName;
                        }

                        if (this.msgIdGenerator == 0 && conf.getInitialSequenceId() == null) {
                            // Only update sequence id generator if it wasn't already modified. That means we only want
                            // to update the id generator the first time the producer gets established, and ignore the
                            // sequence id sent by broker in subsequent producer reconnects
                            // 初始最后一次的SequenceId,可能连接断过
                            this.lastSequenceIdPublished = lastSequenceId;
                            // 初始化应该从Producer上一个SequenceId+1开始
                            this.msgIdGenerator = lastSequenceId + 1;
                        }
                        // 可能之前断过连接,重连后消息需要重发
                        resendMessages(cnx, epoch);
                    }
                }).exceptionally((e) -> {
                    ......
                });
    }

至此,创建生产者的过程就完成了,但是有三个问题没有进一步分析:

  1. 生产写入的 topic 如果没有提前创建,如何处理?
    在服务端:
    (1)如果 zk /managed-ledgers 下没有该 topic,则说明 topic 不存在;
    (2)如果 zk /admin/partitioned-topics下没有该 topic,则返回空metadata,分区数默认0;
    (3)如果 topic 不是带分区的,即 partitionIndex == -1,如: public/default/topic;
    (4)如果开启了自动创建 topic 且类型是分区 topic.
    满足以上4点,则自动创建分区 topic,这里只是把数据写入 zk 的 /admin/partitioned-topics 下。
  2. 寻找 topic 所属 broker,服务端是如何处理的?
    即服务端 lookup 的实现原理,涉及 namespace bundle 的分配。可参考lookup原理
  3. 创建 Producer,服务端是如何处理的?
    创建 Producer 时会先创建 topic,创建 topic 会创建一个 Ledger 用于将来写数据。创建的 Topic 实例维护 Ledger,也会维护Producer列表。

3 sendMessage

3.1 客户端发送

《深入解析 Apache Pulsar》 P49
在这里插入图片描述
Producer 发送数据的入口方法是 ProducerImpl.internalSendWithTxnAsync(),通过 ProducerImpl.sendMessage() 方法对消息、生产者等构建 Send Command,如下:

    protected ByteBufPair sendMessage(long producerId, long sequenceId, int numMessages,
                                      MessageId messageId, MessageMetadata msgMetadata,
                                      ByteBuf compressedPayload) {
        if (messageId instanceof MessageIdImpl) {
            return Commands.newSend(producerId, sequenceId, numMessages, getChecksumType(),
                    ((MessageIdImpl) messageId).getLedgerId(), ((MessageIdImpl) messageId).getEntryId(),
                    msgMetadata, compressedPayload);
        } else {
            return Commands.newSend(producerId, sequenceId, numMessages, getChecksumType(), -1, -1, msgMetadata,
                    compressedPayload);
        }
    }

最终调用 ProducerImpl.processOpSendMsg() 将 Send Command 发送到 broker 服务端。

3.2 服务端写入

《深入解析 Apache Pulsar》 P96
在这里插入图片描述
Broker 端由 ServerCnx 处理客户端发来的请求,包括读写请求,这里写请求通过 ServerCnx.handleSend() 方法处理。

    @Override
    protected void handleSend(CommandSend send, ByteBuf headersAndPayload) {
        checkArgument(state == State.Connected);

        CompletableFuture<Producer> producerFuture = producers.get(send.getProducerId());

        if (producerFuture == null || !producerFuture.isDone() || producerFuture.isCompletedExceptionally()) {
            log.warn("[{}] Received message, but the producer is not ready : {}. Closing the connection.",
                    remoteAddress, send.getProducerId());
            close();
            return;
        }

        Producer producer = producerFuture.getNow(null);
        if (log.isDebugEnabled()) {
            printSendCommandDebug(send, headersAndPayload);
        }

        if (producer.isNonPersistentTopic()) {
            // avoid processing non-persist message if reached max concurrent-message limit
            if (nonPersistentPendingMessages > maxNonPersistentPendingMessages) {
                final long producerId = send.getProducerId();
                final long sequenceId = send.getSequenceId();
                final long highestSequenceId = send.getHighestSequenceId();
                service.getTopicOrderedExecutor().executeOrdered(producer.getTopic().getName(), SafeRun.safeRun(() -> {
                    commandSender.sendSendReceiptResponse(producerId, sequenceId, highestSequenceId, -1, -1);
                }));
                producer.recordMessageDrop(send.getNumMessages());
                return;
            } else {
                nonPersistentPendingMessages++;
            }
        }

        startSendOperation(producer, headersAndPayload.readableBytes(), send.getNumMessages());

        if (send.hasTxnidMostBits() && send.hasTxnidLeastBits()) {
            TxnID txnID = new TxnID(send.getTxnidMostBits(), send.getTxnidLeastBits());
            producer.publishTxnMessage(txnID, producer.getProducerId(), send.getSequenceId(),
                    send.getHighestSequenceId(), headersAndPayload, send.getNumMessages(), send.isIsChunk(),
                    send.isMarker());
            return;
        }

        // This position is only used for shadow replicator
        Position position = send.hasMessageId()
                ? PositionImpl.get(send.getMessageId().getLedgerId(), send.getMessageId().getEntryId()) : null;

        // Persist the message
        if (send.hasHighestSequenceId() && send.getSequenceId() <= send.getHighestSequenceId()) {
            producer.publishMessage(send.getProducerId(), send.getSequenceId(), send.getHighestSequenceId(),
                    headersAndPayload, send.getNumMessages(), send.isIsChunk(), send.isMarker(), position);
        } else {
            producer.publishMessage(send.getProducerId(), send.getSequenceId(), headersAndPayload,
                    send.getNumMessages(), send.isIsChunk(), send.isMarker(), position);
        }
    }

参考文章

  1. Pulsar-Producer实现简介
  2. 服务端Producer消息发送底层实现
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值