client处理逻辑
ConnectionPool
netty相关配置在com.yahoo.pulsar.client.impl.ConnectionPool
public ConnectionPool(final PulsarClientImpl client, EventLoopGroup eventLoopGroup) {
this.eventLoopGroup = eventLoopGroup;
this.maxConnectionsPerHosts = client.getConfiguration().getConnectionsPerBroker();
pool = new ConcurrentHashMap<>();
bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
if (SystemUtils.IS_OS_LINUX && eventLoopGroup instanceof EpollEventLoopGroup) {
bootstrap.channel(EpollSocketChannel.class);
} else {
bootstrap.channel(NioSocketChannel.class);
}
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
bootstrap.option(ChannelOption.TCP_NODELAY, client.getConfiguration().isUseTcpNoDelay());
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) throws Exception {
ClientConfiguration clientConfig = client.getConfiguration();
if (clientConfig.isUseTls()) {
SslContextBuilder builder = SslContextBuilder.forClient();
if (clientConfig.isTlsAllowInsecureConnection()) {
builder.trustManager(InsecureTrustManagerFactory.INSTANCE);
} else {
if (clientConfig.getTlsTrustCertsFilePath().isEmpty()) {
// Use system default
builder.trustManager((File) null);
} else {
File trustCertCollection = new File(clientConfig.getTlsTrustCertsFilePath());
builder.trustManager(trustCertCollection);
}
}
// Set client certificate if available
AuthenticationDataProvider authData = clientConfig.getAuthentication().getAuthData();
if (authData.hasDataForTls()) {
builder.keyManager(authData.getTlsPrivateKey(),
(X509Certificate[]) authData.getTlsCertificates());
}
SslContext sslCtx = builder.build();
ch.pipeline().addLast(TLS_HANDLER, sslCtx.newHandler(ch.alloc()));
}
ch.pipeline().addLast("frameDecoder", new PulsarLengthFieldFrameDecoder(MaxMessageSize, 0, 4, 0, 4));
ch.pipeline().addLast("handler", new ClientCnx(client));
}
});
}
messageReceived方法
void messageReceived(MessageIdData messageId, ByteBuf headersAndPayload, ClientCnx cnx) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Received message: {}", topic, subscription, messageId);
}
MessageMetadata msgMetadata = null;
ByteBuf payload = headersAndPayload;
try {
msgMetadata = Commands.parseMessageMetadata(payload);
} catch (Throwable t) {
discardCorruptedMessage(messageId, cnx, ValidationError.ChecksumMismatch);
return;
}
ByteBuf uncompressedPayload = uncompressPayloadIfNeeded(messageId, msgMetadata, payload, cnx);
if (uncompressedPayload == null) {
// Message was discarded on decompression error
return;
}
if (!verifyChecksum(messageId, msgMetadata, uncompressedPayload, cnx)) {
// Message discarded for checksum error
return;
}
final int numMessages = msgMetadata.getNumMessagesInBatch();
if (numMessages == 1 && !msgMetadata.hasNumMessagesInBatch()) {
final MessageImpl message = new MessageImpl(messageId, msgMetadata, uncompressedPayload,
getPartitionIndex(), cnx);
uncompressedPayload.release();
msgMetadata.recycle();
try {
lock.readLock().lock();
// Enqueue the message so that it can be retrieved when application calls receive()
// if the conf.getReceiverQueueSize() is 0 then discard message if no one is waiting for it.
// if asyncReceive is waiting then notify callback without adding to incomingMessages queue
boolean asyncReceivedWaiting = !pendingReceives.isEmpty();
if ((conf.getReceiverQueueSize() != 0 || waitingOnReceiveForZeroQueueSize) && !asyncReceivedWaiting) {
incomingMessages.add(message);
}
if (asyncReceivedWaiting) {
notifyPendingReceivedCallback(message, null);
}
} finally {
lock.readLock().unlock();
}
} else {
if (conf.getReceiverQueueSize() == 0) {
log.warn(
"Closing consumer [{}]-[{}] due to unsupported received batch-message with zero receiver queue size",
subscription, consumerName);
// close connection
closeAsync().handle((ok, e) -> {
// notify callback with failure result
notifyPendingReceivedCallback(null,
new PulsarClientException.InvalidMessageException(
format("Unsupported Batch message with 0 size receiver queue for [%s]-[%s] ",
subscription, consumerName)));
return null;
});
} else {
// handle batch message enqueuing; uncompressed payload has all messages in batch
receiveIndividualMessagesFromBatch(msgMetadata, uncompressedPayload, messageId, cnx);
}
uncompressedPayload.release();
msgMetadata.recycle();
}
stats.incrementNumAcksTracker(numMessages);
if (listener != null) {
// Trigger the notification on the message listener in a separate thread to avoid blocking the networking
// thread while the message processing happens
listenerExecutor.execute(() -> {
for (int i = 0; i < numMessages; i++) {
Message msg;
try {
msg = internalReceive();
} catch (PulsarClientException e) {
log.warn("[{}] [{}] Failed to dequeue the message for listener", topic, subscription, e);
return;
}
try {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Calling message listener for message {}", topic, subscription, msg);
}
listener.received(ConsumerImpl.this, msg);
} catch (Throwable t) {
log.error("[{}][{}] Message listener error in processing message: {}", topic, subscription, msg,
t);
}
}
});
}
}
消息未压缩大小超过限制直接丢弃
private ByteBuf uncompressPayloadIfNeeded(MessageIdData messageId, MessageMetadata msgMetadata, ByteBuf payload,
ClientCnx currentCnx) {
CompressionType compressionType = msgMetadata.getCompression();
CompressionCodec codec = codecProvider.getCodec(compressionType);
int uncompressedSize = msgMetadata.getUncompressedSize();
if (uncompressedSize > PulsarDecoder.MaxMessageSize) {
// Uncompressed size is itself corrupted since it cannot be bigger than the MaxMessageSize
log.error("[{}][{}] Got corrupted uncompressed message size {} at {}", topic, subscription,
uncompressedSize, messageId);
discardCorruptedMessage(messageId, currentCnx, ValidationError.UncompressedSizeCorruption);
return null;
}
try {
ByteBuf uncompressedPayload = codec.decode(payload, uncompressedSize);
return uncompressedPayload;
} catch (IOException e) {
log.error("[{}][{}] Failed to decompress message with {} at {}: {}", topic, subscription, compressionType,
messageId, e.getMessage(), e);
discardCorruptedMessage(messageId, currentCnx, ValidationError.DecompressionError);
return null;
}
}
discardCorruptedMessage
private void discardCorruptedMessage(MessageIdData messageId, ClientCnx currentCnx,
ValidationError validationError) {
log.error("[{}][{}] Discarding corrupted message at {}:{}", topic, subscription, messageId.getLedgerId(),
messageId.getEntryId());
ByteBuf cmd = Commands.newAck(consumerId, messageId.getLedgerId(), messageId.getEntryId(), AckType.Individual,
validationError);
currentCnx.ctx().writeAndFlush(cmd, currentCnx.ctx().voidPromise());
increaseAvailablePermits(currentCnx);
stats.incrementNumReceiveFailed();
}
接收消息到incomingMessages(BlockingQueue<Message>
)
在配置文件指定queue的大小,或者使用GrowableArrayBlockingQueue
protected ConsumerBase(PulsarClientImpl client, String topic, String subscription, ConsumerConfiguration conf,
ExecutorService listenerExecutor, CompletableFuture<Consumer> subscribeFuture, boolean useGrowableQueue) {
super(client, topic);
this.subscription = subscription;
this.conf = conf;
this.consumerName = conf.getConsumerName() == null
? DigestUtils.sha1Hex(UUID.randomUUID().toString()).substring(0, 5) : conf.getConsumerName();
this.subscribeFuture = subscribeFuture;
this.listener = conf.getMessageListener();
if (conf.getReceiverQueueSize() <= 1) {
this.incomingMessages = Queues.newArrayBlockingQueue(1);
} else if (useGrowableQueue) {
this.incomingMessages = new GrowableArrayBlockingQueue<>();
} else {
this.incomingMessages = Queues.newArrayBlockingQueue(conf.getReceiverQueueSize());
}
this.listenerExecutor = listenerExecutor;
this.pendingReceives = Queues.newConcurrentLinkedQueue();
if (conf.getAckTimeoutMillis() != 0) {
this.unAckedMessageTracker = new UnAckedMessageTracker();
this.unAckedMessageTracker.start(client, this, conf.getAckTimeoutMillis());
} else {
this.unAckedMessageTracker = null;
}
}
接收消息采用add方法,如果队列满了,则会抛出异常。
批量接收消息
void receiveIndividualMessagesFromBatch(MessageMetadata msgMetadata, ByteBuf uncompressedPayload,
MessageIdData messageId, ClientCnx cnx) {
int batchSize = msgMetadata.getNumMessagesInBatch();
// create ack tracker for entry aka batch
BitSet bitSet = new BitSet(batchSize);
MessageIdImpl batchMessage = new MessageIdImpl(messageId.getLedgerId(), messageId.getEntryId(),
getPartitionIndex());
bitSet.set(0, batchSize);
if (log.isDebugEnabled()) {
log.debug("[{}] [{}] added bit set for message {}, cardinality {}, length {}", subscription, consumerName,
batchMessage, bitSet.cardinality(), bitSet.length());
}
batchMessageAckTracker.put(batchMessage, bitSet);
try {
for (int i = 0; i < batchSize; ++i) {
if (log.isDebugEnabled()) {
log.debug("[{}] [{}] processing message num - {} in batch", subscription, consumerName, i);
}
PulsarApi.SingleMessageMetadata.Builder singleMessageMetadataBuilder = PulsarApi.SingleMessageMetadata
.newBuilder();
ByteBuf singleMessagePayload = Commands.deSerializeSingleMessageInBatch(uncompressedPayload,
singleMessageMetadataBuilder, i, batchSize);
BatchMessageIdImpl batchMessageIdImpl = new BatchMessageIdImpl(messageId.getLedgerId(),
messageId.getEntryId(), getPartitionIndex(), i);
final MessageImpl message = new MessageImpl(batchMessageIdImpl, msgMetadata,
singleMessageMetadataBuilder.build(), singleMessagePayload, cnx);
lock.readLock().lock();
if (pendingReceives.isEmpty()) {
incomingMessages.add(message);
} else {
notifyPendingReceivedCallback(message, null);
}
lock.readLock().unlock();
singleMessagePayload.release();
singleMessageMetadataBuilder.recycle();
}
} catch (IOException e) {
//
log.warn("[{}] [{}] unable to obtain message in batch", subscription, consumerName);
batchMessageAckTracker.remove(batchMessage);
discardCorruptedMessage(messageId, cnx, ValidationError.BatchDeSerializeError);
}
if (log.isDebugEnabled()) {
log.debug("[{}] [{}] enqueued messages in batch. queue size - {}, available queue size - {}", subscription,
consumerName, incomingMessages.size(), incomingMessages.remainingCapacity());
}
}
消息的推和拉模式
kafka的消费者的拉模式
kafka的消费者是采用拉的模式,在zk里头保存消息的offset,由消费者自己去拉取数据,这样可以避免生产者速度过快,而采用推的模式可能会有消费者处理消息处理不过来的问题。
pulsar的消费者的推模式
跟rabbitmq类似,是broker推送消息给consumer。如果consumer不在,因为消息在pulsar是持久化的,所以消息还在,等consumer在线的时候,可以接收离线的消息。因为消息是持久化的,所以不会有内存积压问题。不过有个问题,如果消息量过多,超过了consumer的处理速度,那么consumer会不会崩溃掉?
receiverQueueSize
注意下这个参数:receiverQueueSize
this.receiverQueueRefillThreshold = conf.getReceiverQueueSize() / 2;
一开始consumer启动时,跟broker建立了连接,会给broker发送fow命令,开始接受数据
if (!(firstTimeConnect && partitionIndex > -1) && conf.getReceiverQueueSize() != 0) {
receiveMessages(cnx, conf.getReceiverQueueSize());
}
void receiveMessages(ClientCnx cnx, int numMessages) {
if (cnx != null) {
if (log.isDebugEnabled()) {
log.debug("[{}] [{}] Adding {} additional permits", topic, subscription, numMessages);
}
cnx.ctx().writeAndFlush(Commands.newFlow(consumerId, numMessages), cnx.ctx().voidPromise());
}
}
FlowCommand告诉broker,需要最多可以推送多少数据过来,如果有超过receiverQueueSize大小的消息,则只推送这么多,如果没有这么多,则按实际多少推送给consumer。这样就解决了消费者处理速度的问题,第一次启动是最多只让broker推送receiverQueueSize大小的消息,然后当消费到一半的时候,再发送flow命名,请求最多receiverQueueSize/2大小的消息推送过来。
consumer.receive()
public Message receive() throws PulsarClientException {
if (listener != null) {
throw new PulsarClientException.InvalidConfigurationException(
"Cannot use receive() when a listener has been set");
}
switch (state.get()) {
case Ready:
case Connecting:
break; // Ok
case Closing:
case Closed:
throw new PulsarClientException.AlreadyClosedException("Consumer already closed");
case Failed:
case Uninitialized:
throw new PulsarClientException.NotConnectedException();
}
return internalReceive();
}
internalReceive
protected Message internalReceive() throws PulsarClientException {
if (conf.getReceiverQueueSize() == 0) {
return fetchSingleMessageFromBroker();
}
Message message;
try {
message = incomingMessages.take();
messageProcessed(message);
if (unAckedMessageTracker != null) {
unAckedMessageTracker.add((MessageIdImpl) message.getMessageId());
}
return message;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
stats.incrementNumReceiveFailed();
throw new PulsarClientException(e);
}
}
messageProcessed
private synchronized void messageProcessed(Message msg) {
ClientCnx currentCnx = cnx();
ClientCnx msgCnx = ((MessageImpl) msg).getCnx();
if (msgCnx != currentCnx) {
// The processed message did belong to the old queue that was cleared after reconnection.
return;
}
increaseAvailablePermits(currentCnx);
stats.updateNumMsgsReceived(msg);
}
increaseAvailablePermits(receiverQueueRefillThreshold
)
这里有个参数receiverQueueRefillThreshold,他是receiverQueueSize的一半,也就是当消费到队列的一半时,再次给broker发送flow命名,请求broker去推送消息。
private void increaseAvailablePermits(ClientCnx currentCnx) {
int available = availablePermits.incrementAndGet();
while (available >= receiverQueueRefillThreshold) {
if (availablePermits.compareAndSet(available, 0)) {
receiveMessages(currentCnx, available);
break;
} else {
available = availablePermits.get();
}
}
}
availablePermits: The number of messages this consumer has space for in the client library's listen queue. A value of 0 means the client library's queue is full and receive() isn't being called. A nonzero value means this consumer is ready to be dispatched messages.
消息的删除问题
消费者消费完消息之后,持久化的消息会删除掉么?消费者可以进行消息回溯么?
消息发送到指定topic的时候,如果没有consumer已经创建subscribe的话,在此之前发送的消息,该consumer是接收不到的。既然如此,那么消息也就没有存在的必要,应该是有删除的逻辑。实际上,一旦消息ack了,那么broker就会删除掉。
pulsar/pulsar-broker/src/main/java/com/yahoo/pulsar/broker/service/persistent/PersistentSubscription.java
@Override
public void acknowledgeMessage(PositionImpl position, AckType ackType) {
if (ackType == AckType.Cumulative) {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Cumulative ack on {}", topicName, subName, position);
}
cursor.asyncMarkDelete(position, markDeleteCallback, position);
} else {
if (log.isDebugEnabled()) {
log.debug("[{}][{}] Individual ack on {}", topicName, subName, position);
}
cursor.asyncDelete(position, deleteCallback, position);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。