-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[fix][broker]Fixed an issue where the entire subscription would be blocked when a chunk message with an ID of zero did not exist. #25120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
27ccbce
d0eac18
9d2d82f
df84d98
1cc432e
5a17585
ee76a33
6e2ab6e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |||||
|
|
||||||
| import com.google.common.annotations.VisibleForTesting; | ||||||
| import java.util.ArrayList; | ||||||
| import java.util.Collections; | ||||||
| import java.util.IdentityHashMap; | ||||||
| import java.util.List; | ||||||
| import java.util.Map; | ||||||
|
|
@@ -28,6 +29,8 @@ | |||||
| import lombok.Getter; | ||||||
| import lombok.RequiredArgsConstructor; | ||||||
| import lombok.extern.slf4j.Slf4j; | ||||||
| import org.apache.pulsar.broker.service.persistent.PulsarCompactorSubscription; | ||||||
| import org.apache.pulsar.common.api.proto.CommandAck.AckType; | ||||||
| import org.apache.pulsar.common.api.proto.MessageMetadata; | ||||||
|
|
||||||
| /** | ||||||
|
|
@@ -55,7 +58,7 @@ public class SharedConsumerAssignor { | |||||
| private final Subscription subscription; | ||||||
|
|
||||||
| public Map<Consumer, List<EntryAndMetadata>> assign(final List<EntryAndMetadata> entryAndMetadataList, | ||||||
| final int numConsumers) { | ||||||
| final int numConsumers) { | ||||||
| assert numConsumers >= 0; | ||||||
| consumerToPermits.clear(); | ||||||
| final Map<Consumer, List<EntryAndMetadata>> consumerToEntries = new IdentityHashMap<>(); | ||||||
|
|
@@ -86,17 +89,13 @@ public Map<Consumer, List<EntryAndMetadata>> assign(final List<EntryAndMetadata> | |||||
| availablePermits = consumer.getAvailablePermits(); | ||||||
| } | ||||||
|
|
||||||
| if (metadata == null || !metadata.hasUuid() || !metadata.hasChunkId() || !metadata.hasNumChunksFromMsg()) { | ||||||
| consumerToEntries.computeIfAbsent(consumer, __ -> new ArrayList<>()).add(entryAndMetadata); | ||||||
| if (!isChunkedMessage(metadata)) { | ||||||
| addEntry(consumerToEntries, consumer, entryAndMetadata); | ||||||
| availablePermits--; | ||||||
| } else { | ||||||
| final Consumer consumerForUuid = getConsumerForUuid(metadata, consumer, availablePermits); | ||||||
| if (consumerForUuid == null) { | ||||||
| unassignedMessageProcessor.accept(entryAndMetadata); | ||||||
| continue; | ||||||
| } | ||||||
| consumerToEntries.computeIfAbsent(consumerForUuid, __ -> new ArrayList<>()).add(entryAndMetadata); | ||||||
| availablePermits = assignChunk(entryAndMetadata, metadata, consumer, consumerToEntries, | ||||||
| availablePermits); | ||||||
| } | ||||||
| availablePermits--; | ||||||
| } | ||||||
|
|
||||||
| for (; index < entryAndMetadataList.size(); index++) { | ||||||
|
|
@@ -106,6 +105,64 @@ public Map<Consumer, List<EntryAndMetadata>> assign(final List<EntryAndMetadata> | |||||
| return consumerToEntries; | ||||||
| } | ||||||
|
|
||||||
| private int assignChunk(EntryAndMetadata entryAndMetadata, MessageMetadata metadata, Consumer consumer, | ||||||
| Map<Consumer, List<EntryAndMetadata>> consumerToEntries, int availablePermits) { | ||||||
| final String uuid = metadata.getUuid(); | ||||||
| Consumer consumerForUuid = uuidToConsumer.get(uuid); | ||||||
| if (consumerForUuid == null) { | ||||||
| if (skipChunk(entryAndMetadata, metadata)) { | ||||||
| return availablePermits; | ||||||
| } | ||||||
| consumerForUuid = consumer; | ||||||
| uuidToConsumer.put(uuid, consumerForUuid); | ||||||
| } | ||||||
|
|
||||||
| final int permits = consumerToPermits.computeIfAbsent(consumerForUuid, Consumer::getAvailablePermits); | ||||||
| if (permits <= 0) { | ||||||
| unassignedMessageProcessor.accept(entryAndMetadata); | ||||||
| return availablePermits; | ||||||
| } | ||||||
| if (metadata.getChunkId() == metadata.getNumChunksFromMsg() - 1) { | ||||||
| // The last chunk is received, we should remove the uuid from the cache. | ||||||
| uuidToConsumer.remove(uuid); | ||||||
| } | ||||||
|
|
||||||
| addEntry(consumerToEntries, consumerForUuid, entryAndMetadata); | ||||||
| consumerToPermits.put(consumerForUuid, permits - 1); | ||||||
| if (consumerForUuid == consumer) { | ||||||
| return availablePermits - 1; | ||||||
| } | ||||||
| return availablePermits; | ||||||
| } | ||||||
|
|
||||||
| private boolean isChunkedMessage(MessageMetadata metadata) { | ||||||
| return metadata != null && metadata.hasUuid() && metadata.hasChunkId() && metadata.hasNumChunksFromMsg(); | ||||||
| } | ||||||
|
|
||||||
| private void addEntry(Map<Consumer, List<EntryAndMetadata>> consumerToEntries, Consumer consumer, | ||||||
| EntryAndMetadata entry) { | ||||||
| consumerToEntries.computeIfAbsent(consumer, __ -> new ArrayList<>()).add(entry); | ||||||
| } | ||||||
|
|
||||||
| private boolean skipChunk(EntryAndMetadata entryAndMetadata, MessageMetadata metadata) { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if (metadata.getChunkId() != 0) { | ||||||
| if (subscription != null) { | ||||||
| log.warn("[{}][{}] Skip the message because it is not the first chunk." | ||||||
| + " Position: {}, UUID: {}, ChunkId: {}, NumChunksFromMsg: {}", | ||||||
| subscription.getTopicName(), subscription.getName(), entryAndMetadata.getPosition(), | ||||||
| metadata.getUuid(), metadata.getChunkId(), metadata.getNumChunksFromMsg()); | ||||||
| // Directly ack the message. | ||||||
| if (!(subscription instanceof PulsarCompactorSubscription)) { | ||||||
| subscription.acknowledgeMessage(Collections.singletonList( | ||||||
| entryAndMetadata.getPosition()), AckType.Individual, Collections.emptyMap()); | ||||||
| } | ||||||
| } | ||||||
| entryAndMetadata.release(); | ||||||
| return true; | ||||||
|
Comment on lines
+149
to
+161
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there's a potential for data loss, acknowledging the messages should be active only if |
||||||
| } | ||||||
| return false; | ||||||
| } | ||||||
|
|
||||||
| private Consumer getConsumer(final int numConsumers) { | ||||||
| for (int i = 0; i < numConsumers; i++) { | ||||||
| final Consumer consumer = defaultSelector.get(); | ||||||
|
|
@@ -119,29 +176,4 @@ private Consumer getConsumer(final int numConsumers) { | |||||
| } | ||||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| private Consumer getConsumerForUuid(final MessageMetadata metadata, | ||||||
| final Consumer defaultConsumer, | ||||||
| final int currentAvailablePermits) { | ||||||
| final String uuid = metadata.getUuid(); | ||||||
| Consumer consumer = uuidToConsumer.get(uuid); | ||||||
| if (consumer == null) { | ||||||
| if (metadata.getChunkId() != 0) { | ||||||
| // Not the first chunk, skip it | ||||||
| return null; | ||||||
| } | ||||||
| consumer = defaultConsumer; | ||||||
| uuidToConsumer.put(uuid, consumer); | ||||||
| } | ||||||
| final int permits = consumerToPermits.computeIfAbsent(consumer, Consumer::getAvailablePermits); | ||||||
| if (permits <= 0) { | ||||||
| return null; | ||||||
| } | ||||||
| if (metadata.getChunkId() == metadata.getNumChunksFromMsg() - 1) { | ||||||
| // The last chunk is received, we should remove the cache | ||||||
| uuidToConsumer.remove(uuid); | ||||||
| } | ||||||
| consumerToPermits.put(consumer, currentAvailablePermits - 1); | ||||||
| return consumer; | ||||||
| } | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this solution would only skip the first entry of possibly multiple chunk entries.
Let's say if entry with chunkId 0 got lost and there would be subsequent entries chunkId 1, chunkId 2 and chunkId 3. The entries with chunkId 2 and chunkId 3 would get delivered to the client, causing a similar issue.