io.streamnative.pulsar.handlers.kop.KopBrokerLookupManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pulsar-protocol-handler-kafka Show documentation
Show all versions of pulsar-protocol-handler-kafka Show documentation
Kafka on Pulsar implemented using Pulsar Protocol Handler
/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.streamnative.pulsar.handlers.kop;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.pulsar.broker.PulsarService;
import org.apache.pulsar.broker.resources.MetadataStoreCacheLoader;
import org.apache.pulsar.common.naming.TopicName;
import org.apache.pulsar.policies.data.loadbalancer.LoadManagerReport;
import org.apache.pulsar.policies.data.loadbalancer.ServiceLookupData;
/**
* Kop broker lookup manager.
*/
@Slf4j
public class KopBrokerLookupManager {
private final LookupClient lookupClient;
private final MetadataStoreCacheLoader metadataStoreCacheLoader;
private final String selfAdvertisedListeners;
private final PulsarService pulsar;
private final AtomicBoolean closed = new AtomicBoolean(false);
@VisibleForTesting
@Getter
private final ConcurrentHashMap>
localBrokerTopics = new ConcurrentHashMap<>();
public KopBrokerLookupManager(KafkaServiceConfiguration conf, PulsarService pulsarService,
LookupClient lookupClient) throws Exception {
this.pulsar = pulsarService;
this.lookupClient = lookupClient;
this.metadataStoreCacheLoader = new MetadataStoreCacheLoader(pulsarService.getPulsarResources(),
conf.getBrokerLookupTimeoutMs());
this.selfAdvertisedListeners = conf.getKafkaAdvertisedListeners();
}
public CompletableFuture> findBroker(String topic,
@Nullable EndPoint advertisedEndPoint) {
return getTopicBroker(topic)
.thenApply(internalListenerAddress -> {
if (internalListenerAddress == null) {
log.error("[{}] failed get pulsar address, returned null.", topic);
removeTopicManagerCache(topic);
return Optional.empty();
} else if (log.isDebugEnabled()) {
log.debug("[{}] Found broker's internal listener address: {}",
topic, internalListenerAddress);
}
try {
final String listener = getAdvertisedListener(
internalListenerAddress, topic, advertisedEndPoint);
if (listener == null) {
log.error("Failed to find the advertised listener for {} ", topic);
removeTopicManagerCache(topic);
return Optional.empty();
}
if (log.isDebugEnabled()) {
log.debug("Found listener {} for topic {}", listener, topic);
}
final AdvertisedListener advertisedListener = AdvertisedListener.create(listener);
return Optional.of(new InetSocketAddress(
advertisedListener.getHostname(),
advertisedListener.getPort()));
} catch (IllegalStateException | NumberFormatException e) {
log.error("Failed to find the advertised listener: {}", e.getMessage());
removeTopicManagerCache(topic);
return Optional.empty();
}
});
}
public CompletableFuture getTopicBroker(String topicName) {
if (closed.get()) {
if (log.isDebugEnabled()) {
log.debug("Return null for getTopicBroker({}) since channel closing", topicName);
}
return CompletableFuture.completedFuture(null);
}
if (log.isDebugEnabled()) {
log.debug("Handle Lookup for topic {}", topicName);
}
final CompletableFuture future = localBrokerTopics.get(topicName);
if (future != null && future.isCompletedExceptionally()) {
// this is not possible to happen, but just in case
localBrokerTopics.remove(topicName, future);
}
return (future != null) ? future : lookupBroker(topicName);
}
private CompletableFuture lookupBroker(final String topic) {
if (closed.get()) {
if (log.isDebugEnabled()) {
log.debug("Return null for getTopic({}) since channel closing", topic);
}
return CompletableFuture.completedFuture(null);
}
return lookupClient.getBrokerAddress(TopicName.get(topic));
}
public CompletableFuture isTopicExists(final String topic) {
CompletableFuture future = new CompletableFuture<>();
TopicName topicName = TopicName.get(topic);
this.pulsar.getBrokerService().fetchPartitionedTopicMetadataAsync(TopicName.get(topic))
.whenComplete((metadata, ex) -> {
if (ex != null) {
log.error("Fetch partitioned topic metadata has exception.", ex);
future.complete(true);
return;
}
if (metadata.partitions == 0) {
internalCheckTopicExists(topicName).thenAccept(future::complete)
.exceptionally(throwable -> {
log.error("Check topic exists has exception.", throwable);
future.complete(true);
return null;
});
return;
}
future.complete(true);
});
return future;
}
protected CompletableFuture internalCheckTopicExists(TopicName topicName) {
return this.pulsar.getNamespaceService().checkTopicExists(topicName).thenApply(topicExistsInfo -> {
try {
return topicExistsInfo.isExists();
} finally {
topicExistsInfo.recycle();
}
});
}
private String getAdvertisedListener(InetSocketAddress internalListenerAddress,
String topic,
@Nullable EndPoint advertisedEndPoint) {
final List availableBrokers = metadataStoreCacheLoader.getAvailableBrokers();
if (log.isDebugEnabled()) {
availableBrokers.forEach(loadManagerReport ->
log.debug("Handle getProtocolDataToAdvertise for {}, pulsarUrl: {}, "
+ "pulsarUrlTls: {}, webUrl: {}, webUrlTls: {} kafka: {}",
topic,
loadManagerReport.getPulsarServiceUrl(),
loadManagerReport.getPulsarServiceUrlTls(),
loadManagerReport.getWebServiceUrl(),
loadManagerReport.getWebServiceUrlTls(),
loadManagerReport.getProtocol(KafkaProtocolHandler.PROTOCOL_NAME)));
}
final String hostAndPort = internalListenerAddress.getHostName() + ":" + internalListenerAddress.getPort();
final Optional serviceLookupData = availableBrokers.stream()
.filter(loadManagerReport -> lookupDataContainsAddress(loadManagerReport, hostAndPort)).findAny();
if (!serviceLookupData.isPresent()) {
log.error("No node for broker {} under loadBalance", internalListenerAddress);
return null;
}
return serviceLookupData.get().getProtocol(KafkaProtocolHandler.PROTOCOL_NAME).map(kafkaAdvertisedListeners -> {
if (kafkaAdvertisedListeners.equals(selfAdvertisedListeners)) {
// the topic is owned by this broker, cache the look up result
localBrokerTopics.put(topic, CompletableFuture.completedFuture(internalListenerAddress));
}
return Optional.ofNullable(advertisedEndPoint)
.map(endPoint -> EndPoint.findListener(kafkaAdvertisedListeners, endPoint.getListenerName()))
.orElse(EndPoint.findFirstListener(kafkaAdvertisedListeners));
}).orElseThrow(() -> new IllegalStateException(
"No kafkaAdvertisedListeners found in broker " + internalListenerAddress));
}
// whether a ServiceLookupData contains wanted address.
private static boolean lookupDataContainsAddress(ServiceLookupData data, String hostAndPort) {
return StringUtils.endsWith(data.getPulsarServiceUrl(), hostAndPort)
|| StringUtils.endsWith(data.getPulsarServiceUrlTls(), hostAndPort);
}
public void removeTopicManagerCache(String topicName) {
localBrokerTopics.remove(topicName);
}
public void clear() {
localBrokerTopics.clear();
}
public void close() {
if (!closed.compareAndSet(false, true)) {
if (log.isDebugEnabled()) {
log.debug("Closing KopBrokerLookupManager");
}
return;
}
clear();
try {
metadataStoreCacheLoader.close();
} catch (IOException e) {
log.error("Close metadataStoreCacheLoader failed.", e);
}
}
}