
org.apache.rocketmq.client.java.impl.ClientImpl Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.rocketmq.client.java.impl;
import static org.apache.rocketmq.shaded.com.google.common.base.Preconditions.checkNotNull;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.PrintThreadStackTraceCommand;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import apache.rocketmq.v2.RecoverOrphanedTransactionCommand;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.Status;
import apache.rocketmq.v2.TelemetryCommand;
import apache.rocketmq.v2.ThreadStackTrace;
import apache.rocketmq.v2.VerifyMessageCommand;
import apache.rocketmq.v2.VerifyMessageResult;
import org.apache.rocketmq.shaded.com.google.common.base.Function;
import org.apache.rocketmq.shaded.com.google.common.collect.Sets;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.AbstractIdleService;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.FutureCallback;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.Futures;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.ListenableFuture;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.MoreExecutors;
import org.apache.rocketmq.shaded.com.google.common.util.concurrent.SettableFuture;
import org.apache.rocketmq.shaded.com.google.errorprone.annotations.concurrent.GuardedBy;
import org.apache.rocketmq.shaded.io.grpc.Metadata;
import org.apache.rocketmq.shaded.io.grpc.stub.StreamObserver;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import org.apache.rocketmq.client.apis.ClientConfiguration;
import org.apache.rocketmq.client.apis.ClientException;
import org.apache.rocketmq.client.java.exception.InternalErrorException;
import org.apache.rocketmq.client.java.exception.StatusChecker;
import org.apache.rocketmq.client.java.hook.CompositedMessageInterceptor;
import org.apache.rocketmq.client.java.hook.MessageInterceptor;
import org.apache.rocketmq.client.java.hook.MessageInterceptorContext;
import org.apache.rocketmq.client.java.impl.producer.ClientSessionHandler;
import org.apache.rocketmq.client.java.message.GeneralMessage;
import org.apache.rocketmq.client.java.metrics.ClientMeterManager;
import org.apache.rocketmq.client.java.metrics.MessageMeterInterceptor;
import org.apache.rocketmq.client.java.metrics.Metric;
import org.apache.rocketmq.client.java.misc.ClientId;
import org.apache.rocketmq.client.java.misc.ExecutorServices;
import org.apache.rocketmq.client.java.misc.ThreadFactoryImpl;
import org.apache.rocketmq.client.java.misc.Utilities;
import org.apache.rocketmq.client.java.route.Endpoints;
import org.apache.rocketmq.client.java.route.TopicRouteData;
import org.apache.rocketmq.client.java.rpc.RpcFuture;
import org.apache.rocketmq.client.java.rpc.Signature;
import org.apache.rocketmq.shaded.org.slf4j.Logger;
import org.apache.rocketmq.shaded.org.slf4j.LoggerFactory;
@SuppressWarnings({"UnstableApiUsage", "NullableProblems"})
public abstract class ClientImpl extends AbstractIdleService implements Client, ClientSessionHandler,
MessageInterceptor {
private static final Logger log = LoggerFactory.getLogger(ClientImpl.class);
/**
* The telemetry timeout should not be too long, otherwise
* this issue may be triggered in JDK8 + macOS.
*/
private static final Duration TELEMETRY_TIMEOUT = Duration.ofDays(60 * 365);
protected final ClientConfiguration clientConfiguration;
protected final Endpoints endpoints;
protected final Set topics;
// Thread-safe set.
protected final Set isolated;
protected final ExecutorService clientCallbackExecutor;
protected final ClientMeterManager clientMeterManager;
/**
* Telemetry command executor, which aims to execute commands from the remote.
*/
protected final ThreadPoolExecutor telemetryCommandExecutor;
protected final ClientId clientId;
private final ClientManager clientManager;
private volatile ScheduledFuture> updateRouteCacheFuture;
private final ConcurrentMap topicRouteCache;
@GuardedBy("inflightRouteFutureLock")
private final Map>> inflightRouteFutureTable;
private final Lock inflightRouteFutureLock;
@GuardedBy("sessionsLock")
private final Map sessionsTable;
private final ReadWriteLock sessionsLock;
private final CompositedMessageInterceptor compositedMessageInterceptor;
public ClientImpl(ClientConfiguration clientConfiguration, Set topics) {
this.clientConfiguration = checkNotNull(clientConfiguration, "clientConfiguration should not be null");
this.endpoints = new Endpoints(clientConfiguration.getEndpoints());
this.topics = topics;
// Generate client id firstly.
this.clientId = new ClientId();
this.topicRouteCache = new ConcurrentHashMap<>();
this.inflightRouteFutureTable = new ConcurrentHashMap<>();
this.inflightRouteFutureLock = new ReentrantLock();
this.sessionsTable = new HashMap<>();
this.sessionsLock = new ReentrantReadWriteLock();
this.isolated = Collections.newSetFromMap(new ConcurrentHashMap<>());
this.clientManager = new ClientManagerImpl(this);
final long clientIdIndex = clientId.getIndex();
this.clientCallbackExecutor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryImpl("ClientCallbackWorker", clientIdIndex));
this.clientMeterManager = new ClientMeterManager(clientId, clientConfiguration);
this.compositedMessageInterceptor =
new CompositedMessageInterceptor(Collections.singletonList(new MessageMeterInterceptor(this,
clientMeterManager)));
this.telemetryCommandExecutor = new ThreadPoolExecutor(
1,
1,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryImpl("CommandExecutor", clientIdIndex));
}
/**
* Start the rocketmq client and do some preparatory work.
*/
@Override
protected void startUp() throws Exception {
log.info("Begin to start the rocketmq client, clientId={}", clientId);
this.clientManager.startAsync().awaitRunning();
// Fetch topic route from remote.
log.info("Begin to fetch topic(s) route data from remote during client startup, clientId={}, topics={}",
clientId, topics);
for (String topic : topics) {
final ListenableFuture future = fetchTopicRoute(topic);
future.get();
}
log.info("Fetch topic route data from remote successfully during startup, clientId={}, topics={}",
clientId, topics);
// Update route cache periodically.
final ScheduledExecutorService scheduler = clientManager.getScheduler();
this.updateRouteCacheFuture = scheduler.scheduleWithFixedDelay(() -> {
try {
updateRouteCache();
} catch (Throwable t) {
log.error("Exception raised while updating topic route cache, clientId={}", clientId, t);
}
}, 10, 30, TimeUnit.SECONDS);
log.info("The rocketmq client starts successfully, clientId={}", clientId);
}
/**
* Shutdown the rocketmq client and release related resources.
*/
@Override
protected void shutDown() throws InterruptedException {
log.info("Begin to shutdown the rocketmq client, clientId={}", clientId);
notifyClientTermination();
if (null != this.updateRouteCacheFuture) {
updateRouteCacheFuture.cancel(false);
}
telemetryCommandExecutor.shutdown();
if (!ExecutorServices.awaitTerminated(telemetryCommandExecutor)) {
log.error("[Bug] Timeout to shutdown the telemetry command executor, clientId={}", clientId);
} else {
log.info("Shutdown the telemetry command executor successfully, clientId={}", clientId);
}
log.info("Begin to release all telemetry sessions, clientId={}", clientId);
releaseClientSessions();
log.info("Release all telemetry sessions successfully, clientId={}", clientId);
clientManager.stopAsync().awaitTerminated();
clientCallbackExecutor.shutdown();
if (!ExecutorServices.awaitTerminated(clientCallbackExecutor)) {
log.error("[Bug] Timeout to shutdown the client callback executor, clientId={}", clientId);
}
clientMeterManager.shutdown();
log.info("Shutdown the rocketmq client successfully, clientId={}", clientId);
}
protected void addMessageInterceptor(MessageInterceptor messageInterceptor) {
if (!this.isRunning()) {
compositedMessageInterceptor.addInterceptor(messageInterceptor);
}
}
@Override
public void doBefore(MessageInterceptorContext context, List generalMessages) {
try {
compositedMessageInterceptor.doBefore(context, generalMessages);
} catch (Throwable t) {
// Should never reach here.
log.error("[Bug] Exception raised while handling messages, clientId={}", clientId, t);
}
}
@Override
public void doAfter(MessageInterceptorContext context, List generalMessages) {
try {
compositedMessageInterceptor.doAfter(context, generalMessages);
} catch (Throwable t) {
// Should never reach here.
log.error("[Bug] Exception raised while handling messages, clientId={}", clientId, t);
}
}
@Override
public TelemetryCommand settingsCommand() {
final apache.rocketmq.v2.Settings settings = this.getSettings().toProtobuf();
return TelemetryCommand.newBuilder().setSettings(settings).build();
}
@Override
public StreamObserver telemetry(Endpoints endpoints,
StreamObserver observer) throws ClientException {
try {
return clientManager.telemetry(endpoints, TELEMETRY_TIMEOUT, observer);
} catch (ClientException e) {
throw e;
} catch (Throwable t) {
throw new InternalErrorException(t);
}
}
@Override
public boolean isEndpointsDeprecated(Endpoints endpoints) {
final Set totalRouteEndpoints = getTotalRouteEndpoints();
return !totalRouteEndpoints.contains(endpoints);
}
/**
* This method is invoked while request of printing thread stack trace is received from remote.
*
* @param endpoints remote endpoints.
* @param command request of printing thread stack trace from remote.
*/
@Override
public void onPrintThreadStackTraceCommand(Endpoints endpoints, PrintThreadStackTraceCommand command) {
final String nonce = command.getNonce();
Runnable task = () -> {
try {
final String stackTrace = Utilities.stackTrace();
Status status = Status.newBuilder().setCode(Code.OK).build();
ThreadStackTrace threadStackTrace = ThreadStackTrace.newBuilder().setThreadStackTrace(stackTrace)
.setNonce(command.getNonce()).build();
TelemetryCommand telemetryCommand = TelemetryCommand.newBuilder()
.setThreadStackTrace(threadStackTrace)
.setStatus(status)
.build();
telemetry(endpoints, telemetryCommand);
} catch (Throwable t) {
log.error("Failed to send thread stack trace to remote, endpoints={}, nonce={}, clientId={}",
endpoints, nonce, clientId, t);
}
};
try {
telemetryCommandExecutor.submit(task);
} catch (Throwable t) {
log.error("[Bug] Exception raised while submitting task to print thread stack trace, endpoints={}, "
+ "nonce={}, clientId={}", endpoints, nonce, clientId, t);
}
}
public abstract Settings getSettings();
/**
* Apply setting from remote.
*
* @param endpoints remote endpoints.
* @param settings settings received from remote.
*/
@Override
public final void onSettingsCommand(Endpoints endpoints, apache.rocketmq.v2.Settings settings) {
final Metric metric = new Metric(settings.getMetric());
clientMeterManager.reset(metric);
this.getSettings().sync(settings);
}
/**
* @see Client#syncSettings()
*/
@Override
public void syncSettings() {
final apache.rocketmq.v2.Settings settings = getSettings().toProtobuf();
final TelemetryCommand command = TelemetryCommand.newBuilder().setSettings(settings).build();
final Set totalRouteEndpoints = getTotalRouteEndpoints();
for (Endpoints endpoints : totalRouteEndpoints) {
try {
telemetry(endpoints, command);
} catch (Throwable t) {
log.error("Failed to telemeter settings, clientId={}, endpoints={}", clientId, endpoints, t);
}
}
}
public void telemetry(Endpoints endpoints, TelemetryCommand command) {
try {
final ClientSessionImpl clientSession = getClientSession(endpoints);
clientSession.write(command);
} catch (Throwable t) {
log.error("Failed to fire write telemetry command, clientId={}, endpoints={}", clientId, endpoints, t);
}
}
private void releaseClientSessions() {
sessionsLock.readLock().lock();
try {
sessionsTable.values().forEach(ClientSessionImpl::release);
} finally {
sessionsLock.readLock().unlock();
}
}
public void removeClientSession(Endpoints endpoints, ClientSessionImpl clientSession) {
sessionsLock.writeLock().lock();
try {
log.info("Remove client session, clientId={}, endpoints={}", clientId, endpoints);
sessionsTable.remove(endpoints, clientSession);
} finally {
sessionsLock.writeLock().unlock();
}
}
private ClientSessionImpl getClientSession(Endpoints endpoints) throws ClientException {
sessionsLock.readLock().lock();
try {
final ClientSessionImpl session = sessionsTable.get(endpoints);
if (null != session) {
return session;
}
} finally {
sessionsLock.readLock().unlock();
}
sessionsLock.writeLock().lock();
try {
ClientSessionImpl session = sessionsTable.get(endpoints);
if (null != session) {
return session;
}
session = new ClientSessionImpl(this, clientConfiguration.getRequestTimeout(), endpoints);
sessionsTable.put(endpoints, session);
return session;
} finally {
sessionsLock.writeLock().unlock();
}
}
/**
* Triggered when {@link TopicRouteData} is fetched from remote.
*/
public ListenableFuture onTopicRouteDataFetched(String topic,
TopicRouteData topicRouteData) throws ClientException {
final Set routeEndpoints = topicRouteData
.getMessageQueues().stream()
.map(mq -> mq.getBroker().getEndpoints())
.collect(Collectors.toSet());
final Set existRouteEndpoints = getTotalRouteEndpoints();
final Set newEndpoints = new HashSet<>(Sets.difference(routeEndpoints, existRouteEndpoints));
List> futures = new ArrayList<>();
for (Endpoints endpoints : newEndpoints) {
final ClientSessionImpl clientSession = getClientSession(endpoints);
futures.add(clientSession.syncSettings());
}
final ListenableFuture> future = Futures.allAsList(futures);
return Futures.transform(future, (Function
© 2015 - 2025 Weber Informatics LLC | Privacy Policy