refactor: [ISSUE #3949] v2 support

This commit is contained in:
kaiyi.lk
2022-07-13 11:29:33 +08:00
committed by zhouxiang
parent d6720b5689
commit e549a67be3
176 changed files with 10114 additions and 6114 deletions
@@ -248,7 +248,7 @@ public class BrokerConfig extends BrokerIdentity {
* Transaction message check interval.
*/
@ImportantField
private long transactionCheckInterval = 60 * 1000;
private long transactionCheckInterval = 3 * 1000;
/**
* Acl feature switch
@@ -39,7 +39,7 @@ public class MessageBatch extends Message implements Iterable<Message> {
return messages.iterator();
}
public static MessageBatch generateFromList(Collection<Message> messages) {
public static MessageBatch generateFromList(Collection<? extends Message> messages) {
assert messages != null;
assert messages.size() > 0;
List<Message> messageList = new ArrayList<Message>(messages.size());
@@ -0,0 +1,57 @@
/*
* 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.proxy;
public enum ProxyMode {
LOCAL("LOCAL"),
CLUSTER("CLUSTER");
private final String mode;
ProxyMode(String mode) {
this.mode = mode;
}
public static boolean isClusterMode(String mode) {
if (mode == null) {
return false;
}
return CLUSTER.mode.equals(mode.toUpperCase());
}
public static boolean isClusterMode(ProxyMode mode) {
if (mode == null) {
return false;
}
return CLUSTER.equals(mode);
}
public static boolean isLocalMode(String mode) {
if (mode == null) {
return false;
}
return LOCAL.mode.equals(mode.toUpperCase());
}
public static boolean isLocalMode(ProxyMode mode) {
if (mode == null) {
return false;
}
return LOCAL.equals(mode);
}
}
@@ -36,11 +36,9 @@ import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.config.ProxyConfig;
import org.apache.rocketmq.proxy.grpc.GrpcServer;
import org.apache.rocketmq.proxy.grpc.GrpcServerBuilder;
import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingProcessor;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyMode;
import org.apache.rocketmq.proxy.grpc.v2.service.ClusterGrpcService;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcForwardService;
import org.apache.rocketmq.proxy.grpc.v2.service.LocalGrpcService;
import org.apache.rocketmq.proxy.grpc.v2.GrpcMessagingApplication;
import org.apache.rocketmq.proxy.service.ServiceManager;
import org.apache.rocketmq.remoting.RPCHook;
import org.slf4j.LoggerFactory;
public class ProxyStartup {
@@ -65,9 +63,11 @@ public class ProxyStartup {
ThreadPoolExecutor executor = createServerExecutor();
ServiceManager serviceManager = createServiceManager(null);
// create grpcServer
GrpcServer grpcServer = GrpcServerBuilder.newBuilder(executor)
.addService(createServiceProcessor())
.addService(createServiceProcessor(serviceManager))
.configInterceptor()
.build();
PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcServer);
@@ -94,11 +94,12 @@ public class ProxyStartup {
log.info(new Date() + " rmq-proxy startup successfully");
}
private static GrpcMessagingProcessor createServiceProcessor() {
GrpcForwardService grpcService;
private static ServiceManager createServiceManager(RPCHook rpcHook) {
String proxyModeStr = ConfigurationManager.getProxyConfig().getProxyMode();
ServiceManager serviceManager;
if (ProxyMode.isClusterMode(proxyModeStr)) {
grpcService = new ClusterGrpcService();
serviceManager = ServiceManager.createForClusterMode(rpcHook);
} else if (ProxyMode.isLocalMode(proxyModeStr)) {
BrokerController brokerController = createBrokerController();
StartAndShutdown brokerControllerWrapper = new StartAndShutdown() {
@@ -113,13 +114,18 @@ public class ProxyStartup {
}
};
PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(brokerControllerWrapper);
grpcService = new LocalGrpcService(brokerController);
serviceManager = ServiceManager.createForLocalMode(brokerController, rpcHook);
} else {
throw new IllegalArgumentException("try to start grpc server with wrong mode, use 'local' or 'cluster'");
}
PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(serviceManager);
return serviceManager;
}
PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(grpcService);
return new GrpcMessagingProcessor(grpcService);
private static GrpcMessagingApplication createServiceProcessor(ServiceManager serviceManager) {
GrpcMessagingApplication application = GrpcMessagingApplication.create(serviceManager);
PROXY_START_AND_SHUTDOWN.appendStartAndShutdown(application);
return application;
}
private static BrokerController createBrokerController() {
@@ -1,135 +0,0 @@
/*
* 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.proxy.channel;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.GrpcClientChannel;
public class ChannelManager {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final ConcurrentMap<String /* clientId */, SimpleChannel> clientIdChannelMap = new ConcurrentHashMap<>();
protected final ConcurrentMap<String /* group */, Set<String>/* clientId */> groupClientIdMap = new ConcurrentHashMap<>();
public SimpleChannel createChannel(Context ctx) {
return createChannel(ctx, anonymousChannelId(ctx));
}
public SimpleChannel createChannel(Context ctx, String clientId) {
return createChannel(ctx, clientId, ChannelManager::createSimpleChannelDirectly, SimpleChannel.class);
}
public <T extends SimpleChannel> T createChannel(Context ctx, Function<Context, T> creator, Class<T> clazz) {
return createChannel(ctx, anonymousChannelId(ctx, clazz.getName()), creator, clazz);
}
public <T extends SimpleChannel> T createChannel(Context ctx, String clientId, Function<Context, T> creator, Class<T> clazz) {
if (StringUtils.isBlank(clientId)) {
log.warn("ClientId is unexpected null or empty");
return creator.apply(ctx);
}
clientIdChannelMap.computeIfAbsent(clientId, key -> creator.apply(ctx));
T channel = clazz.cast(clientIdChannelMap.get(clientId));
channel.updateLastAccessTime();
return channel;
}
public <T extends SimpleChannel> T getChannel(String clientId, Class<T> clazz) {
SimpleChannel channel = clientIdChannelMap.get(clientId);
if (channel == null) {
return null;
}
return clazz.cast(channel);
}
public <T extends SimpleChannel> void setChannel(String clientId, T channel) {
clientIdChannelMap.put(clientId, channel);
}
public <T extends SimpleChannel> T removeChannel(String clientId, Class<T> clazz) {
SimpleChannel channel = clientIdChannelMap.remove(clientId);
if (channel == null) {
return null;
}
return clazz.cast(channel);
}
private String anonymousChannelId(Context ctx) {
final String clientHost = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.REMOTE_ADDRESS);
final String localAddress = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.LOCAL_ADDRESS);
return clientHost + "@" + localAddress;
}
private String anonymousChannelId(Context ctx, String className) {
final String clientHost = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.REMOTE_ADDRESS);
final String localAddress = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.LOCAL_ADDRESS);
return className + "@" + clientHost + "@" + localAddress;
}
public static SimpleChannel createSimpleChannelDirectly(Context ctx) {
final String clientHost = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.REMOTE_ADDRESS);
final String localAddress = InterceptorConstants.METADATA.get(ctx)
.get(InterceptorConstants.LOCAL_ADDRESS);
return new SimpleChannel(null, clientHost, localAddress, ConfigurationManager.getProxyConfig().getChannelExpiredInSeconds());
}
public void addGroupClientId(String group, String clientId) {
groupClientIdMap.computeIfAbsent(group, k -> Collections.newSetFromMap(new ConcurrentHashMap<>()))
.add(clientId);
}
public List<String> getClientIdList(String group) {
return new ArrayList<>(groupClientIdMap.get(group));
}
public void onClientOffline(String clientId) {
SimpleChannel simpleChannel = clientIdChannelMap.remove(clientId);
if (simpleChannel == null) {
return;
}
if (simpleChannel instanceof GrpcClientChannel) {
GrpcClientChannel grpcClientChannel = (GrpcClientChannel) simpleChannel;
groupClientIdMap.computeIfPresent(grpcClientChannel.getGroup(), (group, clientIds) -> {
clientIds.remove(grpcClientChannel.getClientId());
if (clientIds.isEmpty()) {
return null;
}
return clientIds;
});
}
}
}
@@ -1,83 +0,0 @@
/*
* 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.proxy.channel;
import io.grpc.Context;
import io.netty.channel.ChannelFuture;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.rocketmq.proxy.common.Cleaner;
import org.apache.rocketmq.proxy.grpc.v2.adapter.handler.ResponseHandler;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public abstract class InvocationChannel<R, W> extends SimpleChannel implements Cleaner {
protected final ConcurrentMap<Integer, InvocationContext<R, W>> inFlightRequestMap;
protected final ResponseHandler<R, W> handler;
public InvocationChannel(Context ctx, ResponseHandler<R, W> handler) {
super(ChannelManager.createSimpleChannelDirectly(ctx));
this.inFlightRequestMap = new ConcurrentHashMap<>();
this.handler = handler;
}
@Override
public ChannelFuture writeAndFlush(Object msg) {
if (msg instanceof RemotingCommand) {
RemotingCommand responseCommand = (RemotingCommand) msg;
InvocationContext<R, W> context = inFlightRequestMap.remove(responseCommand.getOpaque());
if (null != context) {
handler.handle(responseCommand, context);
}
inFlightRequestMap.remove(responseCommand.getOpaque());
}
return super.writeAndFlush(msg);
}
@Override
public boolean isWritable() {
return inFlightRequestMap.size() > 0;
}
public void registerInvocationContext(int opaque, InvocationContext<R, W> context) {
inFlightRequestMap.put(opaque, context);
}
public void eraseInvocationContext(int opaque) {
inFlightRequestMap.remove(opaque);
}
@Override
public void clean() {
Iterator<Map.Entry<Integer, InvocationContext<R, W>>> iterator = inFlightRequestMap.entrySet().iterator();
int count = 0;
while (iterator.hasNext()) {
Map.Entry<Integer, InvocationContext<R, W>> entry = iterator.next();
if (entry.getValue().expired(expiredTimeSec)) {
iterator.remove();
count++;
log.debug("An expired request is found, created time-point: {}, Request: {}",
entry.getValue().getTimestamp(), entry.getValue().getRequest());
}
}
if (count > 0) {
log.warn("[BUG] {} expired in-flight requests is cleaned.", count);
}
}
}
@@ -14,24 +14,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.common;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import com.google.common.net.HostAndPort;
public class TelemetryCommandManager {
protected final ConcurrentMap<String, TelemetryCommandRecord> commandTable = new ConcurrentHashMap<>();
protected final AtomicLong commandIdGenerator = new AtomicLong(0);
public class Address {
public String putCommand(int opaque) {
String nonce = String.valueOf(commandIdGenerator.incrementAndGet());
commandTable.put(nonce, new TelemetryCommandRecord(nonce, opaque));
return nonce;
public enum AddressScheme {
IPv4,
IPv6,
DOMAIN_NAME,
UNRECOGNIZED
}
public TelemetryCommandRecord getCommand(String commandId) {
return commandTable.get(commandId);
private AddressScheme addressScheme;
private HostAndPort hostAndPort;
public Address(AddressScheme addressScheme, HostAndPort hostAndPort) {
this.addressScheme = addressScheme;
this.hostAndPort = hostAndPort;
}
public AddressScheme getAddressScheme() {
return addressScheme;
}
public void setAddressScheme(AddressScheme addressScheme) {
this.addressScheme = addressScheme;
}
public HostAndPort getHostAndPort() {
return hostAndPort;
}
public void setHostAndPort(HostAndPort hostAndPort) {
this.hostAndPort = hostAndPort;
}
}
@@ -0,0 +1,42 @@
/*
* 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.proxy.common;
import java.util.HashMap;
import java.util.Map;
public class ProxyContext {
private final Map<String, Object> value = new HashMap<>();
public static ProxyContext create() {
return new ProxyContext();
}
public Map<String, Object> getValue() {
return this.value;
}
public ProxyContext withVal(String key, Object val) {
this.value.put(key, val);
return this;
}
public <T> T getVal(String key) {
return (T) this.value.get(key);
}
}
@@ -14,28 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.common;
public class TelemetryCommandRecord {
private final String nonce;
private final Integer opaque;
public class ProxyException extends RuntimeException {
public TelemetryCommandRecord(String nonce, int opaque) {
this.nonce = nonce;
this.opaque = opaque;
private final ProxyExceptionCode code;
public ProxyException(ProxyExceptionCode code, String message) {
super(message);
this.code = code;
}
public TelemetryCommandRecord(String nonce) {
this.nonce = nonce;
this.opaque = null;
public ProxyException(ProxyExceptionCode code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getNonce() {
return nonce;
}
public Integer getOpaque() {
return opaque;
public ProxyExceptionCode getCode() {
return code;
}
}
@@ -14,8 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.transaction;
package org.apache.rocketmq.proxy.common;
public interface TransactionStateChecker {
void checkTransactionState(TransactionStateCheckRequest checkData);
public enum ProxyExceptionCode {
FORBIDDEN,
RECEIPT_HANDLE_EXPIRED,
INVALID_BROKER_NAME
}
@@ -0,0 +1,32 @@
/*
* 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.proxy.common.utils;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
public class ExceptionUtils {
public static Throwable getRealException(Throwable throwable) {
if (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
if (throwable.getCause() != null) {
throwable = throwable.getCause();
}
}
return throwable;
}
}
@@ -17,7 +17,7 @@
package org.apache.rocketmq.proxy.config;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyMode;
import org.apache.rocketmq.proxy.ProxyMode;
public class ProxyConfig {
public final static String CONFIG_FILE_NAME = "rmq-proxy.json";
@@ -54,6 +54,9 @@ public class ProxyConfig {
private int channelExpiredInSeconds = 60;
private int rocketmqMQClientNum = 6;
private double rocketmqMQClientWorkerFactor = 0.2f;
private int forwardConsumerNum = 2;
private double forwardConsumerWorkerFactor = 0.2f;
private int forwardProducerNum = 2;
@@ -229,6 +232,22 @@ public class ProxyConfig {
this.channelExpiredInSeconds = channelExpiredInSeconds;
}
public int getRocketmqMQClientNum() {
return rocketmqMQClientNum;
}
public void setRocketmqMQClientNum(int rocketmqMQClientNum) {
this.rocketmqMQClientNum = rocketmqMQClientNum;
}
public double getRocketmqMQClientWorkerFactor() {
return rocketmqMQClientWorkerFactor;
}
public void setRocketmqMQClientWorkerFactor(double rocketmqMQClientWorkerFactor) {
this.rocketmqMQClientWorkerFactor = rocketmqMQClientWorkerFactor;
}
public int getForwardConsumerNum() {
return forwardConsumerNum;
}
@@ -1,69 +0,0 @@
/*
* 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.proxy.connector;
import java.time.Duration;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
public abstract class AbstractForwardClient implements StartAndShutdown {
public static final long MAX_CONSUMER_TIMEOUT_MILLIS = Duration.ofSeconds(20).toMillis();
public static final long DEFAULT_MQ_CLIENT_TIMEOUT = Duration.ofSeconds(3).toMillis();
private final ForwardClientManager clientFactory;
private MQClientAPIExt[] clients;
private final String gidPrefix;
public AbstractForwardClient(ForwardClientManager clientFactory, String gidPrefix) {
this.clientFactory = clientFactory;
this.gidPrefix = gidPrefix;
}
protected abstract int getClientNum();
protected abstract MQClientAPIExt createNewClient(ForwardClientManager forwardClientFactory, String name);
protected String getNamePrefix() {
return this.gidPrefix;
}
protected MQClientAPIExt getClient() {
if (clients.length == 1) {
return this.clients[0];
}
int index = ThreadLocalRandom.current().nextInt(this.clients.length);
return this.clients[index];
}
@Override
public void start() throws Exception {
int clientCount = getClientNum();
this.clients = new MQClientAPIExt[clientCount];
for (int i = 0; i < clientCount; i++) {
String name = getNamePrefix() + "N_" + i;
clients[i] = createNewClient(clientFactory, name);
}
}
@Override
public void shutdown() throws Exception {
}
}
@@ -1,84 +0,0 @@
/*
* 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.proxy.connector;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.connector.transaction.TransactionHeartbeatRegisterService;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
public class ConnectorManager extends AbstractStartAndShutdown {
protected ForwardClientManager forwardClientManager;
protected DefaultForwardClient defaultForwardClient;
protected ForwardProducer forwardProducer;
protected ForwardReadConsumer forwardReadConsumer;
protected ForwardWriteConsumer forwardWriteConsumer;
protected TopicRouteCache topicRouteCache;
protected TransactionHeartbeatRegisterService transactionHeartbeatRegisterService;
public ConnectorManager(TransactionStateChecker transactionStateChecker) {
this.init(transactionStateChecker);
}
protected void init(TransactionStateChecker transactionStateChecker) {
this.forwardClientManager = new ForwardClientManager(transactionStateChecker);
this.defaultForwardClient = new DefaultForwardClient(this.forwardClientManager);
this.forwardProducer = new ForwardProducer(this.forwardClientManager);
this.forwardReadConsumer = new ForwardReadConsumer(this.forwardClientManager);
this.forwardWriteConsumer = new ForwardWriteConsumer(this.forwardClientManager);
this.topicRouteCache = new TopicRouteCache(this.defaultForwardClient);
this.transactionHeartbeatRegisterService = new TransactionHeartbeatRegisterService(this.forwardProducer, this.topicRouteCache);
this.appendStartAndShutdown(this.forwardClientManager);
this.appendStartAndShutdown(this.defaultForwardClient);
this.appendStartAndShutdown(this.forwardProducer);
this.appendStartAndShutdown(this.forwardReadConsumer);
this.appendStartAndShutdown(this.forwardWriteConsumer);
this.appendStartAndShutdown(this.transactionHeartbeatRegisterService);
}
public ForwardClientManager getForwardClientManager() {
return forwardClientManager;
}
public DefaultForwardClient getDefaultForwardClient() {
return defaultForwardClient;
}
public ForwardProducer getForwardProducer() {
return forwardProducer;
}
public ForwardReadConsumer getForwardReadConsumer() {
return forwardReadConsumer;
}
public ForwardWriteConsumer getForwardWriteConsumer() {
return forwardWriteConsumer;
}
public TopicRouteCache getTopicRouteCache() {
return topicRouteCache;
}
public TransactionHeartbeatRegisterService getTransactionHeartbeatRegisterService() {
return transactionHeartbeatRegisterService;
}
}
@@ -1,103 +0,0 @@
/*
* 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.proxy.connector;
import io.grpc.Context;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.common.protocol.header.GetConsumerListByGroupRequestHeader;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
import org.apache.rocketmq.remoting.exception.RemotingException;
public class DefaultForwardClient extends AbstractForwardClient {
private static final String CID_PREFIX = "CID_RMQ_PROXY_DEFAULT_";
public DefaultForwardClient(ForwardClientManager clientFactory) {
super(clientFactory, CID_PREFIX);
}
@Override
protected int getClientNum() {
return ConfigurationManager.getProxyConfig().getDefaultForwardClientNum();
}
@Override
protected MQClientAPIExt createNewClient(ForwardClientManager clientFactory, String name) {
double workerFactor = ConfigurationManager.getProxyConfig().getDefaultForwardClientWorkerFactor();
int threadCount = (int) Math.ceil(Runtime.getRuntime().availableProcessors() * workerFactor);
return clientFactory.getMQClient(name, threadCount);
}
public CompletableFuture<List<String>> getConsumerListByGroup(
Context ctx,
String brokerAddr,
GetConsumerListByGroupRequestHeader requestHeader,
long timeoutMillis
) {
return this.getClient().getConsumerListByGroupAsync(brokerAddr, requestHeader, timeoutMillis);
}
public TopicRouteData getTopicRouteInfoFromNameServer(String topic)
throws RemotingException, InterruptedException, MQClientException {
return this.getTopicRouteInfoFromNameServer(topic, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public TopicRouteData getTopicRouteInfoFromNameServer(String topic, long timeoutMillis)
throws RemotingException, InterruptedException, MQClientException {
return this.getClient().getTopicRouteInfoFromNameServer(topic, timeoutMillis);
}
public CompletableFuture<Long> getMaxOffset(Context ctx, String brokerAddr, String topic, int queueId) {
return this.getMaxOffset(ctx, brokerAddr, topic, queueId, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<Long> getMaxOffset(
Context ctx,
String brokerAddr,
String topic,
int queueId,
long timeoutMillis
) {
return this.getClient().getMaxOffsetAsync(brokerAddr, topic, queueId, timeoutMillis);
}
public CompletableFuture<Long> searchOffset(
Context ctx,
String brokerAddr,
String topic,
int queueId,
long timestamp
) {
return this.searchOffset(ctx, brokerAddr, topic, queueId, timestamp, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<Long> searchOffset(
Context ctx,
String brokerAddr,
String topic,
int queueId,
long timestamp,
long timeoutMillis
) {
return this.getClient().searchOffsetAsync(brokerAddr, topic, queueId, timestamp, timeoutMillis);
}
}
@@ -1,142 +0,0 @@
/*
* 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.proxy.connector;
import io.grpc.Context;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
import org.apache.rocketmq.proxy.connector.transaction.TransactionId;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ForwardProducer extends AbstractForwardClient {
private static final String PID_PREFIX = "PID_RMQ_PROXY_PUBLISH_MESSAGE_";
public ForwardProducer(ForwardClientManager clientFactory) {
super(clientFactory, PID_PREFIX);
}
@Override
protected int getClientNum() {
return ConfigurationManager.getProxyConfig().getForwardProducerNum();
}
@Override
protected MQClientAPIExt createNewClient(ForwardClientManager clientFactory, String name) {
double workerFactor = ConfigurationManager.getProxyConfig().getForwardProducerWorkerFactor();
final int threadCount = (int) Math.ceil(Runtime.getRuntime().availableProcessors() * workerFactor);
return clientFactory.getTransactionalProducer(name, threadCount);
}
public CompletableFuture<Integer> heartBeat(Context ctx, String brokerAddr, HeartbeatData heartbeatData) throws Exception {
return this.heartBeat(ctx, brokerAddr, heartbeatData, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<Integer> heartBeat(Context ctx, String brokerAddr, HeartbeatData heartbeatData, long timeout) throws Exception {
return this.getClient().sendHeartbeatAsync(brokerAddr, heartbeatData, timeout);
}
public void endTransaction(Context ctx, String brokerAddr, EndTransactionRequestHeader requestHeader) throws Exception {
this.endTransaction(ctx, brokerAddr, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public void endTransaction(Context ctx, String brokerAddr, EndTransactionRequestHeader requestHeader, long timeoutMillis) throws Exception {
this.getClient().endTransactionOneway(brokerAddr, requestHeader, "end transaction from rmq proxy", timeoutMillis);
}
public CompletableFuture<SendResult> sendMessage(
Context ctx,
String address,
String brokerName,
List<Message> msg,
SendMessageRequestHeader requestHeader
) {
return this.sendMessage(ctx, address, brokerName, msg, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<SendResult> sendMessage(
Context ctx,
String address,
String brokerName,
List<Message> msg,
SendMessageRequestHeader requestHeader,
long timeoutMillis
) {
CompletableFuture<SendResult> future;
if (msg.size() == 1) {
future = this.getClient().sendMessageAsync(address, brokerName, msg.get(0), requestHeader, timeoutMillis);
} else {
future = this.getClient().sendMessageAsync(address, brokerName, msg, requestHeader, timeoutMillis);
}
return processSendMessageResponseFuture(ctx, address, requestHeader, future);
}
protected CompletableFuture<SendResult> processSendMessageResponseFuture(
Context ctx,
String address,
SendMessageRequestHeader requestHeader,
CompletableFuture<SendResult> future) {
return future.thenApply(sendResult -> {
int tranType = MessageSysFlag.getTransactionValue(requestHeader.getSysFlag());
if (SendStatus.SEND_OK.equals(sendResult.getSendStatus()) && tranType == MessageSysFlag.TRANSACTION_PREPARED_TYPE) {
TransactionId transactionId = TransactionId.genByBrokerTransactionId(address, sendResult);
sendResult.setTransactionId(transactionId.getProxyTransactionId());
}
return sendResult;
});
}
public CompletableFuture<RemotingCommand> sendMessageBackThenAckOrg(Context ctx, String brokerAddr, ConsumerSendMsgBackRequestHeader sendMsgBackRequestHeader,
AckMessageRequestHeader ackMessageRequestHeader) {
return sendMessageBackThenAckOrg(ctx, brokerAddr, sendMsgBackRequestHeader, ackMessageRequestHeader, DEFAULT_MQ_CLIENT_TIMEOUT,
future -> {
});
}
public CompletableFuture<RemotingCommand> sendMessageBackThenAckOrg(Context ctx, String brokerAddr, ConsumerSendMsgBackRequestHeader sendMsgBackRequestHeader,
AckMessageRequestHeader ackMessageRequestHeader, long timeoutMillis,
Consumer<CompletableFuture<AckResult>> ackFutureSetter) {
return this.sendMessageBack(ctx, brokerAddr, sendMsgBackRequestHeader, timeoutMillis).whenComplete((result, throwable) -> {
if (throwable != null || ResponseCode.SUCCESS != result.getCode()) {
return;
}
ackFutureSetter.accept(this.getClient().ackMessageAsync(brokerAddr, ackMessageRequestHeader, timeoutMillis));
});
}
public CompletableFuture<RemotingCommand> sendMessageBack(Context ctx, String brokerAddr, ConsumerSendMsgBackRequestHeader requestHeader) {
return this.sendMessageBack(ctx, brokerAddr, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<RemotingCommand> sendMessageBack(Context ctx, String brokerAddr, ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis) {
return this.getClient().sendMessageBackAsync(brokerAddr, requestHeader, timeoutMillis);
}
}
@@ -1,73 +0,0 @@
/*
* 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.proxy.connector;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.PullMessageRequestHeader;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
public class ForwardReadConsumer extends AbstractForwardClient {
private static final String CID_PREFIX = "CID_RMQ_PROXY_CONSUME_MESSAGE_";
public ForwardReadConsumer(ForwardClientManager clientFactory) {
super(clientFactory, CID_PREFIX);
}
@Override
protected int getClientNum() {
return ConfigurationManager.getProxyConfig().getForwardConsumerNum();
}
@Override
protected MQClientAPIExt createNewClient(ForwardClientManager clientFactory, String name) {
double workerFactor = ConfigurationManager.getProxyConfig().getForwardConsumerWorkerFactor();
final int threadCount = (int) Math.ceil(Runtime.getRuntime().availableProcessors() * workerFactor);
return clientFactory.getMQClient(name, threadCount);
}
public CompletableFuture<PopResult> popMessage(Context ctx, String address, String brokerName,
PopMessageRequestHeader requestHeader) {
return this.popMessage(ctx, address, brokerName, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<PopResult> popMessage(
Context ctx,
String address,
String brokerName,
PopMessageRequestHeader requestHeader,
long timeoutMillis
) {
return this.getClient().popMessageAsync(address, brokerName, requestHeader, timeoutMillis);
}
public CompletableFuture<PullResult> pullMessage(Context ctx, String address, PullMessageRequestHeader requestHeader) {
return this.pullMessage(ctx, address, requestHeader, MAX_CONSUMER_TIMEOUT_MILLIS);
}
public CompletableFuture<PullResult> pullMessage(Context ctx, String address, PullMessageRequestHeader requestHeader,
long timeoutMillis) {
return this.getClient().pullMessageAsync(address, requestHeader, timeoutMillis);
}
}
@@ -1,94 +0,0 @@
/*
* 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.proxy.connector;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.UpdateConsumerOffsetRequestHeader;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.factory.ForwardClientManager;
import org.apache.rocketmq.remoting.exception.RemotingException;
public class ForwardWriteConsumer extends AbstractForwardClient {
private static final String CID_PREFIX = "CID_RMQ_PROXY_DELETE_MESSAGE_";
public ForwardWriteConsumer(ForwardClientManager clientFactory) {
super(clientFactory, CID_PREFIX);
}
@Override
protected int getClientNum() {
return ConfigurationManager.getProxyConfig().getForwardConsumerNum();
}
@Override
protected MQClientAPIExt createNewClient(ForwardClientManager clientFactory, String name) {
double workerFactor = ConfigurationManager.getProxyConfig().getForwardConsumerWorkerFactor();
final int threadCount = (int) Math.ceil(Runtime.getRuntime().availableProcessors() * workerFactor);
return clientFactory.getMQClient(name, threadCount);
}
public CompletableFuture<AckResult> ackMessage(Context ctx, String address, String messageId, AckMessageRequestHeader requestHeader) {
return this.ackMessage(ctx, address, messageId, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<AckResult> ackMessage(
Context ctx,
String address,
String messageId,
AckMessageRequestHeader requestHeader,
long timeoutMillis
) {
return this.getClient().ackMessageAsync(address, requestHeader, timeoutMillis);
}
public CompletableFuture<AckResult> changeInvisibleTimeAsync(
Context ctx,
String address,
String brokerName,
String messageId,
ChangeInvisibleTimeRequestHeader requestHeader
) {
return this.changeInvisibleTimeAsync(ctx, address, brokerName, messageId, requestHeader, DEFAULT_MQ_CLIENT_TIMEOUT);
}
public CompletableFuture<AckResult> changeInvisibleTimeAsync(
Context ctx,
String address,
String brokerName,
String messageId,
ChangeInvisibleTimeRequestHeader requestHeader,
long timeoutMillis
) {
return this.getClient().changeInvisibleTimeAsync(address, brokerName, requestHeader, timeoutMillis);
}
public void updateConsumerOffsetOneWay(
Context ctx,
String brokerAddr,
UpdateConsumerOffsetRequestHeader header,
long timeoutMillis
) throws RemotingException, InterruptedException {
this.getClient().updateConsumerOffsetOneWay(brokerAddr, header, timeoutMillis);
}
}
@@ -1,86 +0,0 @@
/*
* 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.proxy.connector.factory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.netty.NettyClientConfig;
public abstract class AbstractClientManager<T> {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final ScheduledExecutorService scheduledExecutorService;
protected Map<String, T> cacheTable = new ConcurrentHashMap<>();
protected RPCHook rpcHook;
public AbstractClientManager(ScheduledExecutorService scheduledExecutorService, RPCHook rpcHook) {
this.scheduledExecutorService = scheduledExecutorService;
this.rpcHook = rpcHook;
}
protected abstract T newOne(String instanceName, RPCHook rpcHook, int bootstrapWorkerThreads);
protected abstract boolean tryStart(T t);
protected abstract void shutdown(T t);
protected static NettyClientConfig createNettyClientConfig(int bootstrapWorkerThreads) {
NettyClientConfig nettyClientConfig = new NettyClientConfig();
nettyClientConfig.setBootstrapWorkerThreads(bootstrapWorkerThreads);
nettyClientConfig.setDisableNettyWorkerGroup(true);
nettyClientConfig.setDisableCallbackExecutor(true);
return nettyClientConfig;
}
public T getOne(String instanceName, int bootstrapWorkerThreads) {
if (cacheTable.containsKey(instanceName)) {
return cacheTable.get(instanceName);
}
T object;
try {
object = this.newOne(instanceName, rpcHook, bootstrapWorkerThreads);
} catch (Throwable t) {
throw new RuntimeException(t);
}
T old = cacheTable.putIfAbsent(instanceName, object);
if (old == null) {
if (!this.tryStart(object)) {
return null;
}
} else {
object = old;
}
return object;
}
public void shutdownAll() {
this.cacheTable.forEach((k, v) -> {
try {
this.shutdown(v);
} catch (Exception e) {
log.warn("try to shutdown client err.", e);
}
});
}
}
@@ -1,66 +0,0 @@
/*
* 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.proxy.connector.factory;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.client.ClientConfig;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.remoting.RPCHook;
public abstract class AbstractMQClientFactory extends AbstractClientManager<MQClientAPIExt> {
public AbstractMQClientFactory(ScheduledExecutorService scheduledExecutorService, RPCHook rpcHook) {
super(scheduledExecutorService, rpcHook);
}
protected abstract ClientRemotingProcessor createClientRemotingProcessor();
@Override
protected MQClientAPIExt newOne(String instanceName, RPCHook rpcHook, int bootstrapWorkerThreads) {
ClientConfig clientConfig = new ClientConfig();
clientConfig.setInstanceName(instanceName);
return new MQClientAPIExt(
clientConfig,
createNettyClientConfig(bootstrapWorkerThreads),
createClientRemotingProcessor(),
rpcHook
);
}
@Override
protected boolean tryStart(MQClientAPIExt client) {
if (!client.updateNameServerAddressList()) {
this.scheduledExecutorService.scheduleAtFixedRate(
client::fetchNameServerAddr,
Duration.ofSeconds(10).toMillis(),
Duration.ofMinutes(2).toMillis(),
TimeUnit.MILLISECONDS
);
}
client.start();
return true;
}
@Override
protected void shutdown(MQClientAPIExt client) {
client.shutdown();
}
}
@@ -1,86 +0,0 @@
/*
* 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.proxy.connector.factory;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.ClientConfig;
import org.apache.rocketmq.proxy.connector.client.MQClientAPIExt;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.remoting.RPCHook;
public class ForwardClientManager implements StartAndShutdown {
protected RPCHook rpcHook;
protected MQClientFactory mqClientFactory;
protected TransactionProducerFactory transactionalProducerFactory;
public ForwardClientManager(TransactionStateChecker transactionStateChecker) {
this.init(transactionStateChecker);
}
public ForwardClientManager(TransactionStateChecker transactionStateChecker, RPCHook rpcHook) {
this.rpcHook = rpcHook;
this.init(transactionStateChecker);
}
protected void init(TransactionStateChecker transactionStateChecker) {
System.setProperty(ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY,
System.getProperty(ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "false"));
if (StringUtils.isEmpty(ConfigurationManager.getProxyConfig().getNameSrvDomain())) {
String nameSrvAddr = ConfigurationManager.getProxyConfig().getNameSrvAddr();
if (StringUtils.isEmpty(nameSrvAddr)) {
throw new IllegalArgumentException("the address of namesrv cannot be empty");
}
System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, nameSrvAddr);
} else {
System.setProperty("rocketmq.namesrv.domain", ConfigurationManager.getProxyConfig().getNameSrvDomain());
System.setProperty("rocketmq.namesrv.domain.subgroup", ConfigurationManager.getProxyConfig().getNameSrvDomainSubgroup());
}
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("ForwardClientFactoryScheduledThread" + "-%d").build()
);
this.mqClientFactory = new MQClientFactory(scheduledExecutorService, this.rpcHook);
this.transactionalProducerFactory = new TransactionProducerFactory(scheduledExecutorService, this.rpcHook, transactionStateChecker);
}
public MQClientAPIExt getMQClient(String instanceName, int bootstrapWorkerThreads) {
return mqClientFactory.getOne(instanceName, bootstrapWorkerThreads);
}
public MQClientAPIExt getTransactionalProducer(String instanceName, int bootstrapWorkerThreads) {
return transactionalProducerFactory.getOne(instanceName, bootstrapWorkerThreads);
}
@Override
public void start() throws Exception {
}
@Override
public void shutdown() throws Exception {
this.mqClientFactory.shutdownAll();
this.transactionalProducerFactory.shutdownAll();
}
}
@@ -1,38 +0,0 @@
/*
* 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.proxy.connector.factory;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
import org.apache.rocketmq.proxy.connector.processor.ProxyClientRemotingProcessor;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.remoting.RPCHook;
public class TransactionProducerFactory extends AbstractMQClientFactory {
protected final TransactionStateChecker transactionStateChecker;
public TransactionProducerFactory(ScheduledExecutorService scheduledExecutorService,
RPCHook rpcHook, TransactionStateChecker transactionStateChecker) {
super(scheduledExecutorService, rpcHook);
this.transactionStateChecker = transactionStateChecker;
}
@Override
protected ClientRemotingProcessor createClientRemotingProcessor() {
return new ProxyClientRemotingProcessor(this.transactionStateChecker);
}
}
@@ -1,103 +0,0 @@
/*
* 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.proxy.connector.transaction;
import org.apache.rocketmq.common.message.MessageExt;
public class TransactionStateCheckRequest {
private String groupId;
private Long tranStateTableOffset;
private Long commitLogOffset;
private String msgId;
private TransactionId transactionId;
private String brokerName;
private MessageExt messageExt;
public TransactionStateCheckRequest(
String groupId,
Long tranStateTableOffset,
Long commitLogOffset,
String msgId,
TransactionId transactionId,
String brokerName,
MessageExt messageExt
) {
this.groupId = groupId;
this.tranStateTableOffset = tranStateTableOffset;
this.commitLogOffset = commitLogOffset;
this.msgId = msgId;
this.transactionId = transactionId;
this.brokerName = brokerName;
this.messageExt = messageExt;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public Long getTranStateTableOffset() {
return tranStateTableOffset;
}
public void setTranStateTableOffset(Long tranStateTableOffset) {
this.tranStateTableOffset = tranStateTableOffset;
}
public Long getCommitLogOffset() {
return commitLogOffset;
}
public void setCommitLogOffset(Long commitLogOffset) {
this.commitLogOffset = commitLogOffset;
}
public String getMsgId() {
return msgId;
}
public void setMsgId(String msgId) {
this.msgId = msgId;
}
public TransactionId getTransactionId() {
return transactionId;
}
public void setTransactionId(TransactionId transactionId) {
this.transactionId = transactionId;
}
public String getBrokerName() {
return brokerName;
}
public void setBrokerName(String brokerName) {
this.brokerName = brokerName;
}
public MessageExt getMessageExt() {
return messageExt;
}
public void setMessageExt(MessageExt messageExt) {
this.messageExt = messageExt;
}
}
@@ -32,7 +32,6 @@ import org.apache.rocketmq.acl.AccessValidator;
import org.apache.rocketmq.acl.common.AclException;
import org.apache.rocketmq.acl.common.MetadataHeader;
import org.apache.rocketmq.acl.plain.PlainAccessResource;
import org.apache.rocketmq.proxy.grpc.v2.adapter.RequestMapping;
public class AuthenticationInterceptor implements ServerInterceptor {
private final List<AccessValidator> accessValidatorList;
@@ -0,0 +1,57 @@
/*
* 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.proxy.grpc.interceptor;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.SendMessageRequest;
import java.util.HashMap;
import java.util.Map;
import org.apache.rocketmq.common.protocol.RequestCode;
public class RequestMapping {
private final static Map<String, Integer> REQUEST_MAP = new HashMap<String, Integer>() {
{
// v2
put(QueryRouteRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC);
put(HeartbeatRequest.getDescriptor().getFullName(), RequestCode.HEART_BEAT);
put(SendMessageRequest.getDescriptor().getFullName(), RequestCode.SEND_MESSAGE_V2);
put(QueryAssignmentRequest.getDescriptor().getFullName(), RequestCode.GET_ROUTEINFO_BY_TOPIC);
put(ReceiveMessageRequest.getDescriptor().getFullName(), RequestCode.PULL_MESSAGE);
put(AckMessageRequest.getDescriptor().getFullName(), RequestCode.UPDATE_CONSUMER_OFFSET);
put(ForwardMessageToDeadLetterQueueResponse.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK);
put(EndTransactionRequest.getDescriptor().getFullName(), RequestCode.END_TRANSACTION);
put(NotifyClientTerminationRequest.getDescriptor().getFullName(), RequestCode.UNREGISTER_CLIENT);
put(ChangeInvisibleDurationRequest.getDescriptor().getFullName(), RequestCode.CONSUMER_SEND_MSG_BACK);
}
};
public static int map(String rpcFullName) {
if (REQUEST_MAP.containsKey(rpcFullName)) {
return REQUEST_MAP.get(rpcFullName);
}
return RequestCode.HEART_BEAT;
}
}
@@ -0,0 +1,42 @@
/*
* 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.proxy.grpc.v2;
import io.grpc.Context;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
public abstract class AbstractMessingActivity {
protected final MessagingProcessor messagingProcessor;
protected final GrpcClientSettingsManager grpcClientSettingsManager;
public AbstractMessingActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
this.messagingProcessor = messagingProcessor;
this.grpcClientSettingsManager = grpcClientSettingsManager;
}
protected ProxyContext createContext(Context ctx) {
return ProxyContext.create()
.withVal(GrpcContextConstants.CLIENT_ID, InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID))
.withVal(GrpcContextConstants.LANGUAGE, LanguageCode.valueOf(InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE)));
}
}
@@ -0,0 +1,154 @@
/*
* 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.proxy.grpc.v2;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.EndTransactionResponse;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.NotifyClientTerminationResponse;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
import org.apache.rocketmq.proxy.grpc.v2.client.ClientActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.consumer.AckMessageActivity;
import org.apache.rocketmq.proxy.grpc.v2.consumer.ChangeInvisibleDurationActivity;
import org.apache.rocketmq.proxy.grpc.v2.consumer.ReceiveMessageActivity;
import org.apache.rocketmq.proxy.grpc.v2.producer.ForwardMessageToDLQActivity;
import org.apache.rocketmq.proxy.grpc.v2.producer.SendMessageActivity;
import org.apache.rocketmq.proxy.grpc.v2.route.RouteActivity;
import org.apache.rocketmq.proxy.grpc.v2.transaction.EndTransactionActivity;
import org.apache.rocketmq.proxy.processor.DefaultMessagingProcessor;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.proxy.service.ServiceManager;
public class DefaultGrpcMessingActivity extends AbstractStartAndShutdown implements GrpcMessingActivity {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final ServiceManager serviceManager;
private GrpcClientSettingsManager grpcClientSettingsManager;
private final ReceiveMessageActivity receiveMessageActivity;
private final AckMessageActivity ackMessageActivity;
private final ChangeInvisibleDurationActivity changeInvisibleDurationActivity;
private final SendMessageActivity sendMessageActivity;
private final ForwardMessageToDLQActivity forwardMessageToDLQActivity;
private final EndTransactionActivity endTransactionActivity;
private final RouteActivity routeActivity;
private final ClientActivity clientActivity;
private final MessagingProcessor messagingProcessor;
protected DefaultGrpcMessingActivity(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
this.grpcClientSettingsManager = new GrpcClientSettingsManager();
this.messagingProcessor = new DefaultMessagingProcessor(serviceManager);
this.receiveMessageActivity = new ReceiveMessageActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.ackMessageActivity = new AckMessageActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.changeInvisibleDurationActivity = new ChangeInvisibleDurationActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.sendMessageActivity = new SendMessageActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.forwardMessageToDLQActivity = new ForwardMessageToDLQActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.endTransactionActivity = new EndTransactionActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.routeActivity = new RouteActivity(this.messagingProcessor, this.grpcClientSettingsManager);
this.clientActivity = new ClientActivity(this.messagingProcessor, this.grpcClientSettingsManager);
}
protected void init() {
this.appendStartAndShutdown(serviceManager);
}
@Override
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
return this.routeActivity.queryRoute(ctx, request);
}
@Override
public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
return this.clientActivity.heartbeat(ctx, request);
}
@Override
public CompletableFuture<SendMessageResponse> sendMessage(Context ctx, SendMessageRequest request) {
return this.sendMessageActivity.sendMessage(ctx, request);
}
@Override
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
return this.routeActivity.queryAssignment(ctx, request);
}
@Override
public void receiveMessage(Context ctx, ReceiveMessageRequest request, StreamObserver<ReceiveMessageResponse> responseObserver) {
this.receiveMessageActivity.receiveMessage(ctx, request, responseObserver);
}
@Override
public CompletableFuture<AckMessageResponse> ackMessage(Context ctx, AckMessageRequest request) {
return this.ackMessageActivity.ackMessage(ctx, request);
}
@Override
public CompletableFuture<ForwardMessageToDeadLetterQueueResponse> forwardMessageToDeadLetterQueue(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
return this.forwardMessageToDLQActivity.forwardMessageToDeadLetterQueue(ctx, request);
}
@Override
public CompletableFuture<EndTransactionResponse> endTransaction(Context ctx, EndTransactionRequest request) {
return this.endTransactionActivity.endTransaction(ctx, request);
}
@Override
public CompletableFuture<NotifyClientTerminationResponse> notifyClientTermination(Context ctx,
NotifyClientTerminationRequest request) {
return this.clientActivity.notifyClientTermination(ctx, request);
}
@Override
public CompletableFuture<ChangeInvisibleDurationResponse> changeInvisibleDuration(Context ctx,
ChangeInvisibleDurationRequest request) {
return this.changeInvisibleDurationActivity.changeInvisibleDuration(ctx, request);
}
@Override
public StreamObserver<TelemetryCommand> telemetry(Context ctx, StreamObserver<TelemetryCommand> responseObserver) {
return this.clientActivity.telemetry(ctx, responseObserver);
}
}
@@ -14,13 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.service.cluster;
package org.apache.rocketmq.proxy.grpc.v2;
import apache.rocketmq.v2.SendMessageRequest;
import io.grpc.Context;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
public class GrpcContextConstants {
public interface WriteQueueSelector {
SelectableMessageQueue selectQueue(Context ctx, SendMessageRequest request);
public static final String CLIENT_ID = "client_id";
public static final String LANGUAGE = "language";
}
@@ -43,16 +43,23 @@ import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseWriter;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcForwardService;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter;
import org.apache.rocketmq.proxy.service.ServiceManager;
public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServiceImplBase {
public class GrpcMessagingApplication extends MessagingServiceGrpc.MessagingServiceImplBase implements StartAndShutdown {
private final GrpcForwardService grpcForwardService;
private final GrpcMessingActivity grpcMessingActivity;
public GrpcMessagingProcessor(GrpcForwardService grpcForwardService) {
this.grpcForwardService = grpcForwardService;
protected GrpcMessagingApplication(GrpcMessingActivity grpcMessingActivity) {
this.grpcMessingActivity = grpcMessingActivity;
}
public static GrpcMessagingApplication create(ServiceManager serviceManager) {
return new GrpcMessagingApplication(new DefaultGrpcMessingActivity(
serviceManager
));
}
protected Status convertExceptionToStatus(Throwable t) {
@@ -61,7 +68,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void queryRoute(QueryRouteRequest request, StreamObserver<QueryRouteResponse> responseObserver) {
CompletableFuture<QueryRouteResponse> future = grpcForwardService.queryRoute(Context.current(), request);
CompletableFuture<QueryRouteResponse> future = grpcMessingActivity.queryRoute(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -74,7 +81,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void heartbeat(HeartbeatRequest request, StreamObserver<HeartbeatResponse> responseObserver) {
CompletableFuture<HeartbeatResponse> future = grpcForwardService.heartbeat(Context.current(), request);
CompletableFuture<HeartbeatResponse> future = grpcMessingActivity.heartbeat(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -87,7 +94,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void sendMessage(SendMessageRequest request, StreamObserver<SendMessageResponse> responseObserver) {
CompletableFuture<SendMessageResponse> future = grpcForwardService.sendMessage(Context.current(), request);
CompletableFuture<SendMessageResponse> future = grpcMessingActivity.sendMessage(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -101,7 +108,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void queryAssignment(QueryAssignmentRequest request,
StreamObserver<QueryAssignmentResponse> responseObserver) {
CompletableFuture<QueryAssignmentResponse> future = grpcForwardService.queryAssignment(Context.current(), request);
CompletableFuture<QueryAssignmentResponse> future = grpcMessingActivity.queryAssignment(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -114,12 +121,12 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void receiveMessage(ReceiveMessageRequest request, StreamObserver<ReceiveMessageResponse> responseObserver) {
grpcForwardService.receiveMessage(Context.current(), request, responseObserver);
grpcMessingActivity.receiveMessage(Context.current(), request, responseObserver);
}
@Override
public void ackMessage(AckMessageRequest request, StreamObserver<AckMessageResponse> responseObserver) {
CompletableFuture<AckMessageResponse> future = grpcForwardService.ackMessage(Context.current(), request);
CompletableFuture<AckMessageResponse> future = grpcMessingActivity.ackMessage(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -133,7 +140,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void forwardMessageToDeadLetterQueue(ForwardMessageToDeadLetterQueueRequest request,
StreamObserver<ForwardMessageToDeadLetterQueueResponse> responseObserver) {
CompletableFuture<ForwardMessageToDeadLetterQueueResponse> future = grpcForwardService.forwardMessageToDeadLetterQueue(Context.current(), request);
CompletableFuture<ForwardMessageToDeadLetterQueueResponse> future = grpcMessingActivity.forwardMessageToDeadLetterQueue(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -146,7 +153,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void endTransaction(EndTransactionRequest request, StreamObserver<EndTransactionResponse> responseObserver) {
CompletableFuture<EndTransactionResponse> future = grpcForwardService.endTransaction(Context.current(), request);
CompletableFuture<EndTransactionResponse> future = grpcMessingActivity.endTransaction(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -160,7 +167,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void notifyClientTermination(NotifyClientTerminationRequest request,
StreamObserver<NotifyClientTerminationResponse> responseObserver) {
CompletableFuture<NotifyClientTerminationResponse> future = grpcForwardService.notifyClientTermination(Context.current(), request);
CompletableFuture<NotifyClientTerminationResponse> future = grpcMessingActivity.notifyClientTermination(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -174,7 +181,7 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public void changeInvisibleDuration(ChangeInvisibleDurationRequest request,
StreamObserver<ChangeInvisibleDurationResponse> responseObserver) {
CompletableFuture<ChangeInvisibleDurationResponse> future = grpcForwardService.changeInvisibleDuration(Context.current(), request);
CompletableFuture<ChangeInvisibleDurationResponse> future = grpcMessingActivity.changeInvisibleDuration(Context.current(), request);
future.thenAccept(response -> ResponseWriter.write(responseObserver, response))
.exceptionally(e -> {
ResponseWriter.write(
@@ -187,6 +194,16 @@ public class GrpcMessagingProcessor extends MessagingServiceGrpc.MessagingServic
@Override
public StreamObserver<TelemetryCommand> telemetry(StreamObserver<TelemetryCommand> responseObserver) {
return grpcForwardService.telemetry(Context.current(), responseObserver);
return grpcMessingActivity.telemetry(Context.current(), responseObserver);
}
@Override
public void shutdown() throws Exception {
this.grpcMessingActivity.shutdown();
}
@Override
public void start() throws Exception {
this.grpcMessingActivity.start();
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.service;
package org.apache.rocketmq.proxy.grpc.v2;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
@@ -43,7 +43,7 @@ import io.grpc.stub.StreamObserver;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
public interface GrpcForwardService extends StartAndShutdown {
public interface GrpcMessingActivity extends StartAndShutdown {
CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request);
@@ -1,733 +0,0 @@
/*
* 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.proxy.grpc.v2.adapter;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.Broker;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ClientType;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Digest;
import apache.rocketmq.v2.DigestType;
import apache.rocketmq.v2.Encoding;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.FilterExpression;
import apache.rocketmq.v2.FilterType;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.MessageType;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.Permission;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.SubscriptionEntry;
import apache.rocketmq.v2.SystemProperties;
import apache.rocketmq.v2.TransactionResolution;
import apache.rocketmq.v2.TransactionSource;
import apache.rocketmq.v2.VerifyMessageResult;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Durations;
import com.google.protobuf.util.Timestamps;
import io.grpc.Context;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.constant.ConsumeInitMode;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.constant.PermName;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.filter.ExpressionType;
import org.apache.rocketmq.common.filter.FilterAPI;
import org.apache.rocketmq.common.message.MessageAccessor;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.NamespaceUtil;
import org.apache.rocketmq.common.protocol.body.CMResult;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.common.utils.BinaryUtil;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.transaction.TransactionId;
public class GrpcConverter {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
public static String wrapResourceWithNamespace(Resource resource) {
return NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName());
}
public static HeartbeatData buildHeartbeatData(String clientId, HeartbeatRequest request,
Settings clientSettings) {
HeartbeatData heartbeatData = new HeartbeatData();
heartbeatData.setClientID(clientId);
switch (clientSettings.getClientType()) {
case PRODUCER: {
Set<org.apache.rocketmq.common.protocol.heartbeat.ProducerData> producerDataSet = new HashSet<>();
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = wrapResourceWithNamespace(topic);
producerDataSet.add(buildProducerData(topicName));
}
heartbeatData.setProducerDataSet(producerDataSet);
break;
}
case PUSH_CONSUMER:
case SIMPLE_CONSUMER: {
String groupName = wrapResourceWithNamespace(request.getGroup());
Set<org.apache.rocketmq.common.protocol.heartbeat.ConsumerData> consumerDataSet = new HashSet<>();
consumerDataSet.add(buildConsumerData(groupName, clientSettings));
heartbeatData.setConsumerDataSet(consumerDataSet);
break;
}
}
return heartbeatData;
}
public static org.apache.rocketmq.common.protocol.heartbeat.ProducerData buildProducerData(String groupName) {
org.apache.rocketmq.common.protocol.heartbeat.ProducerData buildProducerData
= new org.apache.rocketmq.common.protocol.heartbeat.ProducerData();
buildProducerData.setGroupName(groupName);
return buildProducerData;
}
public static org.apache.rocketmq.common.protocol.heartbeat.ConsumerData buildConsumerData(String groupName,
Settings clientSettings) {
org.apache.rocketmq.common.protocol.heartbeat.ConsumerData buildConsumerData = new org.apache.rocketmq.common.protocol.heartbeat.ConsumerData();
buildConsumerData.setGroupName(groupName);
buildConsumerData.setConsumeType(buildConsumeType(clientSettings.getClientType()));
buildConsumerData.setMessageModel(MessageModel.CLUSTERING);
buildConsumerData.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
Set<SubscriptionData> subscriptionDataSet =
buildSubscriptionDataSet(clientSettings.getSubscription().getSubscriptionsList());
buildConsumerData.setSubscriptionDataSet(subscriptionDataSet);
return buildConsumerData;
}
public static ConsumeType buildConsumeType(ClientType clientType) {
switch (clientType) {
case SIMPLE_CONSUMER:
return ConsumeType.CONSUME_ACTIVELY;
case PUSH_CONSUMER:
return ConsumeType.CONSUME_PASSIVELY;
default:
throw new IllegalArgumentException("Client type is not consumer, type: " + clientType);
}
}
public static SendMessageRequestHeader buildSendMessageRequestHeader(SendMessageRequest request,
String producerGroup, int queueId) {
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
if (request.getMessagesCount() <= 0) {
throw new ProxyException(Code.MESSAGE_CORRUPTED, "no message to send");
}
if (request.getMessagesCount() > 1) {
requestHeader.setBatch(true);
}
Message message = request.getMessages(0);
SystemProperties systemProperties = message.getSystemProperties();
Map<String, String> property = buildMessageProperty(message, producerGroup);
requestHeader.setProducerGroup(producerGroup);
requestHeader.setTopic(wrapResourceWithNamespace(message.getTopic()));
requestHeader.setDefaultTopic("");
requestHeader.setDefaultTopicQueueNums(0);
requestHeader.setQueueId(queueId);
// sysFlag (body encoding & message type)
int sysFlag = 0;
Encoding bodyEncoding = systemProperties.getBodyEncoding();
if (bodyEncoding.equals(Encoding.GZIP)) {
sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
}
// transaction
MessageType messageType = systemProperties.getMessageType();
if (messageType.equals(MessageType.TRANSACTION)) {
sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}
requestHeader.setSysFlag(sysFlag);
requestHeader.setBornTimestamp(Timestamps.toMillis(systemProperties.getBornTimestamp()));
requestHeader.setFlag(0);
requestHeader.setProperties(MessageDecoder.messageProperties2String(property));
requestHeader.setReconsumeTimes(systemProperties.getDeliveryAttempt());
return requestHeader;
}
public static PopMessageRequestHeader buildPopMessageRequestHeader(ReceiveMessageRequest request, long pollTime, boolean isFifo) {
Resource group = request.getGroup();
String groupName = GrpcConverter.wrapResourceWithNamespace(group);
MessageQueue messageQueue = request.getMessageQueue();
Resource topic = messageQueue.getTopic();
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
int queueId = messageQueue.getId();
int maxMessageNumbers = request.getBatchSize();
if (maxMessageNumbers > ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST) {
log.warn("change maxNums from {} to {} for pop request, with info: topic:{}, group:{}",
maxMessageNumbers, ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST, topicName, groupName);
maxMessageNumbers = ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST;
}
long invisibleTime = Durations.toMillis(request.getInvisibleDuration());
long bornTime = System.currentTimeMillis();
FilterExpression filterExpression = request.getFilterExpression();
String expression = filterExpression.getExpression();
String expressionType = GrpcConverter.buildExpressionType(filterExpression.getType());
PopMessageRequestHeader requestHeader = new PopMessageRequestHeader();
requestHeader.setConsumerGroup(groupName);
requestHeader.setTopic(topicName);
requestHeader.setQueueId(queueId);
requestHeader.setMaxMsgNums(maxMessageNumbers);
requestHeader.setInvisibleTime(invisibleTime);
requestHeader.setPollTime(pollTime);
requestHeader.setBornTime(bornTime);
requestHeader.setInitMode(ConsumeInitMode.MAX);
requestHeader.setExpType(expressionType);
requestHeader.setExp(expression);
requestHeader.setOrder(isFifo);
return requestHeader;
}
public static AckMessageRequestHeader buildAckMessageRequestHeader(ReceiveMessageRequest request, ReceiptHandle handle) {
return buildAckMessageRequestHeader(request.getMessageQueue().getTopic(), request.getGroup(), handle);
}
public static AckMessageRequestHeader buildAckMessageRequestHeader(AckMessageRequest request, ReceiptHandle handle) {
return buildAckMessageRequestHeader(request.getTopic(), request.getGroup(), handle);
}
public static AckMessageRequestHeader buildAckMessageRequestHeader(Resource topic, Resource group, ReceiptHandle handle) {
String groupName = GrpcConverter.wrapResourceWithNamespace(group);
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
AckMessageRequestHeader ackMessageRequestHeader = new AckMessageRequestHeader();
ackMessageRequestHeader.setConsumerGroup(groupName);
ackMessageRequestHeader.setTopic(handle.getRealTopic(topicName, groupName));
ackMessageRequestHeader.setQueueId(handle.getQueueId());
ackMessageRequestHeader.setExtraInfo(handle.getReceiptHandle());
ackMessageRequestHeader.setOffset(handle.getOffset());
return ackMessageRequestHeader;
}
public static ChangeInvisibleTimeRequestHeader buildChangeInvisibleTimeRequestHeader(ChangeInvisibleDurationRequest request) {
String groupName = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
ReceiptHandle handle = ReceiptHandle.decode(request.getReceiptHandle());
ChangeInvisibleTimeRequestHeader changeInvisibleTimeRequestHeader = new ChangeInvisibleTimeRequestHeader();
changeInvisibleTimeRequestHeader.setConsumerGroup(groupName);
changeInvisibleTimeRequestHeader.setTopic(handle.getRealTopic(topicName, groupName));
changeInvisibleTimeRequestHeader.setQueueId(handle.getQueueId());
changeInvisibleTimeRequestHeader.setExtraInfo(handle.getReceiptHandle());
changeInvisibleTimeRequestHeader.setOffset(handle.getOffset());
changeInvisibleTimeRequestHeader.setInvisibleTime(Durations.toMillis(request.getInvisibleDuration()));
return changeInvisibleTimeRequestHeader;
}
public static ChangeInvisibleTimeRequestHeader buildChangeInvisibleTimeRequestHeader(ReceiveMessageRequest request, ReceiptHandle handle) {
String groupName = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getMessageQueue().getTopic());
ChangeInvisibleTimeRequestHeader changeInvisibleTimeRequestHeader = new ChangeInvisibleTimeRequestHeader();
changeInvisibleTimeRequestHeader.setConsumerGroup(groupName);
changeInvisibleTimeRequestHeader.setTopic(handle.getRealTopic(topicName, groupName));
changeInvisibleTimeRequestHeader.setQueueId(handle.getQueueId());
changeInvisibleTimeRequestHeader.setExtraInfo(handle.getReceiptHandle());
changeInvisibleTimeRequestHeader.setOffset(handle.getOffset());
changeInvisibleTimeRequestHeader.setInvisibleTime(Durations.toMillis(request.getInvisibleDuration()));
return changeInvisibleTimeRequestHeader;
}
public static ConsumerSendMsgBackRequestHeader buildConsumerSendMsgBackRequestHeader(ReceiveMessageRequest request,
ReceiptHandle handle, String messageId, int maxReconsumeTimes) {
return buildConsumerSendMsgBackRequestHeader(request.getMessageQueue().getTopic(), request.getGroup(), handle, messageId, maxReconsumeTimes);
}
public static ConsumerSendMsgBackRequestHeader buildConsumerSendMsgBackRequestHeader(
ForwardMessageToDeadLetterQueueRequest request) {
ReceiptHandle handle = ReceiptHandle.decode(request.getReceiptHandle());
return buildConsumerSendMsgBackRequestHeader(request.getTopic(), request.getGroup(), handle,
request.getMessageId(), request.getMaxDeliveryAttempts());
}
public static ConsumerSendMsgBackRequestHeader buildConsumerSendMsgBackRequestHeader(Resource topic, Resource group, ReceiptHandle handle,
String messageId, int maxReconsumeTimes) {
String groupName = GrpcConverter.wrapResourceWithNamespace(group);
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader();
consumerSendMsgBackRequestHeader.setOffset(handle.getCommitLogOffset());
consumerSendMsgBackRequestHeader.setGroup(groupName);
consumerSendMsgBackRequestHeader.setDelayLevel(-1);
consumerSendMsgBackRequestHeader.setOriginMsgId(messageId);
consumerSendMsgBackRequestHeader.setOriginTopic(handle.getRealTopic(topicName, groupName));
consumerSendMsgBackRequestHeader.setMaxReconsumeTimes(maxReconsumeTimes);
return consumerSendMsgBackRequestHeader;
}
public static EndTransactionRequestHeader buildEndTransactionRequestHeader(EndTransactionRequest request,
String producerGroup) {
String messageId = request.getMessageId();
String transactionId = request.getTransactionId();
TransactionId handle;
try {
handle = TransactionId.decode(transactionId);
} catch (Exception e) {
throw new ProxyException(Code.INVALID_TRANSACTION_ID, "Parse transaction id failed", e);
}
long transactionStateTableOffset = handle.getTranStateTableOffset();
long commitLogOffset = handle.getCommitLogOffset();
boolean fromTransactionCheck = request.getSource() == TransactionSource.SOURCE_SERVER_CHECK;
int commitOrRollback = GrpcConverter.buildTransactionCommitOrRollback(request.getResolution());
EndTransactionRequestHeader endTransactionRequestHeader = new EndTransactionRequestHeader();
endTransactionRequestHeader.setProducerGroup(producerGroup);
endTransactionRequestHeader.setMsgId(messageId);
endTransactionRequestHeader.setTransactionId(handle.getBrokerTransactionId());
endTransactionRequestHeader.setTranStateTableOffset(transactionStateTableOffset);
endTransactionRequestHeader.setCommitLogOffset(commitLogOffset);
endTransactionRequestHeader.setCommitOrRollback(commitOrRollback);
endTransactionRequestHeader.setFromTransactionCheck(fromTransactionCheck);
return endTransactionRequestHeader;
}
public static UnregisterClientRequestHeader buildUnregisterClientRequestHeader(String clientId,
ClientType clientType, NotifyClientTerminationRequest request) {
UnregisterClientRequestHeader header = new UnregisterClientRequestHeader();
String groupName = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
header.setClientID(clientId);
switch (clientType) {
case PRODUCER: {
header.setProducerGroup(groupName);
break;
}
case PUSH_CONSUMER:
case SIMPLE_CONSUMER: {
header.setConsumerGroup(groupName);
break;
}
}
return header;
}
public static Map<String, String> buildMessageProperty(Message message, String producerGroup) {
org.apache.rocketmq.common.message.Message messageWithHeader = new org.apache.rocketmq.common.message.Message();
// set user properties
Map<String, String> userProperties = message.getUserPropertiesMap();
for (String key : userProperties.keySet()) {
if (MessageConst.STRING_HASH_SET.contains(key)) {
throw new ProxyException(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, "property is used by system: " + key);
}
}
MessageAccessor.setProperties(messageWithHeader, Maps.newHashMap(userProperties));
// set tag
String tag = message.getSystemProperties().getTag();
if (!"".equals(tag)) {
messageWithHeader.setTags(tag);
}
// set keys
List<String> keysList = message.getSystemProperties().getKeysList();
if (keysList.size() > 0) {
messageWithHeader.setKeys(keysList);
}
// set message id
String messageId = message.getSystemProperties().getMessageId();
if ("".equals(messageId)) {
throw new ProxyException(Code.ILLEGAL_MESSAGE_ID, "message id is empty");
}
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, messageId);
// set transaction property
MessageType messageType = message.getSystemProperties().getMessageType();
if (messageType.equals(MessageType.TRANSACTION)) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
Duration transactionResolveDelay = message.getSystemProperties().getOrphanedTransactionRecoveryDuration();
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS,
String.valueOf(Durations.toSeconds(transactionResolveDelay)));
}
// set delay level or deliver timestamp
if (message.getSystemProperties().hasDeliveryTimestamp()) {
Timestamp deliveryTimestamp = message.getSystemProperties().getDeliveryTimestamp();
String timestampString = String.valueOf(Timestamps.toMillis(deliveryTimestamp));
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TIMER_DELIVER_MS, timestampString);
}
// set reconsume times
int reconsumeTimes = message.getSystemProperties().getDeliveryAttempt();
MessageAccessor.setReconsumeTime(messageWithHeader, String.valueOf(reconsumeTimes));
// set producer group
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_PRODUCER_GROUP, producerGroup);
// set message group
String messageGroup = message.getSystemProperties().getMessageGroup();
if (!messageGroup.isEmpty()) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_SHARDING_KEY, messageGroup);
}
// set trace context
String traceContext = message.getSystemProperties().getTraceContext();
if (!traceContext.isEmpty()) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRACE_CONTEXT, traceContext);
}
return messageWithHeader.getProperties();
}
public static List<org.apache.rocketmq.common.message.Message> buildMessage(List<Message> protoMessageList,
Resource topic) {
String topicName = wrapResourceWithNamespace(topic);
List<org.apache.rocketmq.common.message.Message> messages = new ArrayList<>();
for (Message protoMessage : protoMessageList) {
if (!protoMessage.getTopic().equals(topic)) {
throw new ProxyException(Code.MESSAGE_CORRUPTED, "topic in message is not same");
}
// here use topicName as producerGroup for transactional checker.
messages.add(buildMessage(protoMessage, topicName));
}
return messages;
}
public static org.apache.rocketmq.common.message.Message buildMessage(Message protoMessage, String producerGroup) {
String topicName = wrapResourceWithNamespace(protoMessage.getTopic());
org.apache.rocketmq.common.message.Message message =
new org.apache.rocketmq.common.message.Message(topicName, protoMessage.getBody().toByteArray());
Map<String, String> messageProperty = buildMessageProperty(protoMessage, producerGroup);
MessageAccessor.setProperties(message, messageProperty);
return message;
}
public static MessageQueue buildMessageQueue(MessageExt messageExt, String brokerName) {
Broker broker = Broker.getDefaultInstance();
if (!StringUtils.isEmpty(brokerName)) {
broker = Broker.newBuilder()
.setName(brokerName)
.setId(0)
.build();
}
return MessageQueue.newBuilder()
.setId(messageExt.getQueueId())
.setTopic(Resource.newBuilder()
.setName(NamespaceUtil.withoutNamespace(messageExt.getTopic()))
.setResourceNamespace(NamespaceUtil.getNamespaceFromResource(messageExt.getTopic()))
.build())
.setBroker(broker)
.build();
}
public static String buildExpressionType(FilterType filterType) {
switch (filterType) {
case SQL:
return ExpressionType.SQL92;
case TAG:
default:
return ExpressionType.TAG;
}
}
public static Set<SubscriptionData> buildSubscriptionDataSet(List<SubscriptionEntry> subscriptionEntryList) {
Set<SubscriptionData> subscriptionDataSet = new HashSet<>();
for (SubscriptionEntry sub : subscriptionEntryList) {
String topicName = GrpcConverter.wrapResourceWithNamespace(sub.getTopic());
FilterExpression filterExpression = sub.getExpression();
subscriptionDataSet.add(buildSubscriptionData(topicName, filterExpression));
}
return subscriptionDataSet;
}
public static SubscriptionData buildSubscriptionData(String topicName, FilterExpression filterExpression) {
String expression = filterExpression.getExpression();
String expressionType = GrpcConverter.buildExpressionType(filterExpression.getType());
try {
return FilterAPI.build(topicName, expression, expressionType);
} catch (Exception e) {
throw new ProxyException(Code.ILLEGAL_FILTER_EXPRESSION, "expression format is not correct", e);
}
}
public static Message buildMessage(MessageExt messageExt) {
Map<String, String> userProperties = buildUserAttributes(messageExt);
SystemProperties systemProperties = buildSystemProperties(messageExt);
Resource topic = buildResource(messageExt.getTopic());
return Message.newBuilder()
.setTopic(topic)
.putAllUserProperties(userProperties)
.setSystemProperties(systemProperties)
.setBody(ByteString.copyFrom(messageExt.getBody()))
.build();
}
protected static Map<String, String> buildUserAttributes(MessageExt messageExt) {
Map<String, String> userAttributes = new HashMap<>();
Map<String, String> properties = messageExt.getProperties();
for (Map.Entry<String, String> property : properties.entrySet()) {
if (!MessageConst.STRING_HASH_SET.contains(property.getKey())) {
userAttributes.put(property.getKey(), property.getValue());
}
}
return userAttributes;
}
protected static SystemProperties buildSystemProperties(MessageExt messageExt) {
SystemProperties.Builder systemPropertiesBuilder = SystemProperties.newBuilder();
// tag
String tag = messageExt.getUserProperty(MessageConst.PROPERTY_TAGS);
if (tag != null) {
systemPropertiesBuilder.setTag(tag);
}
// keys
String keys = messageExt.getKeys();
if (keys != null) {
String[] keysArray = keys.split(MessageConst.KEY_SEPARATOR);
systemPropertiesBuilder.addAllKeys(Arrays.asList(keysArray));
}
// message_id
String uniqKey = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
if (uniqKey != null) {
systemPropertiesBuilder.setMessageId(uniqKey);
}
// body_digest & body_encoding
String md5Result = BinaryUtil.generateMd5(messageExt.getBody());
Digest digest = Digest.newBuilder()
.setType(DigestType.MD5)
.setChecksum(md5Result)
.build();
systemPropertiesBuilder.setBodyDigest(digest);
if ((messageExt.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) {
systemPropertiesBuilder.setBodyEncoding(Encoding.GZIP);
} else {
systemPropertiesBuilder.setBodyEncoding(Encoding.IDENTITY);
}
// message_type
String isTrans = messageExt.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
String isTransValue = "true";
if (isTransValue.equals(isTrans)) {
systemPropertiesBuilder.setMessageType(MessageType.TRANSACTION);
} else if (messageExt.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null
|| messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null
|| messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
systemPropertiesBuilder.setMessageType(MessageType.DELAY);
} else if (messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY) != null) {
systemPropertiesBuilder.setMessageType(MessageType.FIFO);
} else {
systemPropertiesBuilder.setMessageType(MessageType.NORMAL);
}
// born_timestamp (millis)
long bornTimestamp = messageExt.getBornTimestamp();
systemPropertiesBuilder.setBornTimestamp(Timestamps.fromMillis(bornTimestamp));
// born_host
systemPropertiesBuilder.setBornHost(messageExt.getBornHostString());
// store_timestamp (millis)
long storeTimestamp = messageExt.getStoreTimestamp();
systemPropertiesBuilder.setStoreTimestamp(Timestamps.fromMillis(storeTimestamp));
// store_host
SocketAddress storeHost = messageExt.getStoreHost();
if (storeHost != null) {
systemPropertiesBuilder.setStoreHost(storeHost.toString());
}
// delivery_timestamp
String deliverMsString;
long deliverMs;
if (messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
long delayMs = TimeUnit.SECONDS.toMillis(Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)));
deliverMs = System.currentTimeMillis() + delayMs;
systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
} else {
deliverMsString = messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS);
if (deliverMsString != null) {
deliverMs = Long.parseLong(deliverMsString);
systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
}
}
// sharding key
String shardingKey = messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY);
if (shardingKey != null) {
systemPropertiesBuilder.setMessageGroup(shardingKey);
}
// receipt_handle && invisible_period
ReceiptHandle receiptHandle = ReceiptHandle.create(messageExt);
if (receiptHandle != null) {
systemPropertiesBuilder.setReceiptHandle(receiptHandle.encode());
}
// partition_id
systemPropertiesBuilder.setQueueId(messageExt.getQueueId());
// partition_offset
systemPropertiesBuilder.setQueueOffset(messageExt.getQueueOffset());
// delivery_attempt
systemPropertiesBuilder.setDeliveryAttempt(messageExt.getReconsumeTimes() + 1);
// trace context
String traceContext = messageExt.getProperty(MessageConst.PROPERTY_TRACE_CONTEXT);
if (traceContext != null) {
systemPropertiesBuilder.setTraceContext(traceContext);
}
return systemPropertiesBuilder.build();
}
public static int buildTransactionCommitOrRollback(TransactionResolution type) {
switch (type) {
case COMMIT:
return MessageSysFlag.TRANSACTION_COMMIT_TYPE;
case ROLLBACK:
return MessageSysFlag.TRANSACTION_ROLLBACK_TYPE;
default:
return MessageSysFlag.TRANSACTION_NOT_TYPE;
}
}
public static ConsumeMessageDirectlyResult buildConsumeMessageDirectlyResult(VerifyMessageResult request) {
ConsumeMessageDirectlyResult consumeMessageDirectlyResult = new ConsumeMessageDirectlyResult();
switch (request.getStatus().getCode().getNumber()) {
case Code.OK_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_SUCCESS);
break;
}
case Code.FAILED_TO_CONSUME_MESSAGE_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_LATER);
break;
}
case Code.MESSAGE_CORRUPTED_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_RETURN_NULL);
break;
}
}
consumeMessageDirectlyResult.setRemark("From gRPC client");
return consumeMessageDirectlyResult;
}
public static Resource buildResource(String resourceNameWithNamespace) {
return Resource.newBuilder()
.setResourceNamespace(NamespaceUtil.getNamespaceFromResource(resourceNameWithNamespace))
.setName(NamespaceUtil.withoutNamespace(resourceNameWithNamespace))
.build();
}
public static long buildPollTimeFromContext(Context ctx) {
long timeRemaining = ctx.getDeadline()
.timeRemaining(TimeUnit.MILLISECONDS);
long pollTime = timeRemaining - ConfigurationManager.getProxyConfig().getLongPollingReserveTimeInMillis();
if (pollTime <= 0) {
pollTime = timeRemaining;
}
return pollTime;
}
public static List<MessageQueue> genMessageQueueFromQueueData(QueueData queueData, Resource topic, Broker broker) {
List<MessageQueue> messageQueueList = new ArrayList<>();
int r = 0;
int w = 0;
int rw = 0;
if (PermName.isWriteable(queueData.getPerm()) && PermName.isReadable(queueData.getPerm())) {
rw = Math.min(queueData.getWriteQueueNums(), queueData.getReadQueueNums());
r = queueData.getReadQueueNums() - rw;
w = queueData.getWriteQueueNums() - rw;
} else if (PermName.isWriteable(queueData.getPerm())) {
w = queueData.getWriteQueueNums();
} else if (PermName.isReadable(queueData.getPerm())) {
r = queueData.getReadQueueNums();
}
// r here means readOnly queue nums, w means writeOnly queue nums, while rw means both readable and writable queue nums.
int queueIdIndex = 0;
for (int i = 0; i < r; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.READ)
.build();
messageQueueList.add(messageQueue);
}
for (int i = 0; i < w; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.WRITE)
.build();
messageQueueList.add(messageQueue);
}
for (int i = 0; i < rw; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.READ_WRITE)
.build();
messageQueueList.add(messageQueue);
}
return messageQueueList;
}
}
@@ -1,169 +0,0 @@
/*
* 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.proxy.grpc.v2.adapter.channel;
import apache.rocketmq.v2.PrintThreadStackTraceCommand;
import apache.rocketmq.v2.RecoverOrphanedTransactionCommand;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import io.netty.channel.ChannelFuture;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.channel.SimpleChannel;
import org.apache.rocketmq.proxy.common.TelemetryCommandManager;
import org.apache.rocketmq.proxy.connector.transaction.TransactionId;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.remoting.common.RemotingUtil;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class GrpcClientChannel extends SimpleChannel {
private final AtomicReference<StreamObserver<TelemetryCommand>> telemetryCommandRef = new AtomicReference<>();
private final String group;
private final String clientId;
private final TelemetryCommandManager manager;
private GrpcClientChannel(Context ctx, String group, String clientId, TelemetryCommandManager manager) {
super(ChannelManager.createSimpleChannelDirectly(ctx));
this.group = group;
this.clientId = clientId;
this.manager = manager;
}
public void setClientObserver(StreamObserver<TelemetryCommand> future) {
this.telemetryCommandRef.set(future);
}
public static GrpcClientChannel create(
ChannelManager channelManager,
String group,
String clientId,
TelemetryCommandManager manager
) {
return create(Context.current(), channelManager, group, clientId, manager);
}
public static GrpcClientChannel create(
Context ctx,
ChannelManager channelManager,
String group,
String clientId,
TelemetryCommandManager manager
) {
GrpcClientChannel channel = channelManager.createChannel(
ctx,
buildKey(group, clientId),
context -> new GrpcClientChannel(context, group, clientId, manager),
GrpcClientChannel.class
);
channelManager.addGroupClientId(group, clientId);
return channel;
}
public static GrpcClientChannel getChannel(ChannelManager channelManager, String group, String clientId) {
return channelManager.getChannel(buildKey(group, clientId), GrpcClientChannel.class);
}
public static GrpcClientChannel removeChannel(ChannelManager channelManager, String group, String clientId) {
return channelManager.removeChannel(buildKey(group, clientId), GrpcClientChannel.class);
}
private static String buildKey(String group, String clientId) {
return group + "@" + clientId;
}
@Override
public boolean isWritable() {
return this.telemetryCommandRef.get() != null;
}
/**
* Write response to corresponding remote client
*
* @param msg Target write object, {@link RemotingCommand} or {@link TelemetryCommand}
* @return Always success {@link ChannelFuture}
* <p>
* Case {@link RequestCode#CHECK_TRANSACTION_STATE}
* @see org.apache.rocketmq.broker.client.net.Broker2Client#checkProducerTransactionState
* </p>
* <p>
* Case {@link RequestCode#GET_CONSUMER_RUNNING_INFO}
* @see org.apache.rocketmq.broker.processor.AdminBrokerProcessor
* getConsumerRunningInfo
* </p>
*/
@Override
public ChannelFuture writeAndFlush(Object msg) {
StreamObserver<TelemetryCommand> streamObserver = telemetryCommandRef.get();
if (msg instanceof RemotingCommand) {
RemotingCommand command = (RemotingCommand) msg;
try {
switch (command.getCode()) {
case RequestCode.CHECK_TRANSACTION_STATE: {
final CheckTransactionStateRequestHeader header = (CheckTransactionStateRequestHeader) command.readCustomHeader();
MessageExt messageExt = MessageDecoder.decode(ByteBuffer.wrap(command.getBody()), true, false, false);
TransactionId transactionId = TransactionId.genByBrokerTransactionId(RemotingUtil.string2SocketAddress(localAddress),
header.getTransactionId(), messageExt.getCommitLogOffset(), messageExt.getQueueOffset());
streamObserver.onNext(TelemetryCommand.newBuilder()
.setRecoverOrphanedTransactionCommand(RecoverOrphanedTransactionCommand.newBuilder()
.setTransactionId(transactionId.getProxyTransactionId())
.setOrphanedTransactionalMessage(GrpcConverter.buildMessage(messageExt))
.setMessageQueue(GrpcConverter.buildMessageQueue(messageExt, header.getBrokerName()))
.build())
.build());
break;
}
case RequestCode.GET_CONSUMER_RUNNING_INFO: {
final GetConsumerRunningInfoRequestHeader header = (GetConsumerRunningInfoRequestHeader) command.readCustomHeader();
if (!header.isJstackEnable()) {
break;
}
String nonce = manager.putCommand(command.getOpaque());
streamObserver.onNext(TelemetryCommand.newBuilder()
.setPrintThreadStackTraceCommand(PrintThreadStackTraceCommand.newBuilder()
.setNonce(nonce)
.build())
.build());
break;
}
}
} catch (Exception ignore) {
}
}
if (msg instanceof TelemetryCommand) {
TelemetryCommand response = (TelemetryCommand) msg;
streamObserver.onNext(response);
}
return super.writeAndFlush(msg);
}
public String getGroup() {
return group;
}
public String getClientId() {
return clientId;
}
}
@@ -0,0 +1,84 @@
/*
* 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.proxy.grpc.v2.channel;
import io.grpc.Context;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
public class GrpcChannelManager {
private final ProxyOutService proxyOutService;
protected final ConcurrentMap<String /* group */, Map<String, GrpcClientChannel>/* clientId */> groupClientIdChannelMap = new ConcurrentHashMap<>();
protected final AtomicLong nonceIdGenerator = new AtomicLong(0);
protected final ConcurrentMap<String /* nonce */, CompletableFuture<?>> resultNonceFutureMap = new ConcurrentHashMap<>();
public GrpcChannelManager(ProxyOutService proxyOutService) {
this.proxyOutService = proxyOutService;
}
public GrpcClientChannel createChannel(Context ctx, String group, String clientId) {
this.groupClientIdChannelMap.compute(group, (groupKey, clientIdMap) -> {
if (clientIdMap == null) {
clientIdMap = new ConcurrentHashMap<>();
}
clientIdMap.computeIfAbsent(clientId, clientIdKey -> new GrpcClientChannel(proxyOutService, this, ctx, group, clientId));
return clientIdMap;
});
return getChannel(group, clientId);
}
public GrpcClientChannel getChannel(String group, String clientId) {
Map<String, GrpcClientChannel> clientIdChannelMap = this.groupClientIdChannelMap.get(group);
if (clientIdChannelMap == null) {
return null;
}
return clientIdChannelMap.get(clientId);
}
public GrpcClientChannel removeChannel(String group, String clientId) {
AtomicReference<GrpcClientChannel> channelRef = new AtomicReference<>();
this.groupClientIdChannelMap.computeIfPresent(group, (groupKey, clientIdMap) -> {
channelRef.set(clientIdMap.remove(clientId));
if (clientIdMap.isEmpty()) {
return null;
}
return clientIdMap;
});
return channelRef.get();
}
public String addResponseFuture(CompletableFuture<?> responseFuture) {
String nonce = this.nextNonce();
this.resultNonceFutureMap.put(nonce, responseFuture);
return nonce;
}
public <T> CompletableFuture<T> getAndRemoveResponseFuture(String nonce) {
return (CompletableFuture<T>) this.resultNonceFutureMap.remove(nonce);
}
protected String nextNonce() {
return String.valueOf(this.nonceIdGenerator.getAndIncrement());
}
}
@@ -0,0 +1,200 @@
/*
* 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.proxy.grpc.v2.channel;
import apache.rocketmq.v2.PrintThreadStackTraceCommand;
import apache.rocketmq.v2.RecoverOrphanedTransactionCommand;
import apache.rocketmq.v2.TelemetryCommand;
import apache.rocketmq.v2.VerifyMessageCommand;
import com.google.common.collect.ComparisonChain;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import io.netty.channel.ChannelId;
import java.net.SocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.service.out.ProxyChannel;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.common.RemotingUtil;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class GrpcClientChannel extends ProxyChannel {
protected static final String SEPARATOR = "@";
private final GrpcChannelManager grpcChannelManager;
private final AtomicReference<StreamObserver<TelemetryCommand>> telemetryCommandRef = new AtomicReference<>();
private final String group;
private final String clientId;
private final String remoteAddress;
private final String localAddress;
public GrpcClientChannel(ProxyOutService proxyOutService, GrpcChannelManager grpcChannelManager, Context ctx, String group, String clientId) {
super(proxyOutService, null, new GrpcChannelId(group, clientId));
this.grpcChannelManager = grpcChannelManager;
this.remoteAddress = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.REMOTE_ADDRESS);
this.localAddress = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.LOCAL_ADDRESS);
this.group = group;
this.clientId = clientId;
}
protected static class GrpcChannelId implements ChannelId {
private final String group;
private final String clientId;
public GrpcChannelId(String group, String clientId) {
this.group = group;
this.clientId = clientId;
}
@Override
public String asShortText() {
return this.clientId;
}
@Override
public String asLongText() {
return this.group + SEPARATOR + this.clientId;
}
@Override
public int compareTo(ChannelId o) {
if (this == o) {
return 0;
}
if (o instanceof GrpcChannelId) {
GrpcChannelId other = (GrpcChannelId) o;
return ComparisonChain.start()
.compare(this.group, other.group)
.compare(this.clientId, other.clientId)
.result();
}
return asLongText().compareTo(o.asLongText());
}
}
public void setClientObserver(StreamObserver<TelemetryCommand> future) {
this.telemetryCommandRef.set(future);
}
@Override
public boolean isOpen() {
return this.telemetryCommandRef.get() != null;
}
@Override
public boolean isActive() {
return this.telemetryCommandRef.get() != null;
}
@Override
public boolean isWritable() {
return this.telemetryCommandRef.get() != null;
}
@Override
protected SocketAddress localAddress0() {
return RemotingUtil.string2SocketAddress(this.localAddress);
}
@Override
protected SocketAddress remoteAddress0() {
return RemotingUtil.string2SocketAddress(this.remoteAddress);
}
@Override
protected CompletableFuture<Void> processOtherMessage(Object msg) {
if (msg instanceof TelemetryCommand) {
TelemetryCommand response = (TelemetryCommand) msg;
this.getTelemetryCommandStreamObserver().onNext(response);
}
return CompletableFuture.completedFuture(null);
}
@Override
protected CompletableFuture<Void> processCheckTransaction(CheckTransactionStateRequestHeader header,
MessageExt messageExt, TransactionId transactionId) {
this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
.setRecoverOrphanedTransactionCommand(RecoverOrphanedTransactionCommand.newBuilder()
.setTransactionId(transactionId.getProxyTransactionId())
.setOrphanedTransactionalMessage(GrpcConverter.buildMessage(messageExt))
.setMessageQueue(GrpcConverter.buildMessageQueue(messageExt, header.getBrokerName()))
.build())
.build());
return CompletableFuture.completedFuture(null);
}
@Override
protected CompletableFuture<Void> processGetConsumerRunningInfo(RemotingCommand command,
GetConsumerRunningInfoRequestHeader header,
CompletableFuture<ConsumerRunningInfo> responseFuture) {
if (!header.isJstackEnable()) {
return CompletableFuture.completedFuture(null);
}
this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
.setPrintThreadStackTraceCommand(PrintThreadStackTraceCommand.newBuilder()
.setNonce(this.grpcChannelManager.addResponseFuture(responseFuture))
.build())
.build());
return CompletableFuture.completedFuture(null);
}
@Override
protected CompletableFuture<Void> processConsumeMessageDirectly(RemotingCommand command,
ConsumeMessageDirectlyResultRequestHeader header,
MessageExt messageExt, CompletableFuture<ConsumeMessageDirectlyResult> responseFuture) {
this.getTelemetryCommandStreamObserver().onNext(TelemetryCommand.newBuilder()
.setVerifyMessageCommand(VerifyMessageCommand.newBuilder()
.setNonce(this.grpcChannelManager.addResponseFuture(responseFuture))
.setMessage(GrpcConverter.buildMessage(messageExt))
.build())
.build());
return CompletableFuture.completedFuture(null);
}
public String getGroup() {
return group;
}
public String getClientId() {
return clientId;
}
public String getRemoteAddress() {
return remoteAddress;
}
public String getLocalAddress() {
return localAddress;
}
public StreamObserver<TelemetryCommand> getTelemetryCommandStreamObserver() {
return this.telemetryCommandRef.get();
}
}
@@ -0,0 +1,360 @@
/*
* 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.proxy.grpc.v2.client;
import apache.rocketmq.v2.ClientType;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.FilterExpression;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.NotifyClientTerminationResponse;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.SubscriptionEntry;
import apache.rocketmq.v2.TelemetryCommand;
import apache.rocketmq.v2.ThreadStackTrace;
import apache.rocketmq.v2.VerifyMessageResult;
import io.grpc.Context;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.broker.client.ProducerGroupEvent;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.filter.FilterAPI;
import org.apache.rocketmq.common.protocol.body.CMResult;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcChannelManager;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.GrpcContextConstants;
import org.apache.rocketmq.proxy.grpc.v2.channel.GrpcClientChannel;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
public class ClientActivity extends AbstractMessingActivity {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final GrpcChannelManager grpcChannelManager;
public ClientActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
this.grpcChannelManager = new GrpcChannelManager(messagingProcessor.getProxyOutService());
this.init();
}
protected void init() {
this.messagingProcessor.registerConsumerListener(new ConsumerIdsChangeListenerImpl());
this.messagingProcessor.registerProducerListener(new ProducerChangeListenerImpl());
}
public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
CompletableFuture<HeartbeatResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
String clientId = context.getVal(GrpcContextConstants.CLIENT_ID);
LanguageCode languageCode = context.getVal(GrpcContextConstants.LANGUAGE);
Settings clientSettings = grpcClientSettingsManager.getClientSettings(clientId);
switch (clientSettings.getClientType()) {
case PRODUCER: {
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, topicName, clientId);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
// use topic name as producer group
this.messagingProcessor.registerProducer(context, topicName, clientChannelInfo);
this.messagingProcessor.addTransactionSubscription(context, topicName, topicName);
}
break;
}
case PUSH_CONSUMER:
case SIMPLE_CONSUMER: {
if (!request.hasGroup()) {
throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "group cannot be empty for consumer");
}
String consumerGroup = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
GrpcClientChannel channel = this.grpcChannelManager.createChannel(ctx, consumerGroup, clientId);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
this.messagingProcessor.registerConsumer(
context,
consumerGroup,
clientChannelInfo,
this.buildConsumeType(clientSettings.getClientType()),
MessageModel.CLUSTERING,
ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
this.buildSubscriptionDataSet(clientSettings.getSubscription().getSubscriptionsList())
);
break;
}
default: {
throw new IllegalArgumentException("ClientType not exist " + clientSettings.getClientType());
}
}
future.complete(HeartbeatResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
return future;
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public CompletableFuture<NotifyClientTerminationResponse> notifyClientTermination(Context ctx, NotifyClientTerminationRequest request) {
CompletableFuture<NotifyClientTerminationResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
String clientId = context.getVal(GrpcContextConstants.CLIENT_ID);
LanguageCode languageCode = context.getVal(GrpcContextConstants.LANGUAGE);
Settings clientSettings = grpcClientSettingsManager.getClientSettings(clientId);
switch (clientSettings.getClientType()) {
case PRODUCER:
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
// user topic name as producer group
GrpcClientChannel channel = this.grpcChannelManager.removeChannel(topicName, clientId);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
if (channel != null) {
this.messagingProcessor.unRegisterProducer(context, topicName, clientChannelInfo);
}
}
break;
case PUSH_CONSUMER:
case SIMPLE_CONSUMER:
if (!request.hasGroup()) {
throw new GrpcProxyException(Code.ILLEGAL_CONSUMER_GROUP, "group cannot be empty for consumer");
}
String consumerGroup = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
GrpcClientChannel channel = this.grpcChannelManager.removeChannel(consumerGroup, clientId);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
if (channel != null) {
this.messagingProcessor.unRegisterConsumer(context, consumerGroup, clientChannelInfo);
}
break;
default:
break;
}
future.complete(NotifyClientTerminationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public StreamObserver<TelemetryCommand> telemetry(Context ctx, StreamObserver<TelemetryCommand> responseObserver) {
return new StreamObserver<TelemetryCommand>() {
@Override
public void onNext(TelemetryCommand request) {
switch (request.getCommandCase()) {
case SETTINGS: {
responseObserver.onNext(processClientSettings(ctx, request, responseObserver));
break;
}
case THREAD_STACK_TRACE: {
reportThreadStackTrace(ctx, request.getThreadStackTrace());
break;
}
case VERIFY_MESSAGE_RESULT: {
reportVerifyMessageResult(ctx, request.getVerifyMessageResult());
break;
}
default: {
ResponseWriter.writeException(responseObserver, new StatusRuntimeException(
Status.INVALID_ARGUMENT.withDescription("")
));
}
}
}
@Override
public void onError(Throwable t) {
log.error("telemetry on error", t);
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
protected TelemetryCommand processClientSettings(Context ctx, TelemetryCommand request, StreamObserver<TelemetryCommand> responseObserver) {
ProxyContext context = createContext(ctx);
String clientId = context.getVal(GrpcContextConstants.CLIENT_ID);
grpcClientSettingsManager.updateClientSettings(clientId, request.getSettings());
Settings settings = grpcClientSettingsManager.getClientSettings(clientId);
if (settings.hasPublishing()) {
for (Resource topic : settings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcClientChannel producerChannel = this.grpcChannelManager.createChannel(ctx, topicName, clientId);
producerChannel.setClientObserver(responseObserver);
}
}
if (settings.hasSubscription()) {
String groupName = GrpcConverter.wrapResourceWithNamespace(settings.getSubscription().getGroup());
GrpcClientChannel consumerChannel = this.grpcChannelManager.createChannel(ctx, groupName, clientId);
consumerChannel.setClientObserver(responseObserver);
}
return TelemetryCommand.newBuilder()
.setSettings(settings)
.build();
}
protected void reportThreadStackTrace(Context ctx, ThreadStackTrace request) {
String nonce = request.getNonce();
String threadStack = request.getThreadStackTrace();
CompletableFuture<ConsumerRunningInfo> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce);
if (responseFuture != null) {
try {
ConsumerRunningInfo runningInfo = new ConsumerRunningInfo();
runningInfo.setJstack(threadStack);
responseFuture.complete(runningInfo);
} catch (Throwable t) {
responseFuture.completeExceptionally(t);
}
}
}
protected void reportVerifyMessageResult(Context ctx, VerifyMessageResult request) {
String nonce = request.getNonce();
CompletableFuture<ConsumeMessageDirectlyResult> responseFuture = this.grpcChannelManager.getAndRemoveResponseFuture(nonce);
if (responseFuture != null) {
try {
ConsumeMessageDirectlyResult result = this.buildConsumeMessageDirectlyResult(request);
responseFuture.complete(result);
} catch (Throwable t) {
responseFuture.completeExceptionally(t);
}
}
}
protected ConsumeMessageDirectlyResult buildConsumeMessageDirectlyResult(VerifyMessageResult request) {
ConsumeMessageDirectlyResult consumeMessageDirectlyResult = new ConsumeMessageDirectlyResult();
switch (request.getStatus().getCode().getNumber()) {
case Code.OK_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_SUCCESS);
break;
}
case Code.FAILED_TO_CONSUME_MESSAGE_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_LATER);
break;
}
case Code.MESSAGE_CORRUPTED_VALUE: {
consumeMessageDirectlyResult.setConsumeResult(CMResult.CR_RETURN_NULL);
break;
}
}
consumeMessageDirectlyResult.setRemark("from gRPC client");
return consumeMessageDirectlyResult;
}
protected ConsumeType buildConsumeType(ClientType clientType) {
switch (clientType) {
case SIMPLE_CONSUMER:
return ConsumeType.CONSUME_ACTIVELY;
case PUSH_CONSUMER:
return ConsumeType.CONSUME_PASSIVELY;
default:
throw new IllegalArgumentException("Client type is not consumer, type: " + clientType);
}
}
protected Set<SubscriptionData> buildSubscriptionDataSet(List<SubscriptionEntry> subscriptionEntryList) {
Set<SubscriptionData> subscriptionDataSet = new HashSet<>();
for (SubscriptionEntry sub : subscriptionEntryList) {
String topicName = GrpcConverter.wrapResourceWithNamespace(sub.getTopic());
FilterExpression filterExpression = sub.getExpression();
subscriptionDataSet.add(buildSubscriptionData(topicName, filterExpression));
}
return subscriptionDataSet;
}
protected SubscriptionData buildSubscriptionData(String topicName, FilterExpression filterExpression) {
String expression = filterExpression.getExpression();
String expressionType = GrpcConverter.buildExpressionType(filterExpression.getType());
try {
return FilterAPI.build(topicName, expression, expressionType);
} catch (Exception e) {
throw new GrpcProxyException(Code.ILLEGAL_FILTER_EXPRESSION, "expression format is not correct", e);
}
}
protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener {
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {
if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
if (args == null || args.length < 1) {
return;
}
if (args[0] instanceof ClientChannelInfo) {
ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
grpcChannelManager.removeChannel(group, clientChannelInfo.getClientId());
grpcClientSettingsManager.removeClientSettings(clientChannelInfo.getClientId());
}
}
}
@Override
public void shutdown() {
}
}
protected class ProducerChangeListenerImpl implements ProducerChangeListener {
@Override
public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) {
if (event == ProducerGroupEvent.CLIENT_UNREGISTER) {
grpcChannelManager.removeChannel(group, clientChannelInfo.getClientId());
grpcClientSettingsManager.removeClientSettings(clientChannelInfo.getClientId());
}
}
}
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.service;
package org.apache.rocketmq.proxy.grpc.v2.common;
import apache.rocketmq.v2.ExponentialBackoff;
import apache.rocketmq.v2.Publishing;
@@ -23,13 +23,11 @@ import apache.rocketmq.v2.RetryPolicy;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.Subscription;
import com.google.protobuf.util.Durations;
import io.grpc.Context;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
public class GrpcClientManager {
public class GrpcClientSettingsManager {
// TODO: read config from topic or subscription configManager
protected static final Settings DEFAULT_PRODUCER_SETTINGS = Settings.newBuilder()
@@ -63,11 +61,6 @@ public class GrpcClientManager {
.build();
protected static final Map<String, Settings> CLIENT_SETTINGS_MAP = new ConcurrentHashMap<>();
public Settings getClientSettings(Context ctx) {
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
return CLIENT_SETTINGS_MAP.get(clientId);
}
public Settings getClientSettings(String clientId) {
return CLIENT_SETTINGS_MAP.get(clientId);
}
@@ -0,0 +1,229 @@
/*
* 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.proxy.grpc.v2.common;
import apache.rocketmq.v2.Broker;
import apache.rocketmq.v2.Digest;
import apache.rocketmq.v2.DigestType;
import apache.rocketmq.v2.Encoding;
import apache.rocketmq.v2.FilterType;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.MessageType;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SystemProperties;
import com.google.protobuf.ByteString;
import com.google.protobuf.util.Timestamps;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.filter.ExpressionType;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.NamespaceUtil;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.common.utils.BinaryUtil;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
public class GrpcConverter {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
public static String wrapResourceWithNamespace(Resource resource) {
return NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName());
}
public static MessageQueue buildMessageQueue(MessageExt messageExt, String brokerName) {
Broker broker = Broker.getDefaultInstance();
if (!StringUtils.isEmpty(brokerName)) {
broker = Broker.newBuilder()
.setName(brokerName)
.setId(0)
.build();
}
return MessageQueue.newBuilder()
.setId(messageExt.getQueueId())
.setTopic(Resource.newBuilder()
.setName(NamespaceUtil.withoutNamespace(messageExt.getTopic()))
.setResourceNamespace(NamespaceUtil.getNamespaceFromResource(messageExt.getTopic()))
.build())
.setBroker(broker)
.build();
}
public static String buildExpressionType(FilterType filterType) {
switch (filterType) {
case SQL:
return ExpressionType.SQL92;
case TAG:
default:
return ExpressionType.TAG;
}
}
public static Message buildMessage(MessageExt messageExt) {
Map<String, String> userProperties = buildUserAttributes(messageExt);
SystemProperties systemProperties = buildSystemProperties(messageExt);
Resource topic = buildResource(messageExt.getTopic());
return Message.newBuilder()
.setTopic(topic)
.putAllUserProperties(userProperties)
.setSystemProperties(systemProperties)
.setBody(ByteString.copyFrom(messageExt.getBody()))
.build();
}
protected static Map<String, String> buildUserAttributes(MessageExt messageExt) {
Map<String, String> userAttributes = new HashMap<>();
Map<String, String> properties = messageExt.getProperties();
for (Map.Entry<String, String> property : properties.entrySet()) {
if (!MessageConst.STRING_HASH_SET.contains(property.getKey())) {
userAttributes.put(property.getKey(), property.getValue());
}
}
return userAttributes;
}
protected static SystemProperties buildSystemProperties(MessageExt messageExt) {
SystemProperties.Builder systemPropertiesBuilder = SystemProperties.newBuilder();
// tag
String tag = messageExt.getUserProperty(MessageConst.PROPERTY_TAGS);
if (tag != null) {
systemPropertiesBuilder.setTag(tag);
}
// keys
String keys = messageExt.getKeys();
if (keys != null) {
String[] keysArray = keys.split(MessageConst.KEY_SEPARATOR);
systemPropertiesBuilder.addAllKeys(Arrays.asList(keysArray));
}
// message_id
String uniqKey = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
if (uniqKey != null) {
systemPropertiesBuilder.setMessageId(uniqKey);
}
// body_digest & body_encoding
String md5Result = BinaryUtil.generateMd5(messageExt.getBody());
Digest digest = Digest.newBuilder()
.setType(DigestType.MD5)
.setChecksum(md5Result)
.build();
systemPropertiesBuilder.setBodyDigest(digest);
if ((messageExt.getSysFlag() & MessageSysFlag.COMPRESSED_FLAG) == MessageSysFlag.COMPRESSED_FLAG) {
systemPropertiesBuilder.setBodyEncoding(Encoding.GZIP);
} else {
systemPropertiesBuilder.setBodyEncoding(Encoding.IDENTITY);
}
// message_type
String isTrans = messageExt.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
String isTransValue = "true";
if (isTransValue.equals(isTrans)) {
systemPropertiesBuilder.setMessageType(MessageType.TRANSACTION);
} else if (messageExt.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null
|| messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS) != null
|| messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
systemPropertiesBuilder.setMessageType(MessageType.DELAY);
} else if (messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY) != null) {
systemPropertiesBuilder.setMessageType(MessageType.FIFO);
} else {
systemPropertiesBuilder.setMessageType(MessageType.NORMAL);
}
// born_timestamp (millis)
long bornTimestamp = messageExt.getBornTimestamp();
systemPropertiesBuilder.setBornTimestamp(Timestamps.fromMillis(bornTimestamp));
// born_host
systemPropertiesBuilder.setBornHost(messageExt.getBornHostString());
// store_timestamp (millis)
long storeTimestamp = messageExt.getStoreTimestamp();
systemPropertiesBuilder.setStoreTimestamp(Timestamps.fromMillis(storeTimestamp));
// store_host
SocketAddress storeHost = messageExt.getStoreHost();
if (storeHost != null) {
systemPropertiesBuilder.setStoreHost(storeHost.toString());
}
// delivery_timestamp
String deliverMsString;
long deliverMs;
if (messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC) != null) {
long delayMs = TimeUnit.SECONDS.toMillis(Long.parseLong(messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC)));
deliverMs = System.currentTimeMillis() + delayMs;
systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
} else {
deliverMsString = messageExt.getProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS);
if (deliverMsString != null) {
deliverMs = Long.parseLong(deliverMsString);
systemPropertiesBuilder.setDeliveryTimestamp(Timestamps.fromMillis(deliverMs));
}
}
// sharding key
String shardingKey = messageExt.getProperty(MessageConst.PROPERTY_SHARDING_KEY);
if (shardingKey != null) {
systemPropertiesBuilder.setMessageGroup(shardingKey);
}
// receipt_handle && invisible_period
ReceiptHandle receiptHandle = ReceiptHandle.create(messageExt);
if (receiptHandle != null) {
systemPropertiesBuilder.setReceiptHandle(receiptHandle.encode());
}
// partition_id
systemPropertiesBuilder.setQueueId(messageExt.getQueueId());
// partition_offset
systemPropertiesBuilder.setQueueOffset(messageExt.getQueueOffset());
// delivery_attempt
systemPropertiesBuilder.setDeliveryAttempt(messageExt.getReconsumeTimes() + 1);
// trace context
String traceContext = messageExt.getProperty(MessageConst.PROPERTY_TRACE_CONTEXT);
if (traceContext != null) {
systemPropertiesBuilder.setTraceContext(traceContext);
}
return systemPropertiesBuilder.build();
}
public static Resource buildResource(String resourceNameWithNamespace) {
return Resource.newBuilder()
.setResourceNamespace(NamespaceUtil.getNamespaceFromResource(resourceNameWithNamespace))
.setName(NamespaceUtil.withoutNamespace(resourceNameWithNamespace))
.build();
}
}
@@ -0,0 +1,62 @@
/*
* 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.proxy.grpc.v2.common;
import apache.rocketmq.v2.Code;
import java.util.HashMap;
import java.util.Map;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
public class GrpcProxyException extends RuntimeException {
private ProxyException proxyException;
private Code code;
private static final Map<ProxyExceptionCode, Code> CODE_MAPPING = new HashMap<>();
static {
CODE_MAPPING.put(ProxyExceptionCode.INVALID_BROKER_NAME, Code.INVALID_RECEIPT_HANDLE);
CODE_MAPPING.put(ProxyExceptionCode.RECEIPT_HANDLE_EXPIRED, Code.RECEIPT_HANDLE_EXPIRED);
CODE_MAPPING.put(ProxyExceptionCode.FORBIDDEN, Code.FORBIDDEN);
}
public GrpcProxyException(Code code, String message) {
super(message);
this.code = code;
}
public GrpcProxyException(Code code, String message, Throwable t) {
super(message, t);
this.code = code;
}
public GrpcProxyException(ProxyException proxyException) {
super(proxyException);
this.proxyException = proxyException;
}
public Code getCode() {
if (this.code != null) {
return this.code;
}
if (this.proxyException != null) {
return CODE_MAPPING.getOrDefault(this.proxyException.getCode(), Code.INTERNAL_SERVER_ERROR);
}
return Code.INTERNAL_SERVER_ERROR;
}
}
@@ -15,28 +15,32 @@
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.adapter;
package org.apache.rocketmq.proxy.grpc.v2.common;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Status;
import java.util.concurrent.CompletionException;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.utils.ExceptionUtils;
public class ResponseBuilder {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
public static Status buildStatus(Throwable t) {
if (t instanceof CompletionException) {
t = t.getCause();
}
t = ExceptionUtils.getRealException(t);
if (t instanceof ProxyException) {
ProxyException proxyException = (ProxyException) t;
return ResponseBuilder.buildStatus(proxyException.getCode(), proxyException.getMessage());
t = new GrpcProxyException((ProxyException) t);
}
if (t instanceof GrpcProxyException) {
GrpcProxyException grpcProxyException = (GrpcProxyException) t;
return ResponseBuilder.buildStatus(grpcProxyException.getCode(), grpcProxyException.getMessage());
}
log.error("internal server error", t);
return ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "internal error");
}
@@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.adapter;
package org.apache.rocketmq.proxy.grpc.v2.common;
import io.grpc.stub.ServerCallStreamObserver;
import io.grpc.stub.StreamObserver;
@@ -0,0 +1,118 @@
/*
* 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.proxy.grpc.v2.consumer;
import apache.rocketmq.v2.AckMessageEntry;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
import apache.rocketmq.v2.AckMessageResultEntry;
import apache.rocketmq.v2.Code;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.AckStatus;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
public class AckMessageActivity extends AbstractMessingActivity {
public AckMessageActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<AckMessageResponse> ackMessage(Context ctx, AckMessageRequest request) {
ProxyContext proxyContext = createContext(ctx);
CompletableFuture<AckMessageResponse> future = new CompletableFuture<>();
try {
CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
for (int i = 0; i < request.getEntriesCount(); i++) {
futures[i] = processAckMessage(proxyContext, request, request.getEntries(i));
}
CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
if (throwable != null) {
future.completeExceptionally(throwable);
return;
}
List<AckMessageResultEntry> entryList = new ArrayList<>();
for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
entryFuture.thenAccept(entryList::add);
}
AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllEntries(entryList);
future.complete(responseBuilder.build());
});
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected CompletableFuture<AckMessageResultEntry> processAckMessage(ProxyContext ctx, AckMessageRequest request,
AckMessageEntry ackMessageEntry) {
CompletableFuture<AckMessageResultEntry> future = new CompletableFuture<>();
AckMessageResultEntry.Builder failResult = AckMessageResultEntry.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message failed"))
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle());
try {
ReceiptHandle receiptHandle = ReceiptHandle.decode(ackMessageEntry.getReceiptHandle());
CompletableFuture<AckResult> ackResultFuture = this.messagingProcessor.ackMessage(
ctx,
receiptHandle,
ackMessageEntry.getMessageId(),
GrpcConverter.wrapResourceWithNamespace(request.getGroup()),
GrpcConverter.wrapResourceWithNamespace(request.getTopic()));
ackResultFuture
.thenAccept(result -> future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, result)))
.exceptionally(throwable -> {
future.complete(failResult.setStatus(ResponseBuilder.buildStatus(throwable)).build());
return null;
});
} catch (Throwable t) {
future.complete(failResult.setStatus(ResponseBuilder.buildStatus(t)).build());
}
return future;
}
protected AckMessageResultEntry convertToAckMessageResultEntry(ProxyContext ctx, AckMessageEntry ackMessageEntry,
AckResult ackResult) {
if (AckStatus.OK.equals(ackResult.getStatus())) {
return AckMessageResultEntry.newBuilder()
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle())
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build();
}
return AckMessageResultEntry.newBuilder()
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle())
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"))
.build();
}
}
@@ -0,0 +1,76 @@
/*
* 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.proxy.grpc.v2.consumer;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
import apache.rocketmq.v2.Code;
import com.google.protobuf.util.Durations;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.AckStatus;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
public class ChangeInvisibleDurationActivity extends AbstractMessingActivity {
public ChangeInvisibleDurationActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<ChangeInvisibleDurationResponse> changeInvisibleDuration(Context ctx,
ChangeInvisibleDurationRequest request) {
ProxyContext context = createContext(ctx);
CompletableFuture<ChangeInvisibleDurationResponse> future = new CompletableFuture<>();
try {
ReceiptHandle receiptHandle = ReceiptHandle.decode(request.getReceiptHandle());
return this.messagingProcessor.changeInvisibleTime(
context,
receiptHandle,
request.getMessageId(),
GrpcConverter.wrapResourceWithNamespace(request.getGroup()),
GrpcConverter.wrapResourceWithNamespace(request.getTopic()),
Durations.toMillis(request.getInvisibleDuration())
).thenApply(ackResult -> convertToChangeInvisibleDurationResponse(context, request, ackResult));
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected ChangeInvisibleDurationResponse convertToChangeInvisibleDurationResponse(ProxyContext ctx,
ChangeInvisibleDurationRequest request, AckResult ackResult) {
if (AckStatus.OK.equals(ackResult.getStatus())) {
return ChangeInvisibleDurationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.setReceiptHandle(ackResult.getExtraInfo())
.build();
}
return ChangeInvisibleDurationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "changeInvisibleDuration failed: status is abnormal"))
.build();
}
}
@@ -0,0 +1,46 @@
/*
* 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.proxy.grpc.v2.consumer;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.common.utils.FilterUtils;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.GrpcContextConstants;
import org.apache.rocketmq.proxy.processor.PopMessageResultFilter;
public class PopMessageResultFilterImpl implements PopMessageResultFilter {
private final GrpcClientSettingsManager grpcClientSettingsManager;
public PopMessageResultFilterImpl(GrpcClientSettingsManager manager) {
grpcClientSettingsManager = manager;
}
@Override
public FilterResult filterMessage(ProxyContext ctx, String consumerGroup, SubscriptionData subscriptionData, MessageExt messageExt) {
int maxAttempts = grpcClientSettingsManager.getClientSettings(ctx.getVal(GrpcContextConstants.CLIENT_ID)).getBackoffPolicy().getMaxAttempts();
if (!FilterUtils.isTagMatched(subscriptionData.getTagsSet(), messageExt.getTags())) {
return FilterResult.NO_MATCH;
}
if (messageExt.getReconsumeTimes() >= maxAttempts) {
return FilterResult.TO_DLQ;
}
return FilterResult.MATCH;
}
}
@@ -0,0 +1,129 @@
/*
* 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.proxy.grpc.v2.consumer;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.FilterExpression;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import com.google.protobuf.util.Durations;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.constant.ConsumeInitMode;
import org.apache.rocketmq.common.filter.FilterAPI;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.proxy.processor.QueueSelector;
import org.apache.rocketmq.proxy.service.route.MessageQueueSelector;
import org.apache.rocketmq.proxy.service.route.MessageQueueView;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
public class ReceiveMessageActivity extends AbstractMessingActivity {
public ReceiveMessageActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public void receiveMessage(Context ctx, ReceiveMessageRequest request,
StreamObserver<ReceiveMessageResponse> responseObserver) {
ProxyContext proxyContext = createContext(ctx);
boolean fifo = false;
ReceiveMessageResponseStreamWriter writer = new ReceiveMessageResponseStreamWriter(
this.messagingProcessor,
responseObserver
);
long timeRemaining = ctx.getDeadline().timeRemaining(TimeUnit.MILLISECONDS);
long pollTime = timeRemaining - ConfigurationManager.getProxyConfig().getLongPollingReserveTimeInMillis();
if (pollTime <= 0) {
pollTime = timeRemaining;
}
if (pollTime <= 0) {
writer.write(proxyContext, Code.MESSAGE_NOT_FOUND, "no new message");
return;
}
String topic = GrpcConverter.wrapResourceWithNamespace(request.getMessageQueue().getTopic());
String group = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
FilterExpression filterExpression = request.getFilterExpression();
SubscriptionData subscriptionData;
try {
subscriptionData = FilterAPI.build(topic, filterExpression.getExpression(),
GrpcConverter.buildExpressionType(filterExpression.getType()));
} catch (Exception e) {
writer.write(proxyContext, Code.ILLEGAL_FILTER_EXPRESSION, e.getMessage());
return;
}
this.messagingProcessor.popMessage(
proxyContext,
new ReceiveMessageQueueSelector(
request.getMessageQueue().getBroker().getName()
),
group,
topic,
request.getBatchSize(),
Durations.toMillis(request.getInvisibleDuration()),
pollTime,
ConsumeInitMode.MAX,
subscriptionData,
fifo,
new PopMessageResultFilterImpl(grpcClientSettingsManager),
timeRemaining
).thenAccept(popResult -> writer.write(proxyContext, request, popResult))
.exceptionally(t -> {
writer.write(proxyContext, request, t);
return null;
});
}
protected static class ReceiveMessageQueueSelector implements QueueSelector {
private final String brokerName;
public ReceiveMessageQueueSelector(String brokerName) {
this.brokerName = brokerName;
}
@Override
public SelectableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView) {
try {
SelectableMessageQueue selectableMessageQueue = null;
MessageQueueSelector messageQueueSelector = messageQueueView.getReadSelector();
if (StringUtils.isNotBlank(brokerName)) {
selectableMessageQueue = messageQueueSelector.getQueueByBrokerName(brokerName);
}
if (selectableMessageQueue == null) {
selectableMessageQueue = messageQueueSelector.selectOne(true);
}
return selectableMessageQueue;
} catch (Throwable t) {
return null;
}
}
}
}
@@ -0,0 +1,137 @@
/*
* 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.proxy.grpc.v2.consumer;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.stub.StreamObserver;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseWriter;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
public class ReceiveMessageResponseStreamWriter {
protected static final long NACK_INVISIBLE_TIME = Duration.ofSeconds(1).toMillis();
protected final MessagingProcessor messagingProcessor;
protected final StreamObserver<ReceiveMessageResponse> streamObserver;
public ReceiveMessageResponseStreamWriter(
MessagingProcessor messagingProcessor,
StreamObserver<ReceiveMessageResponse> observer) {
this.messagingProcessor = messagingProcessor;
this.streamObserver = observer;
}
public void write(ProxyContext ctx, ReceiveMessageRequest request, PopResult popResult) {
PopStatus status = popResult.getPopStatus();
List<MessageExt> messageFoundList = popResult.getMsgFoundList();
try {
switch (status) {
case FOUND:
if (messageFoundList.isEmpty()) {
streamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.MESSAGE_NOT_FOUND, "no new message"))
.build());
} else {
streamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
Iterator<MessageExt> messageIterator = messageFoundList.iterator();
while (messageIterator.hasNext()) {
MessageExt curMessageExt = messageIterator.next();
Message curMessage = convertToMessage(curMessageExt);
try {
streamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setMessage(curMessage)
.build());
} catch (Throwable t) {
this.processThrowableWhenWriteMessage(t, ctx, request, curMessageExt);
messageIterator.forEachRemaining(messageExt ->
this.processThrowableWhenWriteMessage(t, ctx, request, messageExt));
return;
}
}
}
break;
case POLLING_FULL:
streamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.TOO_MANY_REQUESTS, "polling full"))
.build());
break;
case NO_NEW_MSG:
case POLLING_NOT_FOUND:
default:
streamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.MESSAGE_NOT_FOUND, "no new message"))
.build());
break;
}
} catch (Throwable t) {
write(ctx, request, t);
} finally {
streamObserver.onCompleted();
}
}
protected Message convertToMessage(MessageExt messageExt) {
return GrpcConverter.buildMessage(messageExt);
}
protected void processThrowableWhenWriteMessage(Throwable throwable,
ProxyContext ctx, ReceiveMessageRequest request, MessageExt messageExt) {
ReceiptHandle handle = ReceiptHandle.create(messageExt);
if (handle == null) {
return;
}
this.messagingProcessor.changeInvisibleTime(
ctx,
handle,
messageExt.getMsgId(),
GrpcConverter.wrapResourceWithNamespace(request.getGroup()),
GrpcConverter.wrapResourceWithNamespace(request.getMessageQueue().getTopic()),
NACK_INVISIBLE_TIME
);
}
public void write(ProxyContext ctx, Code code, String message) {
ResponseWriter.write(
streamObserver,
ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.buildStatus(code, message)).build()
);
}
public void write(ProxyContext ctx, ReceiveMessageRequest request, Throwable throwable) {
ResponseWriter.write(
streamObserver,
ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.buildStatus(throwable)).build()
);
}
}
@@ -0,0 +1,65 @@
/*
* 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.proxy.grpc.v2.producer;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ForwardMessageToDLQActivity extends AbstractMessingActivity {
public ForwardMessageToDLQActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<ForwardMessageToDeadLetterQueueResponse> forwardMessageToDeadLetterQueue(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
CompletableFuture<ForwardMessageToDeadLetterQueueResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
ReceiptHandle receiptHandle = ReceiptHandle.decode(request.getReceiptHandle());
return this.messagingProcessor.forwardMessageToDeadLetterQueue(
context,
receiptHandle,
request.getMessageId(),
GrpcConverter.wrapResourceWithNamespace(request.getGroup()),
GrpcConverter.wrapResourceWithNamespace(request.getTopic())
).thenApply(result -> convertToForwardMessageToDeadLetterQueueResponse(ctx, result));
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected ForwardMessageToDeadLetterQueueResponse convertToForwardMessageToDeadLetterQueueResponse(Context ctx,
RemotingCommand result) {
return ForwardMessageToDeadLetterQueueResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(result.getCode(), result.getRemark()))
.build();
}
}
@@ -0,0 +1,244 @@
/*
* 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.proxy.grpc.v2.producer;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Encoding;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.MessageType;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.SendReceipt;
import apache.rocketmq.v2.SystemProperties;
import com.beust.jcommander.internal.Lists;
import com.google.common.collect.Maps;
import com.google.common.hash.Hashing;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Durations;
import com.google.protobuf.util.Timestamps;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.MessageAccessor;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcProxyException;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.proxy.processor.QueueSelector;
import org.apache.rocketmq.proxy.service.route.MessageQueueView;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
public class SendMessageActivity extends AbstractMessingActivity {
public SendMessageActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<SendMessageResponse> sendMessage(Context ctx, SendMessageRequest request) {
ProxyContext context = createContext(ctx);
CompletableFuture<SendMessageResponse> future = new CompletableFuture<>();
try {
if (request.getMessagesCount() <= 0) {
throw new GrpcProxyException(Code.MESSAGE_CORRUPTED, "no message to send");
}
List<Message> messageList = request.getMessagesList();
Resource topic = messageList.get(0).getTopic();
future = this.messagingProcessor.sendMessage(
context,
new SendMessageQueueSelector(request),
GrpcConverter.wrapResourceWithNamespace(topic),
buildMessage(context, request.getMessagesList(), topic)
).thenApply(result -> convertToSendMessageResponse(context, request, result));
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected List<MessageExt> buildMessage(ProxyContext context, List<Message> protoMessageList, Resource topic) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
List<MessageExt> messageExtList = new ArrayList<>();
for (apache.rocketmq.v2.Message protoMessage : protoMessageList) {
if (!protoMessage.getTopic().equals(topic)) {
throw new GrpcProxyException(Code.MESSAGE_CORRUPTED, "topic in message is not same");
}
// here use topicName as producerGroup for transactional checker.
messageExtList.add(buildMessage(protoMessage, topicName));
}
return messageExtList;
}
protected MessageExt buildMessage(Message protoMessage, String producerGroup) {
String topicName = GrpcConverter.wrapResourceWithNamespace(protoMessage.getTopic());
MessageExt messageExt = new MessageExt();
messageExt.setTopic(topicName);
messageExt.setBody(protoMessage.getBody().toByteArray());
Map<String, String> messageProperty = this.buildMessageProperty(protoMessage, producerGroup);
// sysFlag (body encoding & message type)
SystemProperties systemProperties = protoMessage.getSystemProperties();
int sysFlag = 0;
Encoding bodyEncoding = systemProperties.getBodyEncoding();
if (bodyEncoding.equals(Encoding.GZIP)) {
sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
}
// transaction
MessageType messageType = systemProperties.getMessageType();
if (messageType.equals(MessageType.TRANSACTION)) {
sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}
messageExt.setSysFlag(sysFlag);
MessageAccessor.setProperties(messageExt, messageProperty);
return messageExt;
}
protected Map<String, String> buildMessageProperty(Message message, String producerGroup) {
org.apache.rocketmq.common.message.Message messageWithHeader = new org.apache.rocketmq.common.message.Message();
// set user properties
Map<String, String> userProperties = message.getUserPropertiesMap();
for (String key : userProperties.keySet()) {
if (MessageConst.STRING_HASH_SET.contains(key)) {
throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_PROPERTY_KEY, "property is used by system: " + key);
}
}
MessageAccessor.setProperties(messageWithHeader, Maps.newHashMap(userProperties));
// set tag
String tag = message.getSystemProperties().getTag();
if (!"".equals(tag)) {
messageWithHeader.setTags(tag);
}
// set keys
List<String> keysList = message.getSystemProperties().getKeysList();
if (keysList.size() > 0) {
messageWithHeader.setKeys(keysList);
}
// set message id
String messageId = message.getSystemProperties().getMessageId();
if ("".equals(messageId)) {
throw new GrpcProxyException(Code.ILLEGAL_MESSAGE_ID, "message id is empty");
}
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, messageId);
// set transaction property
MessageType messageType = message.getSystemProperties().getMessageType();
if (messageType.equals(MessageType.TRANSACTION)) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
Duration transactionResolveDelay = message.getSystemProperties().getOrphanedTransactionRecoveryDuration();
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS,
String.valueOf(Durations.toSeconds(transactionResolveDelay)));
}
// set delay level or deliver timestamp
if (message.getSystemProperties().hasDeliveryTimestamp()) {
Timestamp deliveryTimestamp = message.getSystemProperties().getDeliveryTimestamp();
String timestampString = String.valueOf(Timestamps.toMillis(deliveryTimestamp));
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TIMER_DELIVER_MS, timestampString);
}
// set reconsume times
int reconsumeTimes = message.getSystemProperties().getDeliveryAttempt();
MessageAccessor.setReconsumeTime(messageWithHeader, String.valueOf(reconsumeTimes));
// set producer group
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_PRODUCER_GROUP, producerGroup);
// set message group
String messageGroup = message.getSystemProperties().getMessageGroup();
if (!messageGroup.isEmpty()) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_SHARDING_KEY, messageGroup);
}
// set trace context
String traceContext = message.getSystemProperties().getTraceContext();
if (!traceContext.isEmpty()) {
MessageAccessor.putProperty(messageWithHeader, MessageConst.PROPERTY_TRACE_CONTEXT, traceContext);
}
return messageWithHeader.getProperties();
}
protected SendMessageResponse convertToSendMessageResponse(ProxyContext ctx, SendMessageRequest request,
SendResult result) {
if (result.getSendStatus() != SendStatus.SEND_OK) {
return SendMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "send message failed, sendStatus=" + result.getSendStatus()))
.build();
}
List<SendReceipt> sendReceiptList = Lists.newArrayList();
sendReceiptList.add(SendReceipt.newBuilder()
.setMessageId(StringUtils.defaultString(result.getMsgId()))
.setTransactionId(StringUtils.defaultString(result.getTransactionId()))
.build());
return SendMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllReceipts(sendReceiptList)
.build();
}
protected static class SendMessageQueueSelector implements QueueSelector {
private final SendMessageRequest request;
public SendMessageQueueSelector(SendMessageRequest request) {
this.request = request;
}
@Override
public SelectableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView) {
try {
Message message = request.getMessages(0);
String shardingKey = null;
if (request.getMessagesCount() == 1) {
shardingKey = message.getSystemProperties().getMessageGroup();
}
SelectableMessageQueue targetMessageQueue;
if (StringUtils.isNotEmpty(shardingKey)) {
// With shardingKey
List<SelectableMessageQueue> writeQueues = messageQueueView.getWriteSelector().getQueues();
int bucket = Hashing.consistentHash(shardingKey.hashCode(), writeQueues.size());
targetMessageQueue = writeQueues.get(bucket);
} else {
targetMessageQueue = messageQueueView.getWriteSelector().selectOne(false);
}
return targetMessageQueue;
} catch (Exception e) {
return null;
}
}
}
}
@@ -0,0 +1,246 @@
/*
* 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.proxy.grpc.v2.route;
import apache.rocketmq.v2.Address;
import apache.rocketmq.v2.AddressScheme;
import apache.rocketmq.v2.Assignment;
import apache.rocketmq.v2.Broker;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Endpoints;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.Permission;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import apache.rocketmq.v2.Resource;
import com.google.common.net.HostAndPort;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.constant.PermName;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
import org.apache.rocketmq.proxy.service.route.TopicRouteHelper;
public class RouteActivity extends AbstractMessingActivity {
public RouteActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
CompletableFuture<QueryRouteResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
List<org.apache.rocketmq.proxy.common.Address> addressList = this.convertToAddressList(request.getEndpoints());
ProxyTopicRouteData proxyTopicRouteData = this.messagingProcessor.getTopicRouteDataForProxy(
context,
addressList,
GrpcConverter.wrapResourceWithNamespace(request.getTopic()));
List<MessageQueue> messageQueueList = new ArrayList<>();
Map<String, Map<Long, Broker>> brokerMap = buildBrokerMap(proxyTopicRouteData.getBrokerDatas());
for (QueueData queueData : proxyTopicRouteData.getQueueDatas()) {
String brokerName = queueData.getBrokerName();
Map<Long, Broker> brokerIdMap = brokerMap.get(brokerName);
if (brokerIdMap == null) {
break;
}
for (Broker broker : brokerIdMap.values()) {
messageQueueList.addAll(this.genMessageQueueFromQueueData(queueData, request.getTopic(), broker));
}
}
QueryRouteResponse response = QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllMessageQueues(messageQueueList)
.build();
future.complete(response);
} catch (Throwable t) {
if (TopicRouteHelper.isTopicNotExistError(t)) {
future.complete(QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.TOPIC_NOT_FOUND, t.getMessage()))
.build());
} else {
future.completeExceptionally(t);
}
}
return future;
}
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
CompletableFuture<QueryAssignmentResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
List<org.apache.rocketmq.proxy.common.Address> addressList = this.convertToAddressList(request.getEndpoints());
ProxyTopicRouteData proxyTopicRouteData = this.messagingProcessor.getTopicRouteDataForProxy(
context,
addressList,
GrpcConverter.wrapResourceWithNamespace(request.getTopic()));
List<Assignment> assignments = new ArrayList<>();
Map<String, Map<Long, Broker>> brokerMap = buildBrokerMap(proxyTopicRouteData.getBrokerDatas());
for (QueueData queueData : proxyTopicRouteData.getQueueDatas()) {
Map<Long, Broker> brokerIdMap = brokerMap.get(queueData.getBrokerName());
if (brokerIdMap != null) {
for (Map.Entry<Long, Broker> brokerIdEntry : brokerIdMap.entrySet()) {
MessageQueue defaultMessageQueue = MessageQueue.newBuilder()
.setTopic(request.getTopic())
.setId(-1)
.setPermission(this.convertToPermission(queueData.getPerm()))
.setBroker(brokerIdEntry.getValue())
.build();
assignments.add(Assignment.newBuilder()
.setMessageQueue(defaultMessageQueue)
.build());
}
}
}
QueryAssignmentResponse response = QueryAssignmentResponse.newBuilder()
.addAllAssignments(assignments)
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build();
future.complete(response);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected Permission convertToPermission(int perm) {
boolean isReadable = PermName.isReadable(perm);
boolean isWriteable = PermName.isWriteable(perm);
if (isReadable && isWriteable) {
return Permission.READ_WRITE;
}
if (isReadable) {
return Permission.READ;
}
if (isWriteable) {
return Permission.WRITE;
}
return Permission.NONE;
}
protected List<org.apache.rocketmq.proxy.common.Address> convertToAddressList(Endpoints endpoints) {
List<org.apache.rocketmq.proxy.common.Address> addressList = new ArrayList<>();
for (Address address : endpoints.getAddressesList()) {
addressList.add(new org.apache.rocketmq.proxy.common.Address(
org.apache.rocketmq.proxy.common.Address.AddressScheme.valueOf(endpoints.getScheme().name()),
HostAndPort.fromParts(address.getHost(), address.getPort()))
);
}
return addressList;
}
protected Map<String /*brokerName*/, Map<Long /*brokerID*/, Broker>> buildBrokerMap(
List<ProxyTopicRouteData.ProxyBrokerData> brokerDataList) {
Map<String, Map<Long, Broker>> brokerMap = new HashMap<>();
for (ProxyTopicRouteData.ProxyBrokerData brokerData : brokerDataList) {
Map<Long, Broker> brokerIdMap = new HashMap<>();
String brokerName = brokerData.getBrokerName();
for (Map.Entry<Long, List<org.apache.rocketmq.proxy.common.Address>> entry : brokerData.getBrokerAddrs().entrySet()) {
Long brokerId = entry.getKey();
List<Address> addressList = new ArrayList<>();
AddressScheme addressScheme = AddressScheme.IPv4;
for (org.apache.rocketmq.proxy.common.Address address : entry.getValue()) {
addressScheme = AddressScheme.valueOf(address.getAddressScheme().name());
addressList.add(Address.newBuilder()
.setHost(address.getHostAndPort().getHost())
.setPort(address.getHostAndPort().getPort())
.build());
}
Broker broker = Broker.newBuilder()
.setName(brokerName)
.setId(Math.toIntExact(brokerId))
.setEndpoints(Endpoints.newBuilder()
.setScheme(addressScheme)
.addAllAddresses(addressList)
.build())
.build();
brokerIdMap.put(brokerId, broker);
}
brokerMap.put(brokerName, brokerIdMap);
}
return brokerMap;
}
protected List<MessageQueue> genMessageQueueFromQueueData(QueueData queueData, Resource topic, Broker broker) {
List<MessageQueue> messageQueueList = new ArrayList<>();
int r = 0;
int w = 0;
int rw = 0;
if (PermName.isWriteable(queueData.getPerm()) && PermName.isReadable(queueData.getPerm())) {
rw = Math.min(queueData.getWriteQueueNums(), queueData.getReadQueueNums());
r = queueData.getReadQueueNums() - rw;
w = queueData.getWriteQueueNums() - rw;
} else if (PermName.isWriteable(queueData.getPerm())) {
w = queueData.getWriteQueueNums();
} else if (PermName.isReadable(queueData.getPerm())) {
r = queueData.getReadQueueNums();
}
// r here means readOnly queue nums, w means writeOnly queue nums, while rw means both readable and writable queue nums.
int queueIdIndex = 0;
for (int i = 0; i < r; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.READ)
.build();
messageQueueList.add(messageQueue);
}
for (int i = 0; i < w; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.WRITE)
.build();
messageQueueList.add(messageQueue);
}
for (int i = 0; i < rw; i++) {
MessageQueue messageQueue = MessageQueue.newBuilder().setBroker(broker).setTopic(topic)
.setId(queueIdIndex++)
.setPermission(Permission.READ_WRITE)
.build();
messageQueueList.add(messageQueue);
}
return messageQueueList;
}
}
@@ -1,99 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.Endpoints;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.proxy.common.ParameterConverter;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.AssignmentQueueSelector;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.DefaultAssignmentQueueSelector;
public abstract class AbstractRouteService extends BaseService {
protected volatile ParameterConverter<Endpoints, Endpoints> queryRouteEndpointConverter;
protected volatile ResponseHook<QueryRouteRequest, QueryRouteResponse> queryRouteHook;
protected volatile ParameterConverter<Endpoints, Endpoints> queryAssignmentEndpointConverter;
protected volatile ResponseHook<QueryAssignmentRequest, QueryAssignmentResponse> queryAssignmentHook;
protected volatile AssignmentQueueSelector assignmentQueueSelector;
protected final GrpcClientManager grpcClientManager;
public AbstractRouteService(ConnectorManager connectorManager, GrpcClientManager grpcClientManager) {
super(connectorManager);
this.grpcClientManager = grpcClientManager;
this.queryRouteEndpointConverter = (ctx, parameter) -> parameter;
this.queryAssignmentEndpointConverter = (ctx, parameter) -> parameter;
this.assignmentQueueSelector = new DefaultAssignmentQueueSelector(this.connectorManager.getTopicRouteCache());
}
public abstract CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request);
public abstract CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request);
public ParameterConverter<Endpoints, Endpoints> getQueryRouteEndpointConverter() {
return queryRouteEndpointConverter;
}
public void setQueryRouteEndpointConverter(
ParameterConverter<Endpoints, Endpoints> queryRouteEndpointConverter) {
this.queryRouteEndpointConverter = queryRouteEndpointConverter;
}
public ResponseHook<QueryRouteRequest, QueryRouteResponse> getQueryRouteHook() {
return queryRouteHook;
}
public void setQueryRouteHook(
ResponseHook<QueryRouteRequest, QueryRouteResponse> queryRouteHook) {
this.queryRouteHook = queryRouteHook;
}
public ParameterConverter<Endpoints, Endpoints> getQueryAssignmentEndpointConverter() {
return queryAssignmentEndpointConverter;
}
public void setQueryAssignmentEndpointConverter(
ParameterConverter<Endpoints, Endpoints> queryAssignmentEndpointConverter) {
this.queryAssignmentEndpointConverter = queryAssignmentEndpointConverter;
}
public AssignmentQueueSelector getAssignmentQueueSelector() {
return assignmentQueueSelector;
}
public void setAssignmentQueueSelector(
AssignmentQueueSelector assignmentQueueSelector) {
this.assignmentQueueSelector = assignmentQueueSelector;
}
public ResponseHook<QueryAssignmentRequest, QueryAssignmentResponse> getQueryAssignmentHook() {
return queryAssignmentHook;
}
public void setQueryAssignmentHook(
ResponseHook<QueryAssignmentRequest, QueryAssignmentResponse> queryAssignmentHook) {
this.queryAssignmentHook = queryAssignmentHook;
}
}
@@ -1,122 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.Iterator;
import java.util.List;
import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseWriter;
public abstract class BaseReceiveMessageResponseStreamWriter {
protected final StreamObserver<ReceiveMessageResponse> streamObserver;
protected final ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook;
protected final ReceiveMessageResultFilter receiveMessageResultFilter;
public interface Builder {
BaseReceiveMessageResponseStreamWriter build(
StreamObserver<ReceiveMessageResponse> observer,
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> hook);
}
public BaseReceiveMessageResponseStreamWriter(
StreamObserver<ReceiveMessageResponse> observer,
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> hook,
ReceiveMessageResultFilter messageResultFilter) {
streamObserver = observer;
receiveMessageHook = hook;
receiveMessageResultFilter = messageResultFilter;
}
public void write(Context ctx, ReceiveMessageRequest request, PopStatus status, List<MessageExt> messageFoundList) {
ReceiveMessageResponseStreamObserver responseStreamObserver = new ReceiveMessageResponseStreamObserver(
ctx,
request,
receiveMessageHook,
streamObserver);
try {
switch (status) {
case FOUND:
List<Message> messageList = this.receiveMessageResultFilter.filterMessage(ctx, request, messageFoundList);
if (messageList.isEmpty()) {
responseStreamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, "no new message"))
.build());
} else {
responseStreamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
Iterator<Message> messageIterator = messageList.iterator();
while (messageIterator.hasNext()) {
Message curMessage = messageIterator.next();
try {
responseStreamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setMessage(curMessage)
.build());
} catch (Throwable t) {
this.processThrowableWhenWriteMessage(t, ctx, request, curMessage);
messageIterator.forEachRemaining(message ->
this.processThrowableWhenWriteMessage(t, ctx, request, message));
return;
}
}
}
break;
case POLLING_FULL:
responseStreamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.TOO_MANY_REQUESTS, "polling full"))
.build());
break;
case NO_NEW_MSG:
case POLLING_NOT_FOUND:
default:
responseStreamObserver.onNext(ReceiveMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, "no new message"))
.build());
break;
}
} catch (Throwable t) {
write(ctx, request, t);
} finally {
responseStreamObserver.onCompleted();
}
}
protected abstract void processThrowableWhenWriteMessage(Throwable throwable,
Context context, ReceiveMessageRequest request, Message message);
public void write(Context ctx, ReceiveMessageRequest request, Throwable throwable) {
ReceiveMessageResponseStreamObserver responseStreamObserver = new ReceiveMessageResponseStreamObserver(
ctx,
request,
receiveMessageHook,
streamObserver);
ResponseWriter.write(
responseStreamObserver,
ReceiveMessageResponse.newBuilder().setStatus(ResponseBuilder.buildStatus(throwable)).build()
);
}
}
@@ -1,67 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.Settings;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.utils.FilterUtils;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
public abstract class BaseReceiveMessageResultFilter implements ReceiveMessageResultFilter {
protected final GrpcClientManager grpcClientManager;
public BaseReceiveMessageResultFilter(GrpcClientManager manager) {
grpcClientManager = manager;
}
@Override
public List<Message> filterMessage(Context ctx, ReceiveMessageRequest request, List<MessageExt> messageExtList) {
if (messageExtList == null || messageExtList.isEmpty()) {
return Collections.emptyList();
}
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getMessageQueue().getTopic());
SubscriptionData subscriptionData = GrpcConverter.buildSubscriptionData(topicName, request.getFilterExpression());
Settings settings = grpcClientManager.getClientSettings(ctx);
int maxAttempts = settings.getBackoffPolicy().getMaxAttempts();
List<Message> resMessageList = new ArrayList<>();
for (MessageExt messageExt : messageExtList) {
if (!FilterUtils.isTagMatched(subscriptionData.getTagsSet(), messageExt.getTags())) {
processNoMatchMessage(ctx, request, messageExt);
continue;
}
if (messageExt.getReconsumeTimes() >= maxAttempts) {
processExceedMaxAttemptsMessage(ctx, request, messageExt, maxAttempts);
continue;
}
resMessageList.add(GrpcConverter.buildMessage(messageExt));
}
return resMessageList;
}
protected abstract void processNoMatchMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt);
protected abstract void processExceedMaxAttemptsMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt, int maxAttempts);
}
@@ -1,77 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.FilterExpression;
import apache.rocketmq.v2.Resource;
import io.grpc.Context;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
public abstract class BaseService implements StartAndShutdown {
protected final ConnectorManager connectorManager;
public BaseService(ConnectorManager connectorManager) {
this.connectorManager = connectorManager;
}
public static ReceiptHandle resolveReceiptHandle(Context ctx, String receiptHandleStr) {
ReceiptHandle receiptHandle = ReceiptHandle.decode(receiptHandleStr);
if (receiptHandle.isExpired()) {
throw new ProxyException(Code.RECEIPT_HANDLE_EXPIRED, "handle has expired");
}
return receiptHandle;
}
public static String getBrokerAddr(Context ctx, TopicRouteCache topicRouteCache, String brokerName) throws Exception {
if (StringUtils.isBlank(brokerName)) {
throw new ProxyException(Code.UNRECOGNIZED, "broker name is empty");
}
String addr = topicRouteCache.getBrokerAddr(brokerName);
if (StringUtils.isBlank(addr)) {
throw new ProxyException(Code.UNRECOGNIZED, brokerName + " not exist");
}
return addr;
}
protected String getBrokerAddr(Context ctx, String brokerName) throws Exception {
return getBrokerAddr(ctx, this.connectorManager.getTopicRouteCache(), brokerName);
}
protected void checkSubscriptionData(Resource topic, FilterExpression filterExpression) {
// for checking filterExpression.
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcConverter.buildSubscriptionData(topicName, filterExpression);
}
@Override
public void start() throws Exception {
}
@Override
public void shutdown() throws Exception {
}
}
@@ -1,65 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.common.TelemetryCommandManager;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.GrpcClientChannel;
public class ClientSettingsService {
private final ChannelManager channelManager;
private final GrpcClientManager grpcClientManager;
private final TelemetryCommandManager telemetryCommandManager;
public ClientSettingsService(ChannelManager channelManager,
GrpcClientManager grpcClientManager,
TelemetryCommandManager telemetryCommandManager) {
this.channelManager = channelManager;
this.grpcClientManager = grpcClientManager;
this.telemetryCommandManager = telemetryCommandManager;
}
public TelemetryCommand processClientSettings(Context ctx, TelemetryCommand request, StreamObserver<TelemetryCommand> responseObserver) {
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
grpcClientManager.updateClientSettings(clientId, request.getSettings());
Settings settings = grpcClientManager.getClientSettings(clientId);
if (settings.hasPublishing()) {
for (Resource topic : settings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcClientChannel producerChannel = GrpcClientChannel.create(channelManager, topicName, clientId, telemetryCommandManager);
producerChannel.setClientObserver(responseObserver);
}
}
if (settings.hasSubscription()) {
String groupName = GrpcConverter.wrapResourceWithNamespace(settings.getSubscription().getGroup());
GrpcClientChannel consumerChannel = GrpcClientChannel.create(channelManager, groupName, clientId, telemetryCommandManager);
consumerChannel.setClientObserver(responseObserver);
}
return TelemetryCommand.newBuilder()
.setSettings(settings)
.build();
}
}
@@ -1,183 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.EndTransactionResponse;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.NotifyClientTerminationResponse;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.common.TelemetryCommandManager;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateCheckRequest;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.ConsumerService;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.ForwardClientService;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.ProducerService;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.RouteService;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.TransactionService;
public class ClusterGrpcService extends AbstractStartAndShutdown implements GrpcForwardService {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryImpl("ClusterGrpcServiceScheduledThread")
);
protected ChannelManager channelManager;
protected ConnectorManager connectorManager;
protected ProducerService producerService;
protected ConsumerService consumerService;
protected RouteService routeService;
protected ForwardClientService clientService;
protected TransactionService transactionService;
protected TelemetryCommandManager pollCommandResponseManager;
protected GrpcClientManager grpcClientManager;
public ClusterGrpcService() {
this.init();
}
protected void init() {
this.channelManager = new ChannelManager();
this.grpcClientManager = new GrpcClientManager();
this.pollCommandResponseManager = new TelemetryCommandManager();
this.connectorManager = new ConnectorManager(new GrpcTransactionStateChecker());
this.consumerService = new ConsumerService(connectorManager, grpcClientManager);
this.producerService = new ProducerService(connectorManager);
this.routeService = new RouteService(connectorManager, grpcClientManager);
this.clientService = new ForwardClientService(connectorManager, scheduledExecutorService,
channelManager, grpcClientManager, pollCommandResponseManager);
this.transactionService = new TransactionService(connectorManager, channelManager);
this.appendStartAndShutdown(new ClusterGrpcServiceStartAndShutdown());
this.appendStartAndShutdown(this.connectorManager);
this.appendStartAndShutdown(this.consumerService);
this.appendStartAndShutdown(this.producerService);
this.appendStartAndShutdown(this.routeService);
this.appendStartAndShutdown(this.clientService);
this.appendStartAndShutdown(this.transactionService);
}
@Override
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
return routeService.queryRoute(ctx, request);
}
@Override
public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
return clientService.heartbeat(ctx, request);
}
@Override
public CompletableFuture<SendMessageResponse> sendMessage(Context ctx, SendMessageRequest request) {
return producerService.sendMessage(ctx, request);
}
@Override
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
return routeService.queryAssignment(ctx, request);
}
@Override
public void receiveMessage(Context ctx, ReceiveMessageRequest request,
StreamObserver<ReceiveMessageResponse> responseObserver) {
consumerService.receiveMessage(ctx, request, responseObserver);
}
@Override
public CompletableFuture<AckMessageResponse> ackMessage(Context ctx, AckMessageRequest request) {
return consumerService.ackMessage(ctx, request);
}
@Override
public CompletableFuture<ForwardMessageToDeadLetterQueueResponse> forwardMessageToDeadLetterQueue(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
return producerService.forwardMessageToDeadLetterQueue(ctx, request);
}
@Override
public CompletableFuture<EndTransactionResponse> endTransaction(Context ctx, EndTransactionRequest request) {
return transactionService.endTransaction(ctx, request);
}
@Override
public CompletableFuture<NotifyClientTerminationResponse> notifyClientTermination(Context ctx,
NotifyClientTerminationRequest request) {
return clientService.notifyClientTermination(ctx, request);
}
@Override
public CompletableFuture<ChangeInvisibleDurationResponse> changeInvisibleDuration(Context ctx,
ChangeInvisibleDurationRequest request) {
return consumerService.changeInvisibleDuration(ctx, request);
}
@Override
public StreamObserver<TelemetryCommand> telemetry(Context ctx, StreamObserver<TelemetryCommand> responseObserver) {
return clientService.telemetry(ctx, responseObserver);
}
protected class ClusterGrpcServiceStartAndShutdown implements StartAndShutdown {
@Override
public void start() throws Exception {
}
@Override
public void shutdown() throws Exception {
scheduledExecutorService.shutdown();
}
}
protected class GrpcTransactionStateChecker implements TransactionStateChecker {
@Override
public void checkTransactionState(TransactionStateCheckRequest checkData) {
transactionService.checkTransactionState(checkData);
}
}
}
@@ -1,565 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.AckMessageEntry;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
import apache.rocketmq.v2.AckMessageResultEntry;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.EndTransactionResponse;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.NotifyClientTerminationResponse;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.TelemetryCommand;
import apache.rocketmq.v2.ThreadStackTrace;
import apache.rocketmq.v2.VerifyMessageResult;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import io.netty.channel.Channel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.broker.client.ProducerGroupEvent;
import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageBatch;
import org.apache.rocketmq.common.message.MessageClientIDSetter;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeResponseHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.UnregisterClientRequestHeader;
import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.channel.InvocationContext;
import org.apache.rocketmq.proxy.channel.SimpleChannel;
import org.apache.rocketmq.proxy.channel.SimpleChannelHandlerContext;
import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
import org.apache.rocketmq.proxy.common.TelemetryCommandManager;
import org.apache.rocketmq.proxy.common.TelemetryCommandRecord;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.GrpcClientChannel;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.ReceiveMessageChannel;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.SendMessageChannel;
import org.apache.rocketmq.proxy.grpc.v2.adapter.handler.ReceiveMessageResponseHandler;
import org.apache.rocketmq.proxy.grpc.v2.adapter.handler.SendMessageResponseHandler;
import org.apache.rocketmq.proxy.grpc.v2.service.local.LocalReceiveMessageResponseStreamWriter;
import org.apache.rocketmq.proxy.grpc.v2.service.local.LocalReceiveMessageResultFilter;
import org.apache.rocketmq.proxy.grpc.v2.service.local.LocalWriteQueueSelector;
import org.apache.rocketmq.proxy.grpc.v2.service.local.RouteService;
import org.apache.rocketmq.remoting.RemotingServer;
import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class LocalGrpcService extends AbstractStartAndShutdown implements GrpcForwardService {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final BrokerController brokerController;
private final ChannelManager channelManager;
private final TelemetryCommandManager telemetryCommandManager;
private final GrpcClientManager grpcClientManager;
private final RouteService routeService;
private final ClientSettingsService clientSettingsService;
private final LocalWriteQueueSelector localWriteQueueSelector;
private final BaseReceiveMessageResponseStreamWriter.Builder streamWriterBuilder;
private volatile ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook;
public LocalGrpcService(BrokerController brokerController) {
this(brokerController, new TelemetryCommandManager());
}
/**
* For unit test
* @param brokerController BrokerController works in local mode
* @param telemetryCommandManager Used to manage telemetry command
*/
LocalGrpcService(BrokerController brokerController, TelemetryCommandManager telemetryCommandManager) {
this.brokerController = brokerController;
this.channelManager = new ChannelManager();
// TransactionStateChecker is not used in Local mode.
ConnectorManager connectorManager = new ConnectorManager(null);
this.telemetryCommandManager = telemetryCommandManager;
this.grpcClientManager = new GrpcClientManager();
this.routeService = new RouteService(connectorManager, grpcClientManager);
this.clientSettingsService = new ClientSettingsService(this.channelManager, this.grpcClientManager, this.telemetryCommandManager);
this.localWriteQueueSelector = new LocalWriteQueueSelector(brokerController.getBrokerConfig().getBrokerName(),
brokerController.getTopicConfigManager(), connectorManager.getTopicRouteCache());
this.brokerController.getConsumerManager().appendConsumerIdsChangeListener(new ConsumerIdsChangeListenerImpl());
this.brokerController.getProducerManager().appendProducerChangeListener(new ProducerChangeListenerImpl());
this.streamWriterBuilder = (observer, hook) -> new LocalReceiveMessageResponseStreamWriter(
observer,
hook,
channelManager,
brokerController,
new LocalReceiveMessageResultFilter(channelManager, brokerController, grpcClientManager)
);
this.appendStartAndShutdown(connectorManager);
}
@Override
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
return this.routeService.queryRoute(ctx, request);
}
@Override
public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
LanguageCode languageCode;
String language = InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE);
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
languageCode = LanguageCode.valueOf(language);
Settings clientSettings = grpcClientManager.getClientSettings(clientId);
HeartbeatData heartbeatData = GrpcConverter.buildHeartbeatData(clientId, request, clientSettings);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, null);
command.setLanguage(languageCode);
command.setVersion(MQVersion.Version.V5_0_0.ordinal());
command.setBody(heartbeatData.encode());
command.makeCustomHeaderToNet();
CompletableFuture<HeartbeatResponse> future = new CompletableFuture<>();
switch (clientSettings.getClientType()) {
case PRODUCER: {
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcClientChannel channel = GrpcClientChannel.create(channelManager, topicName, clientId, telemetryCommandManager);
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
this.brokerController.getClientManageProcessor()
.heartBeat(simpleChannelHandlerContext, command);
}
HeartbeatResponse heartbeatResponse = HeartbeatResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, "Producer heartbeat"))
.build();
future.complete(heartbeatResponse);
break;
}
case PUSH_CONSUMER:
case SIMPLE_CONSUMER: {
String groupName = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
GrpcClientChannel channel = GrpcClientChannel.create(channelManager, groupName, clientId, telemetryCommandManager);
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
RemotingCommand response = this.brokerController.getClientManageProcessor()
.heartBeat(simpleChannelHandlerContext, command);
HeartbeatResponse heartbeatResponse = HeartbeatResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(response.getCode(), response.getRemark()))
.build();
future.complete(heartbeatResponse);
break;
}
default: {
throw new IllegalArgumentException("ClientType not exist " + clientSettings.getClientType());
}
}
return future;
}
@Override
public CompletableFuture<SendMessageResponse> sendMessage(Context ctx, SendMessageRequest request) {
MessageQueue messageQueue = localWriteQueueSelector.selectQueue(ctx, request).getMessageQueue();
String topicName = messageQueue.getTopic();
SendMessageRequestHeader requestHeader = GrpcConverter.buildSendMessageRequestHeader(request, topicName, messageQueue.getQueueId());
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
List<org.apache.rocketmq.common.message.Message> messageList = GrpcConverter.buildMessage(request.getMessagesList(), request.getMessages(0).getTopic());
String messageId;
if (messageList.size() == 1) {
org.apache.rocketmq.common.message.Message message = messageList.get(0);
command.setBody(message.getBody());
messageId = MessageClientIDSetter.getUniqID(message);
} else {
MessageBatch messageBatch = MessageBatch.generateFromList(messageList);
MessageClientIDSetter.setUniqID(messageBatch);
messageBatch.setBody(messageBatch.encode());
command.setBody(messageBatch.encode());
messageId = MessageClientIDSetter.getUniqID(messageBatch);
}
command.makeCustomHeaderToNet();
SendMessageResponseHandler handler = new SendMessageResponseHandler(messageId, requestHeader.getSysFlag(), brokerController.getBrokerAddr());
SendMessageChannel channel = channelManager.createChannel(ctx, context -> new SendMessageChannel(context, handler), SendMessageChannel.class);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
CompletableFuture<SendMessageResponse> future = new CompletableFuture<>();
InvocationContext<SendMessageRequest, SendMessageResponse> context
= new InvocationContext<>(request, future);
channel.registerInvocationContext(command.getOpaque(), context);
try {
RemotingCommand response = brokerController.getSendMessageProcessor()
.processRequest(channelHandlerContext, command);
if (response != null) {
handler.handle(response, context);
channel.eraseInvocationContext(command.getOpaque());
}
} catch (final Exception e) {
log.error("Failed to process send message command", e);
channel.eraseInvocationContext(command.getOpaque());
future.completeExceptionally(e);
}
return future;
}
@Override
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
return this.routeService.queryAssignment(ctx, request);
}
@Override
public void receiveMessage(Context ctx, ReceiveMessageRequest request, StreamObserver<ReceiveMessageResponse> responseObserver) {
long pollTime = GrpcConverter.buildPollTimeFromContext(ctx);
// TODO: get fifo config from subscriptionGroupManager
boolean fifo = false;
BaseReceiveMessageResponseStreamWriter writer = streamWriterBuilder.build(responseObserver, receiveMessageHook);
ReceiveMessageResponseHandler handler = new ReceiveMessageResponseHandler(brokerController.getBrokerConfig().getBrokerName(), fifo);
ReceiveMessageChannel channel = channelManager.createChannel(ctx, context -> new ReceiveMessageChannel(context, handler), ReceiveMessageChannel.class);
CompletableFuture<List<MessageExt>> future = new CompletableFuture<>();
InvocationContext<ReceiveMessageRequest, List<MessageExt>> context
= new InvocationContext<>(request, future);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
PopMessageRequestHeader requestHeader = GrpcConverter.buildPopMessageRequestHeader(request, pollTime, fifo);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.POP_MESSAGE, requestHeader);
command.makeCustomHeaderToNet();
channel.registerInvocationContext(command.getOpaque(), context);
try {
RemotingCommand response = brokerController.getPopMessageProcessor().processRequest(channelHandlerContext, command);
if (response != null) {
handler.handle(response, context);
channel.eraseInvocationContext(command.getOpaque());
}
} catch (Exception e) {
log.error("Failed to process pop message command", e);
channel.eraseInvocationContext(command.getOpaque());
future.completeExceptionally(e);
}
future.thenAccept(r -> writer.write(ctx, request, PopStatus.FOUND, r))
.exceptionally(e -> {
writer.write(ctx, request, e);
return null;
});
}
@Override
public CompletableFuture<AckMessageResponse> ackMessage(Context ctx, AckMessageRequest request) {
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
CompletableFuture<AckMessageResponse> future = new CompletableFuture<>();
List<AckMessageResultEntry> ackMessageResultEntryList = new ArrayList<>();
for (AckMessageEntry entry : request.getEntriesList()) {
ReceiptHandle receiptHandle = ReceiptHandle.decode(entry.getReceiptHandle());
if (receiptHandle.isExpired()) {
ackMessageResultEntryList.add(AckMessageResultEntry.newBuilder()
.setReceiptHandle(entry.getReceiptHandle())
.setMessageId(entry.getMessageId())
.setStatus(ResponseBuilder.buildStatus(Code.RECEIPT_HANDLE_EXPIRED, "expired"))
.build());
continue;
}
AckMessageRequestHeader requestHeader = GrpcConverter.buildAckMessageRequestHeader(request, receiptHandle);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
command.makeCustomHeaderToNet();
try {
RemotingCommand responseCommand = brokerController.getAckMessageProcessor()
.processRequest(channelHandlerContext, command);
ackMessageResultEntryList.add(AckMessageResultEntry.newBuilder()
.setReceiptHandle(entry.getReceiptHandle())
.setMessageId(entry.getMessageId())
.setStatus(ResponseBuilder.buildStatus(responseCommand.getCode(), responseCommand.getRemark()))
.build());
} catch (Exception e) {
log.error("Exception raised when ack message", e);
ackMessageResultEntryList.add(AckMessageResultEntry.newBuilder()
.setReceiptHandle(entry.getReceiptHandle())
.setMessageId(entry.getMessageId())
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, e.getMessage()))
.build());
}
}
AckMessageResponse response = AckMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(ResponseCode.SUCCESS, "ok"))
.addAllEntries(ackMessageResultEntryList)
.build();
future.complete(response);
return future;
}
@Override
public CompletableFuture<ForwardMessageToDeadLetterQueueResponse> forwardMessageToDeadLetterQueue(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
SimpleChannel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
ConsumerSendMsgBackRequestHeader requestHeader = GrpcConverter.buildConsumerSendMsgBackRequestHeader(request);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, requestHeader);
command.makeCustomHeaderToNet();
CompletableFuture<ForwardMessageToDeadLetterQueueResponse> future = new CompletableFuture<>();
try {
RemotingCommand response = brokerController.getSendMessageProcessor()
.processRequest(channelHandlerContext, command);
future.complete(ForwardMessageToDeadLetterQueueResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(response.getCode(), response.getRemark()))
.build());
} catch (Exception e) {
log.error("Exception raised when forwardMessageToDeadLetterQueue", e);
future.completeExceptionally(e);
}
return future;
}
@Override
public CompletableFuture<EndTransactionResponse> endTransaction(Context ctx, EndTransactionRequest request) {
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
EndTransactionRequestHeader requestHeader = GrpcConverter.buildEndTransactionRequestHeader(request, topicName);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader);
command.makeCustomHeaderToNet();
CompletableFuture<EndTransactionResponse> future = new CompletableFuture<>();
try {
RemotingCommand responseCommand = brokerController.getEndTransactionProcessor()
.processRequest(channelHandlerContext, command);
EndTransactionResponse.Builder builder = EndTransactionResponse.newBuilder();
if (null != responseCommand) {
builder.setStatus(ResponseBuilder.buildStatus(responseCommand.getCode(), responseCommand.getRemark()));
} else {
builder.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "Response command is null"));
}
EndTransactionResponse response = builder.build();
future.complete(response);
} catch (Exception e) {
log.error("Exception raised while endTransaction", e);
future.completeExceptionally(e);
}
return future;
}
public void reportThreadStackTrace(Context ctx, ThreadStackTrace request) {
String nonce = request.getNonce();
String threadStack = request.getThreadStackTrace();
TelemetryCommandRecord pollCommandResponseFuture = telemetryCommandManager.getCommand(nonce);
if (pollCommandResponseFuture != null) {
Integer opaque = pollCommandResponseFuture.getOpaque();
if (opaque != null) {
RemotingServer remotingServer = this.brokerController.getRemotingServer();
if (remotingServer instanceof NettyRemotingAbstract) {
NettyRemotingAbstract nettyRemotingAbstract = (NettyRemotingAbstract) remotingServer;
RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "From gRPC client");
remotingCommand.setOpaque(pollCommandResponseFuture.getOpaque());
ConsumerRunningInfo runningInfo = new ConsumerRunningInfo();
runningInfo.setJstack(threadStack);
remotingCommand.setBody(runningInfo.encode());
nettyRemotingAbstract.processResponseCommand(new SimpleChannelHandlerContext(channelManager.createChannel(ctx)), remotingCommand);
}
}
}
}
public void reportVerifyMessageResult(Context ctx, VerifyMessageResult request) {
String nonce = request.getNonce();
TelemetryCommandRecord pollCommandResponseFuture = telemetryCommandManager.getCommand(nonce);
if (pollCommandResponseFuture != null) {
Integer opaque = pollCommandResponseFuture.getOpaque();
if (opaque != null) {
RemotingServer remotingServer = this.brokerController.getRemotingServer();
if (remotingServer instanceof NettyRemotingAbstract) {
NettyRemotingAbstract nettyRemotingAbstract = (NettyRemotingAbstract) remotingServer;
RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "From gRPC client");
remotingCommand.setOpaque(pollCommandResponseFuture.getOpaque());
ConsumeMessageDirectlyResult result = GrpcConverter.buildConsumeMessageDirectlyResult(request);
remotingCommand.setBody(result.encode());
nettyRemotingAbstract.processResponseCommand(new SimpleChannelHandlerContext(channelManager.createChannel(ctx)), remotingCommand);
}
}
}
}
@Override
public CompletableFuture<NotifyClientTerminationResponse> notifyClientTermination(Context ctx,
NotifyClientTerminationRequest request) {
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
Settings clientSettings = grpcClientManager.getClientSettings(clientId);
UnregisterClientRequestHeader header = GrpcConverter.buildUnregisterClientRequestHeader(clientId, clientSettings.getClientType(), request);
RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RequestCode.UNREGISTER_CLIENT, header);
remotingCommand.makeCustomHeaderToNet();
try {
this.brokerController.getClientManageProcessor().unregisterClient(simpleChannelHandlerContext, remotingCommand);
} catch (Exception ignored) {
}
return new CompletableFuture<>();
}
@Override
public CompletableFuture<ChangeInvisibleDurationResponse> changeInvisibleDuration(Context ctx,
ChangeInvisibleDurationRequest request) {
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
ChangeInvisibleTimeRequestHeader requestHeader = GrpcConverter.buildChangeInvisibleTimeRequestHeader(request);
ReceiptHandle receiptHandle = ReceiptHandle.decode(request.getReceiptHandle());
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader);
command.makeCustomHeaderToNet();
CompletableFuture<ChangeInvisibleDurationResponse> future = new CompletableFuture<>();
try {
RemotingCommand responseCommand = brokerController.getChangeInvisibleTimeProcessor()
.processRequest(channelHandlerContext, command);
ChangeInvisibleTimeResponseHeader responseHeader = (ChangeInvisibleTimeResponseHeader) responseCommand.readCustomHeader();
ChangeInvisibleDurationResponse.Builder builder = ChangeInvisibleDurationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(responseCommand.getCode(), responseCommand.getRemark()));
if (responseCommand.getCode() == ResponseCode.SUCCESS) {
builder.setReceiptHandle(ReceiptHandle.builder()
.startOffset(requestHeader.getOffset())
.retrieveTime(responseHeader.getPopTime())
.invisibleTime(responseHeader.getInvisibleTime())
.reviveQueueId(responseHeader.getReviveQid())
.topicType(receiptHandle.getTopicType())
.brokerName(brokerController.getBrokerConfig().getBrokerName())
.queueId(requestHeader.getQueueId())
.offset(requestHeader.getOffset())
.build()
.encode());
}
future.complete(builder.build());
} catch (Exception e) {
log.error("Exception raised while changeInvisibleDuration", e);
future.completeExceptionally(e);
}
return future;
}
@Override
public StreamObserver<TelemetryCommand> telemetry(Context ctx, StreamObserver<TelemetryCommand> responseObserver) {
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
return new StreamObserver<TelemetryCommand>() {
@Override
public void onNext(TelemetryCommand request) {
switch (request.getCommandCase()) {
case SETTINGS: {
responseObserver.onNext(clientSettingsService.processClientSettings(ctx, request, responseObserver));
break;
}
case THREAD_STACK_TRACE: {
reportThreadStackTrace(ctx, request.getThreadStackTrace());
break;
}
case VERIFY_MESSAGE_RESULT: {
reportVerifyMessageResult(ctx, request.getVerifyMessageResult());
break;
}
default: {
throw new IllegalArgumentException("Request type is illegal");
}
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener {
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {
if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
if (args == null || args.length < 1) {
return;
}
if (args[0] instanceof ClientChannelInfo) {
ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
channelManager.onClientOffline(clientChannelInfo.getClientId());
grpcClientManager.removeClientSettings(clientChannelInfo.getClientId());
}
}
}
@Override
public void shutdown() {
}
}
protected class ProducerChangeListenerImpl implements ProducerChangeListener {
@Override
public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) {
if (event == ProducerGroupEvent.CLIENT_UNREGISTER) {
channelManager.onClientOffline(clientChannelInfo.getClientId());
grpcClientManager.removeClientSettings(clientChannelInfo.getClientId());
}
}
}
}
@@ -1,67 +0,0 @@
/*
* 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.proxy.grpc.v2.service;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseWriter;
public class ReceiveMessageResponseStreamObserver implements StreamObserver<ReceiveMessageResponse> {
private final Context context;
private final ReceiveMessageRequest request;
private final ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook;
private final StreamObserver<ReceiveMessageResponse> observer;
public ReceiveMessageResponseStreamObserver(Context context, ReceiveMessageRequest request,
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook,
StreamObserver<ReceiveMessageResponse> observer) {
this.context = context;
this.request = request;
this.receiveMessageHook = receiveMessageHook;
this.observer = observer;
}
@Override
public void onNext(ReceiveMessageResponse response) {
if (receiveMessageHook != null) {
receiveMessageHook.beforeResponse(context, request, response, null);
}
observer.onNext(response);
}
@Override
public void onError(Throwable throwable) {
if (receiveMessageHook != null) {
receiveMessageHook.beforeResponse(context, request, null, throwable);
}
observer.onError(throwable);
}
@Override
public void onCompleted() {
observer.onCompleted();
}
public boolean isCancelled() {
return ResponseWriter.isCancelled(observer);
}
}
@@ -1,283 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.AckMessageEntry;
import apache.rocketmq.v2.AckMessageRequest;
import apache.rocketmq.v2.AckMessageResponse;
import apache.rocketmq.v2.AckMessageResultEntry;
import apache.rocketmq.v2.ChangeInvisibleDurationRequest;
import apache.rocketmq.v2.ChangeInvisibleDurationResponse;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.AckStatus;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.ForwardProducer;
import org.apache.rocketmq.proxy.connector.ForwardReadConsumer;
import org.apache.rocketmq.proxy.connector.ForwardWriteConsumer;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseService;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseReceiveMessageResponseStreamWriter;
public class ConsumerService extends BaseService {
protected final ForwardReadConsumer readConsumer;
protected final ForwardWriteConsumer writeConsumer;
/**
* For sending messages back to broker.
*/
protected final ForwardProducer producer;
protected final GrpcClientManager grpcClientManager;
private volatile ReadQueueSelector readQueueSelector;
private volatile BaseReceiveMessageResponseStreamWriter.Builder receiveMessageWriterBuilder;
private volatile ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook;
private volatile ResponseHook<AckMessageRequest, AckMessageResponse> ackMessageHook;
private volatile ResponseHook<ChangeInvisibleDurationRequest, ChangeInvisibleDurationResponse> changeInvisibleDurationHook;
public ConsumerService(ConnectorManager connectorManager, GrpcClientManager grpcClientManager) {
super(connectorManager);
this.readConsumer = connectorManager.getForwardReadConsumer();
this.writeConsumer = connectorManager.getForwardWriteConsumer();
this.producer = connectorManager.getForwardProducer();
this.grpcClientManager = grpcClientManager;
this.readQueueSelector = new DefaultReadQueueSelector(connectorManager.getTopicRouteCache());
this.receiveMessageWriterBuilder = (observer, hook) -> new DefaultReceiveMessageResponseStreamWriter(
observer,
hook,
writeConsumer,
connectorManager.getTopicRouteCache(),
new DefaultReceiveMessageResultFilter(
producer, writeConsumer, grpcClientManager, connectorManager.getTopicRouteCache())
);
}
public void receiveMessage(Context ctx, ReceiveMessageRequest request,
StreamObserver<ReceiveMessageResponse> responseObserver) {
BaseReceiveMessageResponseStreamWriter writer = receiveMessageWriterBuilder.build(responseObserver, receiveMessageHook);
try {
PopMessageRequestHeader requestHeader = this.buildPopMessageRequestHeader(ctx, request);
SelectableMessageQueue messageQueue = this.readQueueSelector.select(ctx, request, requestHeader);
if (messageQueue == null) {
throw new ProxyException(Code.FORBIDDEN, "no readable topic route for topic " + requestHeader.getTopic());
}
this.readConsumer.popMessage(
ctx,
messageQueue.getBrokerAddr(),
messageQueue.getBrokerName(),
requestHeader,
requestHeader.getPollTime())
.thenAccept(result -> writer.write(ctx, request, result.getPopStatus(), result.getMsgFoundList()))
.exceptionally(e -> {
writer.write(ctx, request, e);
return null;
});
} catch (Throwable t) {
writer.write(ctx, request, t);
}
}
protected PopMessageRequestHeader buildPopMessageRequestHeader(Context ctx, ReceiveMessageRequest request) {
checkSubscriptionData(request.getMessageQueue().getTopic(), request.getFilterExpression());
boolean fifo = grpcClientManager.getClientSettings(ctx).getSubscription().getFifo();
return GrpcConverter.buildPopMessageRequestHeader(request, GrpcConverter.buildPollTimeFromContext(ctx), fifo);
}
public CompletableFuture<AckMessageResponse> ackMessage(Context ctx, AckMessageRequest request) {
CompletableFuture<AckMessageResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (ackMessageHook != null) {
ackMessageHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
CompletableFuture<AckMessageResultEntry>[] futures = new CompletableFuture[request.getEntriesCount()];
for (int i = 0; i < request.getEntriesCount(); i++) {
futures[i] = processAckMessage(ctx, request, request.getEntries(i));
}
CompletableFuture.allOf(futures).whenComplete((val, throwable) -> {
if (throwable != null) {
future.completeExceptionally(throwable);
return;
}
List<AckMessageResultEntry> entryList = new ArrayList<>();
for (CompletableFuture<AckMessageResultEntry> entryFuture : futures) {
entryFuture.thenAccept(entryList::add);
}
AckMessageResponse.Builder responseBuilder = AckMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllEntries(entryList);
future.complete(responseBuilder.build());
});
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected CompletableFuture<AckMessageResultEntry> processAckMessage(Context ctx, AckMessageRequest request,
AckMessageEntry ackMessageEntry) {
CompletableFuture<AckMessageResultEntry> future = new CompletableFuture<>();
AckMessageResultEntry.Builder failResult = AckMessageResultEntry.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "ack message failed"))
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle());
try {
ReceiptHandle receiptHandle = resolveReceiptHandle(ctx, ackMessageEntry.getReceiptHandle());
String brokerAddr = this.getBrokerAddr(ctx, receiptHandle.getBrokerName());
AckMessageRequestHeader requestHeader = this.buildAckMessageRequestHeader(ctx, request, receiptHandle);
CompletableFuture<AckResult> ackResultFuture = this.writeConsumer.ackMessage(ctx, brokerAddr, ackMessageEntry.getMessageId(), requestHeader);
ackResultFuture
.thenAccept(result -> future.complete(convertToAckMessageResultEntry(ctx, ackMessageEntry, result)))
.exceptionally(throwable -> {
future.complete(failResult.setStatus(ResponseBuilder.buildStatus(throwable)).build());
return null;
});
} catch (Throwable t) {
future.complete(failResult.setStatus(ResponseBuilder.buildStatus(t)).build());
}
return future;
}
protected AckMessageRequestHeader buildAckMessageRequestHeader(Context ctx, AckMessageRequest request,
ReceiptHandle handle) {
return GrpcConverter.buildAckMessageRequestHeader(request, handle);
}
protected AckMessageResultEntry convertToAckMessageResultEntry(Context ctx, AckMessageEntry ackMessageEntry,
AckResult ackResult) {
if (AckStatus.OK.equals(ackResult.getStatus())) {
return AckMessageResultEntry.newBuilder()
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle())
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build();
}
return AckMessageResultEntry.newBuilder()
.setMessageId(ackMessageEntry.getMessageId())
.setReceiptHandle(ackMessageEntry.getReceiptHandle())
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "ack failed: status is abnormal"))
.build();
}
public CompletableFuture<ChangeInvisibleDurationResponse> changeInvisibleDuration(Context ctx,
ChangeInvisibleDurationRequest request) {
CompletableFuture<ChangeInvisibleDurationResponse> future = new CompletableFuture<>();
try {
ReceiptHandle receiptHandle = resolveReceiptHandle(ctx, request.getReceiptHandle());
String brokerAddr = this.getBrokerAddr(ctx, receiptHandle.getBrokerName());
ChangeInvisibleTimeRequestHeader requestHeader = convertToChangeInvisibleTimeRequestHeader(ctx, request);
future = this.writeConsumer.changeInvisibleTimeAsync(ctx, brokerAddr, receiptHandle.getBrokerName(),
request.getMessageId(), requestHeader)
.thenApply(result -> convertToChangeInvisibleDurationResponse(ctx, request, result));
} catch (Throwable t) {
future.completeExceptionally(t);
}
future.whenComplete((response, throwable) -> {
if (changeInvisibleDurationHook != null) {
changeInvisibleDurationHook.beforeResponse(ctx, request, response, throwable);
}
});
return future;
}
protected ChangeInvisibleTimeRequestHeader convertToChangeInvisibleTimeRequestHeader(Context ctx,
ChangeInvisibleDurationRequest request) {
return GrpcConverter.buildChangeInvisibleTimeRequestHeader(request);
}
protected ChangeInvisibleDurationResponse convertToChangeInvisibleDurationResponse(Context ctx,
ChangeInvisibleDurationRequest request, AckResult ackResult) {
if (AckStatus.OK.equals(ackResult.getStatus())) {
return ChangeInvisibleDurationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.setReceiptHandle(ackResult.getExtraInfo())
.build();
}
return ChangeInvisibleDurationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "changeInvisibleDuration failed: status is abnormal"))
.build();
}
public ReadQueueSelector getReadQueueSelector() {
return readQueueSelector;
}
public void setReadQueueSelector(ReadQueueSelector readQueueSelector) {
this.readQueueSelector = readQueueSelector;
}
public BaseReceiveMessageResponseStreamWriter.Builder getReceiveMessageWriterBuilder() {
return receiveMessageWriterBuilder;
}
public void setReceiveMessageWriterBuilder(
BaseReceiveMessageResponseStreamWriter.Builder receiveMessageWriterBuilder) {
this.receiveMessageWriterBuilder = receiveMessageWriterBuilder;
}
public ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> getReceiveMessageHook() {
return receiveMessageHook;
}
public void setReceiveMessageHook(
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> receiveMessageHook) {
this.receiveMessageHook = receiveMessageHook;
}
public ResponseHook<AckMessageRequest, AckMessageResponse> getAckMessageHook() {
return ackMessageHook;
}
public void setAckMessageHook(
ResponseHook<AckMessageRequest, AckMessageResponse> ackMessageHook) {
this.ackMessageHook = ackMessageHook;
}
public ResponseHook<ChangeInvisibleDurationRequest, ChangeInvisibleDurationResponse> getChangeInvisibleDurationHook() {
return changeInvisibleDurationHook;
}
public void setChangeInvisibleDurationHook(
ResponseHook<ChangeInvisibleDurationRequest, ChangeInvisibleDurationResponse> changeInvisibleDurationHook) {
this.changeInvisibleDurationHook = changeInvisibleDurationHook;
}
}
@@ -1,41 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.QueryAssignmentRequest;
import io.grpc.Context;
import java.util.List;
import org.apache.rocketmq.proxy.connector.route.MessageQueueWrapper;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
public class DefaultAssignmentQueueSelector implements AssignmentQueueSelector {
private final TopicRouteCache topicRouteCache;
public DefaultAssignmentQueueSelector(TopicRouteCache topicRouteCache) {
this.topicRouteCache = topicRouteCache;
}
@Override
public List<SelectableMessageQueue> getAssignment(Context ctx, QueryAssignmentRequest request) throws Exception {
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
MessageQueueWrapper messageQueueWrapper = topicRouteCache.getMessageQueue(topicName);
return messageQueueWrapper.getReadSelector().getBrokerActingQueues();
}
}
@@ -1,55 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.ReceiveMessageRequest;
import io.grpc.Context;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
public class DefaultReadQueueSelector implements ReadQueueSelector {
private final TopicRouteCache topicRouteCache;
public DefaultReadQueueSelector(TopicRouteCache topicRouteCache) {
this.topicRouteCache = topicRouteCache;
}
@Override
public SelectableMessageQueue select(Context ctx, ReceiveMessageRequest request, PopMessageRequestHeader requestHeader) {
SelectableMessageQueue messageQueue = null;
try {
String topic = requestHeader.getTopic();
if (request.hasMessageQueue() && request.getMessageQueue().hasBroker()) {
String brokerName = request.getMessageQueue().getBroker().getName();
if (StringUtils.isNotBlank(brokerName)) {
messageQueue = topicRouteCache.selectReadBrokerByName(topic, brokerName);
}
}
if (messageQueue == null) {
messageQueue = topicRouteCache.selectOneReadBroker(topic, null);
}
return messageQueue;
} catch (Throwable t) {
return null;
}
}
}
@@ -1,96 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.time.Duration;
import org.apache.rocketmq.client.consumer.AckStatus;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.connector.ForwardWriteConsumer;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseService;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseReceiveMessageResponseStreamWriter;
import org.apache.rocketmq.proxy.grpc.v2.service.ReceiveMessageResultFilter;
public class DefaultReceiveMessageResponseStreamWriter extends BaseReceiveMessageResponseStreamWriter {
protected static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected static final long NACK_INVISIBLE_TIME = Duration.ofSeconds(1).toMillis();
protected final ForwardWriteConsumer writeConsumer;
protected final TopicRouteCache topicRouteCache;
public DefaultReceiveMessageResponseStreamWriter(
StreamObserver<ReceiveMessageResponse> observer,
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> hook,
ForwardWriteConsumer writeConsumer,
TopicRouteCache topicRouteCache,
ReceiveMessageResultFilter receiveMessageResultFilter) {
super(observer, hook, receiveMessageResultFilter);
this.writeConsumer = writeConsumer;
this.topicRouteCache = topicRouteCache;
}
@Override
protected void processThrowableWhenWriteMessage(Throwable throwable, Context context, ReceiveMessageRequest request,
Message message) {
this.nackFailToWriteMessage(context, request, message);
}
protected void nackFailToWriteMessage(Context ctx, ReceiveMessageRequest request, Message message) {
try {
String receiptHandleStr = message.getSystemProperties().getReceiptHandle();
ReceiptHandle handle = BaseService.resolveReceiptHandle(ctx, receiptHandleStr);
String brokerAddr = BaseService.getBrokerAddr(ctx, this.topicRouteCache, handle.getBrokerName());
String groupName = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getMessageQueue().getTopic());
ChangeInvisibleTimeRequestHeader changeInvisibleTimeRequestHeader = new ChangeInvisibleTimeRequestHeader();
changeInvisibleTimeRequestHeader.setConsumerGroup(groupName);
changeInvisibleTimeRequestHeader.setTopic(handle.getRealTopic(topicName, groupName));
changeInvisibleTimeRequestHeader.setQueueId(handle.getQueueId());
changeInvisibleTimeRequestHeader.setExtraInfo(handle.getReceiptHandle());
changeInvisibleTimeRequestHeader.setOffset(handle.getOffset());
changeInvisibleTimeRequestHeader.setInvisibleTime(NACK_INVISIBLE_TIME);
this.writeConsumer.changeInvisibleTimeAsync(
ctx,
brokerAddr,
handle.getBrokerName(),
message.getSystemProperties().getMessageId(),
changeInvisibleTimeRequestHeader
).whenComplete((ackResult, t) -> {
if (t != null) {
log.warn("err when nack message. request:{}, message:{}", request, message, t);
} else if (!AckStatus.OK.equals(ackResult.getStatus())) {
log.warn("nack failed. request:{}, message:{}, ackResult:{}", request, message, ackResult);
}
});
} catch (Throwable t) {
log.warn("err when nack message. request:{}, message:{}", request, message, t);
}
}
}
@@ -1,127 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.ReceiveMessageRequest;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.proxy.connector.ForwardProducer;
import org.apache.rocketmq.proxy.connector.ForwardWriteConsumer;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseReceiveMessageResultFilter;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import static org.apache.rocketmq.proxy.grpc.v2.service.BaseService.getBrokerAddr;
public class DefaultReceiveMessageResultFilter extends BaseReceiveMessageResultFilter {
protected final ForwardProducer producer;
protected final ForwardWriteConsumer writeConsumer;
protected final TopicRouteCache topicRouteCache;
private volatile ResponseHook<AckMessageRequestHeader, AckResult> ackNoMatchedMessageHook;
private volatile ResponseHook<ConsumerSendMsgBackRequestHeader, RemotingCommand> forwardToDLQInRecvMessageHook;
public DefaultReceiveMessageResultFilter(ForwardProducer producer, ForwardWriteConsumer writeConsumer,
GrpcClientManager grpcClientManager, TopicRouteCache topicRouteCache) {
super(grpcClientManager);
this.producer = producer;
this.writeConsumer = writeConsumer;
this.topicRouteCache = topicRouteCache;
}
@Override
protected void processNoMatchMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt) {
CompletableFuture<AckResult> future = new CompletableFuture<>();
ReceiptHandle handle = ReceiptHandle.create(messageExt);
if (handle == null) {
return;
}
AckMessageRequestHeader ackMessageRequestHeader = GrpcConverter.buildAckMessageRequestHeader(request, handle);
try {
String brokerAddr = getBrokerAddr(ctx, topicRouteCache, handle.getBrokerName());
future = this.writeConsumer.ackMessage(ctx, brokerAddr, messageExt.getMsgId(), ackMessageRequestHeader);
} catch (Throwable t) {
future.completeExceptionally(t);
}
future.whenComplete((ackResult, throwable) -> {
if (ackNoMatchedMessageHook != null) {
ackNoMatchedMessageHook.beforeResponse(ctx, ackMessageRequestHeader, ackResult, throwable);
}
});
}
@Override
protected void processExceedMaxAttemptsMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt,
int maxAttempts) {
CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader();
try {
ReceiptHandle handle = ReceiptHandle.create(messageExt);
if (handle == null) {
return;
}
String brokerAddr = getBrokerAddr(ctx, topicRouteCache, handle.getBrokerName());
ConsumerSendMsgBackRequestHeader sendMsgBackRequestHeader = GrpcConverter.buildConsumerSendMsgBackRequestHeader(
request,
handle,
messageExt.getMsgId(),
maxAttempts);
AckMessageRequestHeader ackMessageRequestHeader = GrpcConverter.buildAckMessageRequestHeader(request, handle);
future = this.producer.sendMessageBackThenAckOrg(ctx, brokerAddr, sendMsgBackRequestHeader, ackMessageRequestHeader);
} catch (Throwable t) {
future.completeExceptionally(t);
}
future.whenComplete((result, throwable) -> {
if (forwardToDLQInRecvMessageHook != null) {
forwardToDLQInRecvMessageHook.beforeResponse(ctx, consumerSendMsgBackRequestHeader, result, throwable);
}
});
}
public ResponseHook<AckMessageRequestHeader, AckResult> getAckNoMatchedMessageHook() {
return ackNoMatchedMessageHook;
}
public void setAckNoMatchedMessageHook(
ResponseHook<AckMessageRequestHeader, AckResult> ackNoMatchedMessageHook) {
this.ackNoMatchedMessageHook = ackNoMatchedMessageHook;
}
public ResponseHook<ConsumerSendMsgBackRequestHeader, RemotingCommand> getForwardToDLQInRecvMessageHook() {
return forwardToDLQInRecvMessageHook;
}
public void setForwardToDLQInRecvMessageHook(
ResponseHook<ConsumerSendMsgBackRequestHeader, RemotingCommand> forwardToDLQInRecvMessageHook) {
this.forwardToDLQInRecvMessageHook = forwardToDLQInRecvMessageHook;
}
}
@@ -1,77 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.SendMessageRequest;
import io.grpc.Context;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
public class DefaultWriteQueueSelector implements WriteQueueSelector {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final TopicRouteCache topicRouteCache;
public DefaultWriteQueueSelector(TopicRouteCache topicRouteCache) {
this.topicRouteCache = topicRouteCache;
}
@Override
public SelectableMessageQueue selectQueue(
Context ctx,
SendMessageRequest request
) {
try {
if (request.getMessagesCount() <= 0) {
throw new ProxyException(Code.MESSAGE_CORRUPTED, "no message to send");
}
Message message = request.getMessages(0);
String topic = GrpcConverter.wrapResourceWithNamespace(message.getTopic());
String shardingKey = null;
if (request.getMessagesCount() == 1) {
shardingKey = message.getSystemProperties().getMessageGroup();
}
SelectableMessageQueue targetMessageQueue;
if (StringUtils.isNotEmpty(shardingKey)) {
// With shardingKey
targetMessageQueue = selectOrderQueue(topic, shardingKey);
} else {
targetMessageQueue = selectNormalQueue(topic);
}
return targetMessageQueue;
} catch (Exception e) {
log.error("error when select queue in DefaultMessageQueueSelector. request: {}", request, e);
return null;
}
}
protected SelectableMessageQueue selectNormalQueue(String topic) throws Exception {
return this.topicRouteCache.selectOneWriteQueue(topic, null);
}
protected SelectableMessageQueue selectOrderQueue(String topic, String shardingKey) throws Exception {
return this.topicRouteCache.selectOneWriteQueueByKey(topic, shardingKey);
}
}
@@ -1,266 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.HeartbeatRequest;
import apache.rocketmq.v2.HeartbeatResponse;
import apache.rocketmq.v2.NotifyClientTerminationRequest;
import apache.rocketmq.v2.NotifyClientTerminationResponse;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.Settings;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ConsumerManager;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.broker.client.ProducerGroupEvent;
import org.apache.rocketmq.broker.client.ProducerManager;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.common.TelemetryCommandManager;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.grpc.interceptor.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.GrpcClientChannel;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseService;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
import org.apache.rocketmq.proxy.grpc.v2.service.ClientSettingsService;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
public class ForwardClientService extends BaseService {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final ChannelManager channelManager;
protected final GrpcClientManager grpcClientManager;
protected final TelemetryCommandManager telemetryCommandManager;
protected ConsumerManager consumerManager;
protected ProducerManager producerManager;
protected ClientSettingsService clientSettingsService;
public ForwardClientService(
ConnectorManager connectorManager,
ScheduledExecutorService scheduledExecutorService,
ChannelManager channelManager,
GrpcClientManager grpcClientManager,
TelemetryCommandManager telemetryCommandManager
) {
super(connectorManager);
scheduledExecutorService.scheduleWithFixedDelay(
this::scanNotActiveChannel,
Duration.ofSeconds(10).toMillis(),
Duration.ofSeconds(10).toMillis(),
TimeUnit.MILLISECONDS);
this.channelManager = channelManager;
this.grpcClientManager = grpcClientManager;
this.telemetryCommandManager = telemetryCommandManager;
this.clientSettingsService = new ClientSettingsService(this.channelManager, this.grpcClientManager, this.telemetryCommandManager);
this.consumerManager = new ConsumerManager(new ConsumerIdsChangeListenerImpl());
this.producerManager = new ProducerManager();
this.producerManager.appendProducerChangeListener(new ProducerChangeListenerImpl());
}
protected class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener {
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {
if (event == ConsumerGroupEvent.CLIENT_UNREGISTER) {
if (args == null || args.length < 1) {
return;
}
if (args[0] instanceof ClientChannelInfo) {
ClientChannelInfo clientChannelInfo = (ClientChannelInfo) args[0];
channelManager.onClientOffline(clientChannelInfo.getClientId());
grpcClientManager.removeClientSettings(clientChannelInfo.getClientId());
}
}
}
@Override
public void shutdown() {
}
}
protected class ProducerChangeListenerImpl implements ProducerChangeListener {
@Override
public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) {
switch (event) {
case GROUP_UNREGISTER:
connectorManager.getTransactionHeartbeatRegisterService().onProducerGroupOffline(group);
break;
case CLIENT_UNREGISTER:
channelManager.onClientOffline(clientChannelInfo.getClientId());
grpcClientManager.removeClientSettings(clientChannelInfo.getClientId());
break;
default:
break;
}
}
}
public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
CompletableFuture<HeartbeatResponse> future = new CompletableFuture<>();
try {
String language = InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE);
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
LanguageCode languageCode = LanguageCode.valueOf(language);
Settings clientSettings = grpcClientManager.getClientSettings(clientId);
switch (clientSettings.getClientType()) {
case PRODUCER: {
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
GrpcClientChannel channel = GrpcClientChannel.create(channelManager, topicName, clientId, telemetryCommandManager);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
// use topic name as producer group
producerManager.registerProducer(topicName, clientChannelInfo);
connectorManager.getTransactionHeartbeatRegisterService().addProducerGroup(topicName, topicName);
}
break;
}
case PUSH_CONSUMER:
case SIMPLE_CONSUMER: {
if (!request.hasGroup()) {
throw new ProxyException(Code.ILLEGAL_CONSUMER_GROUP, "group cannot be empty for consumer");
}
String consumerGroup = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
GrpcClientChannel channel = GrpcClientChannel.create(ctx, channelManager, consumerGroup, clientId, telemetryCommandManager);
ClientChannelInfo clientChannelInfo = new ClientChannelInfo(channel, clientId, languageCode, MQVersion.Version.V5_0_0.ordinal());
consumerManager.registerConsumer(
consumerGroup,
clientChannelInfo,
GrpcConverter.buildConsumeType(clientSettings.getClientType()),
MessageModel.CLUSTERING,
ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET,
GrpcConverter.buildSubscriptionDataSet(clientSettings.getSubscription().getSubscriptionsList()),
false
);
break;
}
default: {
throw new IllegalArgumentException("ClientType not exist " + clientSettings.getClientType());
}
}
future.complete(HeartbeatResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
return future;
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public CompletableFuture<NotifyClientTerminationResponse> notifyClientTermination(Context ctx, NotifyClientTerminationRequest request) {
CompletableFuture<NotifyClientTerminationResponse> future = new CompletableFuture<>();
try {
String clientId = InterceptorConstants.METADATA.get(ctx).get(InterceptorConstants.CLIENT_ID);
Settings clientSettings = grpcClientManager.getClientSettings(clientId);
switch (clientSettings.getClientType()) {
case PRODUCER:
for (Resource topic : clientSettings.getPublishing().getTopicsList()) {
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
// user topic name as producer group
GrpcClientChannel channel = GrpcClientChannel.removeChannel(channelManager, topicName, clientId);
if (channel != null) {
producerManager.doChannelCloseEvent(topicName, channel);
}
}
break;
case PUSH_CONSUMER:
case SIMPLE_CONSUMER:
if (!request.hasGroup()) {
throw new ProxyException(Code.ILLEGAL_CONSUMER_GROUP, "group cannot be empty for consumer");
}
String consumerGroup = GrpcConverter.wrapResourceWithNamespace(request.getGroup());
GrpcClientChannel channel = GrpcClientChannel.removeChannel(channelManager, consumerGroup, clientId);
if (channel != null) {
consumerManager.doChannelCloseEvent(consumerGroup, channel);
}
break;
default:
break;
}
future.complete(NotifyClientTerminationResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public StreamObserver<TelemetryCommand> telemetry(Context ctx, StreamObserver<TelemetryCommand> responseObserver) {
return new StreamObserver<TelemetryCommand>() {
@Override
public void onNext(TelemetryCommand request) {
if (request.getCommandCase() == TelemetryCommand.CommandCase.SETTINGS) {
responseObserver.onNext(clientSettingsService.processClientSettings(ctx, request, responseObserver));
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
private void scanNotActiveChannel() {
try {
this.consumerManager.scanNotActiveChannel();
this.producerManager.scanNotActiveChannel();
} catch (Exception e) {
log.error("error occurred when scan not active client channels.", e);
}
}
public ConsumerManager getConsumerManager() {
return consumerManager;
}
public ProducerManager getProducerManager() {
return producerManager;
}
}
@@ -1,184 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueRequest;
import apache.rocketmq.v2.ForwardMessageToDeadLetterQueueResponse;
import apache.rocketmq.v2.Resource;
import apache.rocketmq.v2.SendMessageRequest;
import apache.rocketmq.v2.SendMessageResponse;
import apache.rocketmq.v2.SendReceipt;
import com.beust.jcommander.internal.Lists;
import io.grpc.Context;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.ForwardProducer;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseService;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ProducerService extends BaseService {
protected final ForwardProducer producer;
private volatile WriteQueueSelector writeQueueSelector;
private volatile ResponseHook<SendMessageRequest, SendMessageResponse> sendMessageHook;
private volatile ResponseHook<ForwardMessageToDeadLetterQueueRequest, ForwardMessageToDeadLetterQueueResponse> forwardMessageToDLQHook;
public ProducerService(ConnectorManager connectorManager) {
super(connectorManager);
this.producer = connectorManager.getForwardProducer();
this.writeQueueSelector = new DefaultWriteQueueSelector(this.connectorManager.getTopicRouteCache());
}
public CompletableFuture<SendMessageResponse> sendMessage(Context ctx, SendMessageRequest request) {
CompletableFuture<SendMessageResponse> future = new CompletableFuture<>();
try {
SelectableMessageQueue selectableMessageQueue = writeQueueSelector.selectQueue(ctx, request);
if (selectableMessageQueue == null) {
throw new ProxyException(Code.FORBIDDEN, "no writeable topic route");
}
// send message to broker.
future = this.producer.sendMessage(
ctx,
selectableMessageQueue.getBrokerAddr(),
selectableMessageQueue.getBrokerName(),
convertToMessageList(ctx, request),
convertToSendMessageRequestHeader(ctx, request, selectableMessageQueue)
).thenApply(result -> convertToSendMessageResponse(ctx, request, result));
} catch (Throwable t) {
future.completeExceptionally(t);
}
future.whenComplete((response, throwable) -> {
if (sendMessageHook != null) {
sendMessageHook.beforeResponse(ctx, request, response, throwable);
}
});
return future;
}
protected SendMessageRequestHeader convertToSendMessageRequestHeader(Context ctx, SendMessageRequest request, SelectableMessageQueue selectableMessageQueue) {
// use topic name as group
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getMessages(0).getTopic());
return GrpcConverter.buildSendMessageRequestHeader(request, topicName,
selectableMessageQueue.getQueueId());
}
protected List<org.apache.rocketmq.common.message.Message> convertToMessageList(Context ctx, SendMessageRequest request) {
// use topic name as group
Resource topic = request.getMessages(0).getTopic();
String topicName = GrpcConverter.wrapResourceWithNamespace(topic);
return GrpcConverter.buildMessage(request.getMessagesList(), topic);
}
protected SendMessageResponse convertToSendMessageResponse(Context ctx, SendMessageRequest request,
SendResult result) {
if (result.getSendStatus() != SendStatus.SEND_OK) {
return SendMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.INTERNAL_SERVER_ERROR, "send message failed, sendStatus=" + result.getSendStatus()))
.build();
}
List<SendReceipt> sendReceiptList = Lists.newArrayList();
sendReceiptList.add(SendReceipt.newBuilder()
.setMessageId(StringUtils.defaultString(result.getMsgId()))
.setTransactionId(StringUtils.defaultString(result.getTransactionId()))
.build());
return SendMessageResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllReceipts(sendReceiptList)
.build();
}
public CompletableFuture<ForwardMessageToDeadLetterQueueResponse> forwardMessageToDeadLetterQueue(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
CompletableFuture<ForwardMessageToDeadLetterQueueResponse> future = new CompletableFuture<>();
try {
ReceiptHandle receiptHandle = resolveReceiptHandle(ctx, request.getReceiptHandle());
String brokerAddr = this.getBrokerAddr(ctx, receiptHandle.getBrokerName());
ConsumerSendMsgBackRequestHeader sendMsgBackRequestHeader = this.buildConsumerSendMsgBackRequestHeader(ctx, request);
AckMessageRequestHeader ackMessageRequestHeader = GrpcConverter.buildAckMessageRequestHeader(
request.getTopic(), request.getGroup(), receiptHandle);
future = this.producer.sendMessageBackThenAckOrg(ctx, brokerAddr, sendMsgBackRequestHeader, ackMessageRequestHeader)
.thenApply(result -> convertToForwardMessageToDeadLetterQueueResponse(ctx, result));
} catch (Throwable t) {
future.completeExceptionally(t);
}
future.whenComplete((response, throwable) -> {
if (forwardMessageToDLQHook != null) {
forwardMessageToDLQHook.beforeResponse(ctx, request, response, throwable);
}
});
return future;
}
protected ConsumerSendMsgBackRequestHeader buildConsumerSendMsgBackRequestHeader(Context ctx,
ForwardMessageToDeadLetterQueueRequest request) {
return GrpcConverter.buildConsumerSendMsgBackRequestHeader(request);
}
protected ForwardMessageToDeadLetterQueueResponse convertToForwardMessageToDeadLetterQueueResponse(Context ctx,
RemotingCommand result) {
return ForwardMessageToDeadLetterQueueResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(result.getCode(), result.getRemark()))
.build();
}
public WriteQueueSelector getWriteQueueSelector() {
return writeQueueSelector;
}
public void setWriteQueueSelector(WriteQueueSelector writeQueueSelector) {
this.writeQueueSelector = writeQueueSelector;
}
public ResponseHook<SendMessageRequest, SendMessageResponse> getSendMessageHook() {
return sendMessageHook;
}
public void setSendMessageHook(
ResponseHook<SendMessageRequest, SendMessageResponse> sendMessageHook) {
this.sendMessageHook = sendMessageHook;
}
public ResponseHook<ForwardMessageToDeadLetterQueueRequest, ForwardMessageToDeadLetterQueueResponse> getForwardMessageToDLQHook() {
return forwardMessageToDLQHook;
}
public void setForwardMessageToDLQHook(
ResponseHook<ForwardMessageToDeadLetterQueueRequest, ForwardMessageToDeadLetterQueueResponse> forwardMessageToDLQHook) {
this.forwardMessageToDLQHook = forwardMessageToDLQHook;
}
}
@@ -1,150 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Assignment;
import apache.rocketmq.v2.Broker;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Endpoints;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.Permission;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.route.MessageQueueWrapper;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteHelper;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.service.AbstractRouteService;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
public class RouteService extends AbstractRouteService {
public RouteService(ConnectorManager connectorManager, GrpcClientManager grpcClientManager) {
super(connectorManager, grpcClientManager);
}
@Override
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
CompletableFuture<QueryRouteResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (queryRouteHook != null) {
queryRouteHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
MessageQueueWrapper messageQueueWrapper = this.connectorManager.getTopicRouteCache().getMessageQueue(topicName);
TopicRouteData topicRouteData = messageQueueWrapper.getTopicRouteData();
List<QueueData> queueDataList = topicRouteData.getQueueDatas();
List<MessageQueue> messageQueueList = new ArrayList<>();
Endpoints endpoints = request.getEndpoints();
Endpoints resEndpoints = this.queryRouteEndpointConverter.convert(ctx, endpoints);
if (resEndpoints == null || resEndpoints.getDefaultInstanceForType().equals(resEndpoints)) {
future.complete(QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.ILLEGAL_ACCESS_POINT, "endpoint " +
endpoints + " is invalidate"))
.build());
return future;
}
for (QueueData queueData : queueDataList) {
Broker broker = Broker.newBuilder()
.setName(queueData.getBrokerName())
.setId(0)
.setEndpoints(resEndpoints)
.build();
messageQueueList.addAll(GrpcConverter.genMessageQueueFromQueueData(queueData, request.getTopic(), broker));
}
QueryRouteResponse response = QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllMessageQueues(messageQueueList)
.build();
future.complete(response);
} catch (Throwable t) {
if (TopicRouteHelper.isTopicNotExistError(t)) {
future.complete(QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.TOPIC_NOT_FOUND, t.getMessage()))
.build());
} else {
future.completeExceptionally(t);
}
}
return future;
}
@Override
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
CompletableFuture<QueryAssignmentResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (queryAssignmentHook != null) {
queryAssignmentHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
List<Assignment> assignments = new ArrayList<>();
List<SelectableMessageQueue> messageQueueList = this.assignmentQueueSelector.getAssignment(ctx, request);
Endpoints endpoints = request.getEndpoints();
Endpoints resEndpoints = this.queryAssignmentEndpointConverter.convert(ctx, endpoints);
if (resEndpoints == null || Endpoints.getDefaultInstance().equals(resEndpoints)) {
future.complete(QueryAssignmentResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.ILLEGAL_ACCESS_POINT, "endpoint " +
endpoints + " is invalidate"))
.build());
return future;
}
for (SelectableMessageQueue messageQueue : messageQueueList) {
Broker broker = Broker.newBuilder()
.setName(messageQueue.getBrokerName())
.setId(0)
.setEndpoints(resEndpoints)
.build();
MessageQueue defaultMessageQueue = MessageQueue.newBuilder()
.setTopic(request.getTopic())
.setId(-1)
.setPermission(Permission.READ_WRITE)
.setBroker(broker)
.build();
assignments.add(Assignment.newBuilder()
.setMessageQueue(defaultMessageQueue)
.build());
}
QueryAssignmentResponse response = QueryAssignmentResponse.newBuilder()
.addAllAssignments(assignments)
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build();
future.complete(response);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
}
@@ -1,139 +0,0 @@
/*
* 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.proxy.grpc.v2.service.cluster;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.EndTransactionResponse;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.RecoverOrphanedTransactionCommand;
import apache.rocketmq.v2.TelemetryCommand;
import io.grpc.Context;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import org.apache.commons.collections.CollectionUtils;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.ForwardProducer;
import org.apache.rocketmq.proxy.connector.transaction.TransactionId;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateCheckRequest;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.adapter.channel.GrpcClientChannel;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseService;
import org.apache.rocketmq.remoting.common.RemotingHelper;
public class TransactionService extends BaseService implements TransactionStateChecker {
protected final ChannelManager channelManager;
protected final ForwardProducer forwardProducer;
private volatile ResponseHook<TransactionStateCheckRequest, TelemetryCommand> checkTransactionStateHook;
private volatile ResponseHook<EndTransactionRequest, EndTransactionResponse> endTransactionHook;
public TransactionService(ConnectorManager connectorManager, ChannelManager channelManager) {
super(connectorManager);
this.forwardProducer = connectorManager.getForwardProducer();
this.channelManager = channelManager;
}
@Override
public void checkTransactionState(TransactionStateCheckRequest checkData) {
Context ctx = Context.current();
try {
List<String> clientIdList = this.channelManager.getClientIdList(checkData.getGroupId());
if (CollectionUtils.isEmpty(clientIdList)) {
return;
}
String clientId = clientIdList.get(ThreadLocalRandom.current().nextInt(clientIdList.size()));
GrpcClientChannel channel = GrpcClientChannel.getChannel(this.channelManager, checkData.getGroupId(), clientId);
String transactionId = checkData.getTransactionId().getProxyTransactionId();
MessageExt messageExt = checkData.getMessageExt();
Message message = GrpcConverter.buildMessage(messageExt);
TelemetryCommand response = TelemetryCommand.newBuilder()
.setRecoverOrphanedTransactionCommand(
RecoverOrphanedTransactionCommand.newBuilder()
.setOrphanedTransactionalMessage(message)
.setTransactionId(transactionId)
.setMessageQueue(GrpcConverter.buildMessageQueue(messageExt, checkData.getBrokerName()))
.build()
).build();
channel.writeAndFlush(response);
if (this.checkTransactionStateHook != null) {
this.checkTransactionStateHook.beforeResponse(ctx, checkData, response, null);
}
} catch (Throwable t) {
if (this.checkTransactionStateHook != null) {
this.checkTransactionStateHook.beforeResponse(ctx, checkData, null, t);
}
}
}
public CompletableFuture<EndTransactionResponse> endTransaction(Context ctx, EndTransactionRequest request) {
CompletableFuture<EndTransactionResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (endTransactionHook != null) {
endTransactionHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
TransactionId handle = TransactionId.decode(request.getTransactionId());
String brokerAddr = RemotingHelper.parseSocketAddressAddr(handle.getBrokerAddr());
EndTransactionRequestHeader requestHeader = this.toEndTransactionRequestHeader(ctx, request);
this.forwardProducer.endTransaction(ctx, brokerAddr, requestHeader);
future.complete(EndTransactionResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected EndTransactionRequestHeader toEndTransactionRequestHeader(Context ctx, EndTransactionRequest request) {
String topic = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
// use topic name as producerGroup
return GrpcConverter.buildEndTransactionRequestHeader(request, topic);
}
public ResponseHook<TransactionStateCheckRequest, TelemetryCommand> getCheckTransactionStateHook() {
return checkTransactionStateHook;
}
public void setCheckTransactionStateHook(
ResponseHook<TransactionStateCheckRequest, TelemetryCommand> checkTransactionStateHook) {
this.checkTransactionStateHook = checkTransactionStateHook;
}
public ResponseHook<EndTransactionRequest, EndTransactionResponse> getEndTransactionHook() {
return endTransactionHook;
}
public void setEndTransactionHook(
ResponseHook<EndTransactionRequest, EndTransactionResponse> endTransactionHook) {
this.endTransactionHook = endTransactionHook;
}
}
@@ -1,76 +0,0 @@
/*
* 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.proxy.grpc.v2.service.local;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.ReceiveMessageRequest;
import apache.rocketmq.v2.ReceiveMessageResponse;
import io.grpc.Context;
import io.grpc.stub.StreamObserver;
import io.netty.channel.Channel;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.channel.SimpleChannelHandlerContext;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseHook;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseReceiveMessageResponseStreamWriter;
import org.apache.rocketmq.proxy.grpc.v2.service.ReceiveMessageResultFilter;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LocalReceiveMessageResponseStreamWriter extends BaseReceiveMessageResponseStreamWriter {
private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final ChannelManager channelManager;
private final BrokerController brokerController;
public LocalReceiveMessageResponseStreamWriter(
StreamObserver<ReceiveMessageResponse> observer,
ResponseHook<ReceiveMessageRequest, ReceiveMessageResponse> hook,
ChannelManager channelManager,
BrokerController brokerController,
ReceiveMessageResultFilter receiveMessageResultFilter) {
super(observer, hook, receiveMessageResultFilter);
this.channelManager = channelManager;
this.brokerController = brokerController;
}
@Override
protected void processThrowableWhenWriteMessage(Throwable throwable, Context context, ReceiveMessageRequest request,
Message message) {
this.changeInvisibleTime(context, request, ReceiptHandle.decode(message.getSystemProperties().getReceiptHandle()));
}
private void changeInvisibleTime(Context ctx, ReceiveMessageRequest request, ReceiptHandle handle) {
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
ChangeInvisibleTimeRequestHeader requestHeader = GrpcConverter.buildChangeInvisibleTimeRequestHeader(request, handle);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.CHANGE_MESSAGE_INVISIBLETIME, requestHeader);
command.makeCustomHeaderToNet();
try {
brokerController.getChangeInvisibleTimeProcessor().processRequest(simpleChannelHandlerContext, command);
} catch (RemotingCommandException e) {
log.error("ChangeInvisibleTime error when write response", e);
}
}
}
@@ -1,94 +0,0 @@
/*
* 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.proxy.grpc.v2.service.local;
import apache.rocketmq.v2.ReceiveMessageRequest;
import io.grpc.Context;
import io.netty.channel.Channel;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.channel.SimpleChannelHandlerContext;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.service.BaseReceiveMessageResultFilter;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LocalReceiveMessageResultFilter extends BaseReceiveMessageResultFilter {
private final static Logger log = LoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final ChannelManager channelManager;
private final BrokerController brokerController;
public LocalReceiveMessageResultFilter(ChannelManager channelManager, BrokerController brokerController, GrpcClientManager grpcClientManager) {
super(grpcClientManager);
this.channelManager = channelManager;
this.brokerController = brokerController;
}
@Override
protected void processNoMatchMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt) {
ReceiptHandle handle = ReceiptHandle.create(messageExt);
if (handle == null) {
return;
}
Channel channel = channelManager.createChannel(ctx);
AckMessageRequestHeader requestHeader = GrpcConverter.buildAckMessageRequestHeader(request, handle);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, requestHeader);
command.makeCustomHeaderToNet();
try {
brokerController.getAckMessageProcessor().processRequest(new SimpleChannelHandlerContext(channel), command);
} catch (RemotingCommandException e) {
log.error("AckMessage failed in filterMessage", e);
}
}
@Override
protected void processExceedMaxAttemptsMessage(Context ctx, ReceiveMessageRequest request, MessageExt messageExt,
int maxAttempts) {
try {
ReceiptHandle handle = ReceiptHandle.create(messageExt);
if (handle == null) {
return;
}
Channel channel = channelManager.createChannel(ctx);
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = GrpcConverter.buildConsumerSendMsgBackRequestHeader(request, handle, messageExt.getMsgId(), maxAttempts);
RemotingCommand command = RemotingCommand.createRequestCommand(RequestCode.CONSUMER_SEND_MSG_BACK, consumerSendMsgBackRequestHeader);
command.makeCustomHeaderToNet();
RemotingCommand response = brokerController.getSendMessageProcessor().processRequest(simpleChannelHandlerContext, command);
if (response.getCode() == ResponseCode.SUCCESS) {
AckMessageRequestHeader ackMessageRequestHeader = GrpcConverter.buildAckMessageRequestHeader(request, handle);
command = RemotingCommand.createRequestCommand(RequestCode.ACK_MESSAGE, ackMessageRequestHeader);
command.makeCustomHeaderToNet();
brokerController.getAckMessageProcessor().processRequest(simpleChannelHandlerContext, command);
}
} catch (Exception e) {
log.error("ForwardMessageToDLQ failed in filterMessage", e);
}
}
}
@@ -1,101 +0,0 @@
/*
* 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.proxy.grpc.v2.service.local;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Message;
import apache.rocketmq.v2.SendMessageRequest;
import com.google.common.hash.Hashing;
import io.grpc.Context;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.broker.topic.TopicConfigManager;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteCache;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ProxyException;
import org.apache.rocketmq.proxy.grpc.v2.service.cluster.DefaultWriteQueueSelector;
public class LocalWriteQueueSelector extends DefaultWriteQueueSelector {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final String brokerName;
private final AtomicInteger queueIndex;
private final TopicConfigManager topicConfigManager;
public LocalWriteQueueSelector(String brokerName, TopicConfigManager topicConfigManager, TopicRouteCache topicRouteCache) {
super(topicRouteCache);
this.brokerName = brokerName;
this.queueIndex = new AtomicInteger(Math.abs(new Random().nextInt()));
this.topicConfigManager = topicConfigManager;
}
@Override
public SelectableMessageQueue selectQueue(
Context ctx,
SendMessageRequest request
) {
try {
if (request.getMessagesCount() <= 0) {
throw new ProxyException(Code.MESSAGE_CORRUPTED, "no message to send");
}
Message message = request.getMessages(0);
String topic = GrpcConverter.wrapResourceWithNamespace(message.getTopic());
String shardingKey = null;
if (request.getMessagesCount() == 1) {
shardingKey = message.getSystemProperties().getMessageGroup();
}
SelectableMessageQueue targetMessageQueue;
if (StringUtils.isNotEmpty(shardingKey)) {
// With shardingKey
targetMessageQueue = selectOrderQueue(topic, shardingKey);
} else {
targetMessageQueue = selectNormalQueue(topic);
}
return targetMessageQueue;
} catch (Exception e) {
log.error("error when select queue in DefaultMessageQueueSelector. request: {}", request, e);
return null;
}
}
protected SelectableMessageQueue selectNormalQueue(String topic) throws Exception {
TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic);
if (topicConfig == null) {
throw new ProxyException(Code.TOPIC_NOT_FOUND, topic);
}
int selectQueueId = queueIndex.getAndIncrement() % topicConfig.getWriteQueueNums();
MessageQueue mq = new MessageQueue(topic, brokerName, selectQueueId);
return new SelectableMessageQueue(mq, null);
}
protected SelectableMessageQueue selectOrderQueue(String topic, String shardingKey) throws Exception {
TopicConfig topicConfig = topicConfigManager.selectTopicConfig(topic);
if (topicConfig == null) {
throw new ProxyException(Code.TOPIC_NOT_FOUND, topic);
}
int selectQueueId = Hashing.consistentHash(shardingKey.hashCode(), topicConfig.getWriteQueueNums());
MessageQueue mq = new MessageQueue(topic, brokerName, selectQueueId);
return new SelectableMessageQueue(mq, null);
}
}
@@ -1,175 +0,0 @@
/*
* 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.proxy.grpc.v2.service.local;
import apache.rocketmq.v2.Address;
import apache.rocketmq.v2.AddressScheme;
import apache.rocketmq.v2.Assignment;
import apache.rocketmq.v2.Broker;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.Endpoints;
import apache.rocketmq.v2.MessageQueue;
import apache.rocketmq.v2.Permission;
import apache.rocketmq.v2.QueryAssignmentRequest;
import apache.rocketmq.v2.QueryAssignmentResponse;
import apache.rocketmq.v2.QueryRouteRequest;
import apache.rocketmq.v2.QueryRouteResponse;
import com.google.common.net.HostAndPort;
import io.grpc.Context;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.connector.ConnectorManager;
import org.apache.rocketmq.proxy.connector.route.MessageQueueWrapper;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.connector.route.TopicRouteHelper;
import org.apache.rocketmq.proxy.grpc.v2.adapter.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.adapter.ResponseBuilder;
import org.apache.rocketmq.proxy.grpc.v2.service.AbstractRouteService;
import org.apache.rocketmq.proxy.grpc.v2.service.GrpcClientManager;
public class RouteService extends AbstractRouteService {
public RouteService(ConnectorManager connectorManager, GrpcClientManager grpcClientManager) {
super(connectorManager, grpcClientManager);
}
@Override
public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
CompletableFuture<QueryRouteResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (queryRouteHook != null) {
queryRouteHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
MessageQueueWrapper messageQueueWrapper = this.connectorManager.getTopicRouteCache().getMessageQueue(topicName);
TopicRouteData topicRouteData = messageQueueWrapper.getTopicRouteData();
List<QueueData> queueDataList = topicRouteData.getQueueDatas();
List<BrokerData> brokerDataList = topicRouteData.getBrokerDatas();
List<MessageQueue> messageQueueList = new ArrayList<>();
Map<String, Map<Long, Broker>> brokerMap = buildBrokerMap(brokerDataList);
for (QueueData queueData : queueDataList) {
String brokerName = queueData.getBrokerName();
Map<Long, Broker> brokerIdMap = brokerMap.get(brokerName);
if (brokerIdMap == null) {
break;
}
for (Broker broker : brokerIdMap.values()) {
messageQueueList.addAll(GrpcConverter.genMessageQueueFromQueueData(queueData, request.getTopic(), broker));
}
}
QueryRouteResponse response = QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.addAllMessageQueues(messageQueueList)
.build();
future.complete(response);
} catch (Throwable t) {
if (TopicRouteHelper.isTopicNotExistError(t)) {
future.complete(QueryRouteResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.TOPIC_NOT_FOUND, t.getMessage()))
.build());
} else {
future.completeExceptionally(t);
}
}
return future;
}
@Override
public CompletableFuture<QueryAssignmentResponse> queryAssignment(Context ctx, QueryAssignmentRequest request) {
CompletableFuture<QueryAssignmentResponse> future = new CompletableFuture<>();
future.whenComplete((response, throwable) -> {
if (queryAssignmentHook != null) {
queryAssignmentHook.beforeResponse(ctx, request, response, throwable);
}
});
try {
List<Assignment> assignments = new ArrayList<>();
List<SelectableMessageQueue> messageQueueList = this.assignmentQueueSelector.getAssignment(ctx, request);
String topicName = GrpcConverter.wrapResourceWithNamespace(request.getTopic());
MessageQueueWrapper messageQueueWrapper = this.connectorManager.getTopicRouteCache().getMessageQueue(topicName);
TopicRouteData topicRouteData = messageQueueWrapper.getTopicRouteData();
Map<String, Map<Long, Broker>> brokerMap = buildBrokerMap(topicRouteData.getBrokerDatas());
for (SelectableMessageQueue messageQueue : messageQueueList) {
Map<Long, Broker> brokerIdMap = brokerMap.get(messageQueue.getBrokerName());
if (brokerIdMap != null) {
Broker broker = brokerIdMap.get(0L);
MessageQueue defaultMessageQueue = MessageQueue.newBuilder()
.setTopic(request.getTopic())
.setId(-1)
.setPermission(Permission.READ_WRITE)
.setBroker(broker)
.build();
assignments.add(Assignment.newBuilder()
.setMessageQueue(defaultMessageQueue)
.build());
}
}
QueryAssignmentResponse response = QueryAssignmentResponse.newBuilder()
.addAllAssignments(assignments)
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build();
future.complete(response);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
private Map<String/*brokerName*/, Map<Long/*brokerID*/, Broker>> buildBrokerMap(List<BrokerData> brokerDataList) {
Map<String, Map<Long, Broker>> brokerMap = new HashMap<>();
for (BrokerData brokerData : brokerDataList) {
Map<Long, Broker> brokerIdMap = new HashMap<>();
String brokerName = brokerData.getBrokerName();
for (Map.Entry<Long, String> entry : brokerData.getBrokerAddrs().entrySet()) {
Long brokerId = entry.getKey();
HostAndPort hostAndPort = HostAndPort.fromString(entry.getValue());
Broker broker = Broker.newBuilder()
.setName(brokerName)
.setId(Math.toIntExact(brokerId))
.setEndpoints(Endpoints.newBuilder()
.setScheme(AddressScheme.IPv4)
.addAddresses(
Address.newBuilder()
.setPort(ConfigurationManager.getProxyConfig().getGrpcServerPort())
.setHost(hostAndPort.getHost())
)
.build())
.build();
brokerIdMap.put(brokerId, broker);
}
brokerMap.put(brokerName, brokerIdMap);
}
return brokerMap;
}
}
@@ -0,0 +1,74 @@
/*
* 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.proxy.grpc.v2.transaction;
import apache.rocketmq.v2.Code;
import apache.rocketmq.v2.EndTransactionRequest;
import apache.rocketmq.v2.EndTransactionResponse;
import apache.rocketmq.v2.TransactionResolution;
import apache.rocketmq.v2.TransactionSource;
import io.grpc.Context;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.grpc.v2.AbstractMessingActivity;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcClientSettingsManager;
import org.apache.rocketmq.proxy.grpc.v2.common.GrpcConverter;
import org.apache.rocketmq.proxy.grpc.v2.common.ResponseBuilder;
import org.apache.rocketmq.proxy.processor.MessagingProcessor;
import org.apache.rocketmq.proxy.processor.TransactionStatus;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
public class EndTransactionActivity extends AbstractMessingActivity {
public EndTransactionActivity(MessagingProcessor messagingProcessor,
GrpcClientSettingsManager grpcClientSettingsManager) {
super(messagingProcessor, grpcClientSettingsManager);
}
public CompletableFuture<EndTransactionResponse> endTransaction(Context ctx, EndTransactionRequest request) {
CompletableFuture<EndTransactionResponse> future = new CompletableFuture<>();
try {
ProxyContext context = createContext(ctx);
TransactionId transactionId = TransactionId.decode(request.getTransactionId());
TransactionStatus transactionStatus = TransactionStatus.UNKNOWN;
TransactionResolution transactionResolution = request.getResolution();
switch (transactionResolution) {
case COMMIT:
transactionStatus = TransactionStatus.COMMIT;
break;
case ROLLBACK:
transactionStatus = TransactionStatus.ROLLBACK;
break;
default:
break;
}
this.messagingProcessor.endTransaction(
context,
transactionId,
request.getMessageId(),
GrpcConverter.wrapResourceWithNamespace(request.getTopic()),
transactionStatus,
request.getSource().equals(TransactionSource.SOURCE_SERVER_CHECK));
future.complete(EndTransactionResponse.newBuilder()
.setStatus(ResponseBuilder.buildStatus(Code.OK, Code.OK.name()))
.build());
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
}
@@ -0,0 +1,40 @@
/*
* 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.proxy.processor;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
import org.apache.rocketmq.proxy.service.ServiceManager;
public abstract class AbstractProcessor {
protected MessagingProcessor messagingProcessor;
protected ServiceManager serviceManager;
public AbstractProcessor(MessagingProcessor messagingProcessor,
ServiceManager serviceManager) {
this.messagingProcessor = messagingProcessor;
this.serviceManager = serviceManager;
}
protected void checkReceiptHandle(ReceiptHandle handle) {
if (handle.isExpired()) {
throw new ProxyException(ProxyExceptionCode.RECEIPT_HANDLE_EXPIRED, "receipt handle is expired");
}
}
}
@@ -0,0 +1,104 @@
/*
* 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.proxy.processor;
import io.netty.channel.Channel;
import java.util.Set;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.ServiceManager;
public class ClientProcessor extends AbstractProcessor {
public ClientProcessor(MessagingProcessor messagingProcessor,
ServiceManager serviceManager) {
super(messagingProcessor, serviceManager);
}
public void registerProducer(
ProxyContext ctx,
String producerGroup,
ClientChannelInfo clientChannelInfo
) {
this.serviceManager.getProducerManager().registerProducer(producerGroup, clientChannelInfo);
}
public void unRegisterProducer(
ProxyContext ctx,
String producerGroup,
ClientChannelInfo clientChannelInfo
) {
this.serviceManager.getProducerManager().unregisterProducer(producerGroup, clientChannelInfo);
}
public Channel findProducerChannel(
ProxyContext ctx,
String producerGroup,
String clientId
) {
return this.serviceManager.getProducerManager().findChannel(clientId);
}
public void registerProducerChangeListener(ProducerChangeListener listener) {
this.serviceManager.getProducerManager().appendProducerChangeListener(listener);
}
public void registerConsumer(
ProxyContext ctx,
String consumerGroup,
ClientChannelInfo clientChannelInfo,
ConsumeType consumeType,
MessageModel messageModel,
ConsumeFromWhere consumeFromWhere,
Set<SubscriptionData> subList
) {
this.serviceManager.getConsumerManager().registerConsumer(
consumerGroup,
clientChannelInfo,
consumeType,
messageModel,
consumeFromWhere,
subList,
false);
}
public ClientChannelInfo findConsumerChannel(
ProxyContext ctx,
String consumerGroup,
String clientId
) {
return this.serviceManager.getConsumerManager().findChannel(consumerGroup, clientId);
}
public void unRegisterConsumer(
ProxyContext ctx,
String consumerGroup,
ClientChannelInfo clientChannelInfo
) {
this.serviceManager.getConsumerManager().unregisterConsumer(consumerGroup, clientChannelInfo, false);
}
public void registerConsumerIdsChangeListener(ConsumerIdsChangeListener listener) {
this.serviceManager.getConsumerManager().appendConsumerIdsChangeListener(listener);
}
}
@@ -0,0 +1,200 @@
/*
* 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.proxy.processor;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.consumer.PopStatus;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
import org.apache.rocketmq.proxy.common.utils.ProxyUtils;
import org.apache.rocketmq.proxy.service.ServiceManager;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
public class ConsumerProcessor extends AbstractProcessor {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
public ConsumerProcessor(MessagingProcessor messagingProcessor,
ServiceManager serviceManager) {
super(messagingProcessor, serviceManager);
}
public CompletableFuture<PopResult> popMessage(
ProxyContext ctx,
QueueSelector queueSelector,
String consumerGroup,
String topic,
int maxMsgNums,
long invisibleTime,
long pollTime,
int initMode,
SubscriptionData subscriptionData,
boolean fifo,
PopMessageResultFilter popMessageResultFilter,
long timeoutMillis
) {
CompletableFuture<PopResult> future = new CompletableFuture<>();
try {
SelectableMessageQueue messageQueue = queueSelector.select(ctx, this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(topic));
if (messageQueue == null) {
throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no readable queue");
}
if (maxMsgNums > ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST) {
log.warn("change maxNums from {} to {} for pop request, with info: topic:{}, group:{}",
maxMsgNums, ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST, topic, consumerGroup);
maxMsgNums = ProxyUtils.MAX_MSG_NUMS_FOR_POP_REQUEST;
}
PopMessageRequestHeader requestHeader = new PopMessageRequestHeader();
requestHeader.setConsumerGroup(consumerGroup);
requestHeader.setTopic(topic);
requestHeader.setQueueId(messageQueue.getQueueId());
requestHeader.setMaxMsgNums(maxMsgNums);
requestHeader.setInvisibleTime(invisibleTime);
requestHeader.setPollTime(pollTime);
requestHeader.setInitMode(initMode);
requestHeader.setExpType(subscriptionData.getExpressionType());
requestHeader.setExp(subscriptionData.getSubString());
requestHeader.setOrder(fifo);
return this.serviceManager.getMessageService().popMessage(
ctx,
messageQueue,
requestHeader,
timeoutMillis)
.thenApply(popResult -> {
if (PopStatus.FOUND.equals(popResult.getPopStatus()) &&
popResult.getMsgFoundList() != null &&
!popResult.getMsgFoundList().isEmpty() &&
popMessageResultFilter != null) {
List<MessageExt> messageExtList = new ArrayList<>();
for (MessageExt messageExt : popResult.getMsgFoundList()) {
try {
PopMessageResultFilter.FilterResult filterResult =
popMessageResultFilter.filterMessage(ctx, consumerGroup, subscriptionData, messageExt);
switch (filterResult) {
case NO_MATCH:
this.messagingProcessor.ackMessage(
ctx,
ReceiptHandle.create(messageExt),
messageExt.getMsgId(),
consumerGroup,
topic,
MessagingProcessor.DEFAULT_TIMEOUT_MILLS);
break;
case TO_DLQ:
this.messagingProcessor.forwardMessageToDeadLetterQueue(
ctx,
ReceiptHandle.create(messageExt),
messageExt.getMsgId(),
consumerGroup,
topic,
MessagingProcessor.DEFAULT_TIMEOUT_MILLS);
break;
case MATCH:
default:
messageExtList.add(messageExt);
break;
}
} catch (Throwable t) {
log.error("process filterMessage failed. requestHeader:{}, msg:{}", requestHeader, messageExt, t);
messageExtList.add(messageExt);
}
}
popResult.setMsgFoundList(messageExtList);
}
return popResult;
});
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public CompletableFuture<AckResult> ackMessage(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String consumerGroup,
String topic,
long timeoutMillis
) {
CompletableFuture<AckResult> future = new CompletableFuture<>();
try {
this.checkReceiptHandle(handle);
AckMessageRequestHeader ackMessageRequestHeader = new AckMessageRequestHeader();
ackMessageRequestHeader.setConsumerGroup(consumerGroup);
ackMessageRequestHeader.setTopic(handle.getRealTopic(topic, consumerGroup));
ackMessageRequestHeader.setQueueId(handle.getQueueId());
ackMessageRequestHeader.setExtraInfo(handle.getReceiptHandle());
ackMessageRequestHeader.setOffset(handle.getOffset());
return this.serviceManager.getMessageService().ackMessage(
ctx,
handle,
messageId,
ackMessageRequestHeader,
timeoutMillis);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle,
String messageId, String groupName, String topicName, long invisibleTime, long timeoutMillis) {
CompletableFuture<AckResult> future = new CompletableFuture<>();
try {
this.checkReceiptHandle(handle);
ChangeInvisibleTimeRequestHeader changeInvisibleTimeRequestHeader = new ChangeInvisibleTimeRequestHeader();
changeInvisibleTimeRequestHeader.setConsumerGroup(groupName);
changeInvisibleTimeRequestHeader.setTopic(handle.getRealTopic(topicName, groupName));
changeInvisibleTimeRequestHeader.setQueueId(handle.getQueueId());
changeInvisibleTimeRequestHeader.setExtraInfo(handle.getReceiptHandle());
changeInvisibleTimeRequestHeader.setOffset(handle.getOffset());
changeInvisibleTimeRequestHeader.setInvisibleTime(invisibleTime);
return this.serviceManager.getMessageService().changeInvisibleTime(
ctx,
handle,
messageId,
changeInvisibleTimeRequestHeader,
timeoutMillis);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
}
@@ -0,0 +1,168 @@
/*
* 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.proxy.processor;
import io.netty.channel.Channel;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.Address;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.ServiceManager;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class DefaultMessagingProcessor implements MessagingProcessor {
private final ServiceManager serviceManager;
private final ProducerProcessor producerProcessor;
private final ConsumerProcessor consumerProcessor;
private final TransactionProcessor transactionProcessor;
private final ClientProcessor clientProcessor;
public DefaultMessagingProcessor(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
this.producerProcessor = new ProducerProcessor(this, serviceManager);
this.consumerProcessor = new ConsumerProcessor(this, serviceManager);
this.transactionProcessor = new TransactionProcessor(this, serviceManager);
this.clientProcessor = new ClientProcessor(this, serviceManager);
}
@Override
public ProxyTopicRouteData getTopicRouteDataForProxy(ProxyContext ctx, List<Address> requestHostAndPortList,
String topicName) throws Exception {
return this.serviceManager.getTopicRouteService().getTopicRouteForProxy(requestHostAndPortList, topicName);
}
@Override
public CompletableFuture<SendResult> sendMessage(ProxyContext ctx, QueueSelector queueSelector,
String producerGroup, List<MessageExt> msg, long timeoutMillis) {
return this.producerProcessor.sendMessage(ctx, queueSelector, producerGroup, msg, timeoutMillis);
}
@Override
public CompletableFuture<RemotingCommand> forwardMessageToDeadLetterQueue(ProxyContext ctx, ReceiptHandle handle,
String messageId, String groupName, String topicName, long timeoutMillis) {
return this.producerProcessor.forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, timeoutMillis);
}
@Override
public void endTransaction(ProxyContext ctx, TransactionId transactionId, String messageId,
String producerGroup, TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis) throws MQBrokerException, RemotingException, InterruptedException {
this.transactionProcessor.endTransaction(ctx, transactionId, messageId, producerGroup, transactionStatus, fromTransactionCheck, timeoutMillis);
}
@Override
public CompletableFuture<PopResult> popMessage(
ProxyContext ctx,
QueueSelector queueSelector,
String consumerGroup,
String topic,
int maxMsgNums,
long invisibleTime,
long pollTime,
int initMode,
SubscriptionData subscriptionData,
boolean fifo,
PopMessageResultFilter popMessageResultFilter,
long timeoutMillis
) {
return this.consumerProcessor.popMessage(ctx, queueSelector, consumerGroup, topic, maxMsgNums,
invisibleTime, pollTime, initMode, subscriptionData, fifo, popMessageResultFilter, timeoutMillis);
}
@Override
public CompletableFuture<AckResult> ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId,
String consumerGroup, String topic, long timeoutMillis) {
return this.consumerProcessor.ackMessage(ctx, handle, messageId, consumerGroup, topic, timeoutMillis);
}
@Override
public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId,
String groupName, String topicName, long invisibleTime, long timeoutMillis) {
return this.consumerProcessor.changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, timeoutMillis);
}
@Override
public void registerProducer(ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo) {
this.clientProcessor.registerProducer(ctx, producerGroup, clientChannelInfo);
}
@Override
public void unRegisterProducer(ProxyContext ctx, String producerGroup, ClientChannelInfo clientChannelInfo) {
this.clientProcessor.unRegisterProducer(ctx, producerGroup, clientChannelInfo);
}
@Override
public Channel findProducerChannel(ProxyContext ctx, String producerGroup, String clientId) {
return this.clientProcessor.findProducerChannel(ctx, producerGroup, clientId);
}
@Override
public void registerProducerListener(ProducerChangeListener producerChangeListener) {
this.clientProcessor.registerProducerChangeListener(producerChangeListener);
}
@Override
public void registerConsumer(ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo,
ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere,
Set<SubscriptionData> subList) {
this.clientProcessor.registerConsumer(ctx, consumerGroup, clientChannelInfo, consumeType, messageModel, consumeFromWhere, subList);
}
@Override
public ClientChannelInfo findConsumerChannel(ProxyContext ctx, String consumerGroup, String clientId) {
return this.clientProcessor.findConsumerChannel(ctx, consumerGroup, clientId);
}
@Override
public void unRegisterConsumer(ProxyContext ctx, String consumerGroup, ClientChannelInfo clientChannelInfo) {
this.clientProcessor.unRegisterConsumer(ctx, consumerGroup, clientChannelInfo);
}
@Override
public void registerConsumerListener(ConsumerIdsChangeListener consumerIdsChangeListener) {
this.clientProcessor.registerConsumerIdsChangeListener(consumerIdsChangeListener);
}
@Override
public void addTransactionSubscription(ProxyContext ctx, String producerGroup, String topic) {
this.transactionProcessor.addTransactionSubscription(ctx, producerGroup, topic);
}
@Override
public ProxyOutService getProxyOutService() {
return this.serviceManager.getProxyOutService();
}
}
@@ -0,0 +1,222 @@
/*
* 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.proxy.processor;
import io.netty.channel.Channel;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.ConsumeType;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.Address;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.route.ProxyTopicRouteData;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface MessagingProcessor {
long DEFAULT_TIMEOUT_MILLS = Duration.ofSeconds(2).toMillis();
ProxyTopicRouteData getTopicRouteDataForProxy(
ProxyContext ctx,
List<Address> requestHostAndPortList,
String topicName
) throws Exception;
default CompletableFuture<SendResult> sendMessage(
ProxyContext ctx,
QueueSelector queueSelector,
String producerGroup,
List<MessageExt> msg
) {
return sendMessage(ctx, queueSelector, producerGroup, msg, DEFAULT_TIMEOUT_MILLS);
}
CompletableFuture<SendResult> sendMessage(
ProxyContext ctx,
QueueSelector queueSelector,
String producerGroup,
List<MessageExt> msg,
long timeoutMillis
);
default CompletableFuture<RemotingCommand> forwardMessageToDeadLetterQueue(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String groupName,
String topicName
) {
return forwardMessageToDeadLetterQueue(ctx, handle, messageId, groupName, topicName, DEFAULT_TIMEOUT_MILLS);
}
CompletableFuture<RemotingCommand> forwardMessageToDeadLetterQueue(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String groupName,
String topicName,
long timeoutMillis
);
default void endTransaction(
ProxyContext ctx,
TransactionId transactionId,
String messageId,
String producerGroup,
TransactionStatus transactionStatus,
boolean fromTransactionCheck
) throws MQBrokerException, RemotingException, InterruptedException {
endTransaction(ctx, transactionId, messageId, producerGroup, transactionStatus, fromTransactionCheck, DEFAULT_TIMEOUT_MILLS);
}
void endTransaction(
ProxyContext ctx,
TransactionId transactionId,
String messageId,
String producerGroup,
TransactionStatus transactionStatus,
boolean fromTransactionCheck,
long timeoutMillis
) throws MQBrokerException, RemotingException, InterruptedException;
CompletableFuture<PopResult> popMessage(
ProxyContext ctx,
QueueSelector queueSelector,
String consumerGroup,
String topic,
int maxMsgNums,
long invisibleTime,
long pollTime,
int initMode,
SubscriptionData subscriptionData,
boolean fifo,
PopMessageResultFilter popMessageResultFilter,
long timeoutMillis
);
default CompletableFuture<AckResult> ackMessage(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String consumerGroup,
String topic
) {
return ackMessage(ctx, handle, messageId, consumerGroup, topic, DEFAULT_TIMEOUT_MILLS);
}
CompletableFuture<AckResult> ackMessage(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String consumerGroup,
String topic,
long timeoutMillis
);
default CompletableFuture<AckResult> changeInvisibleTime(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String groupName,
String topicName,
long invisibleTime
) {
return changeInvisibleTime(ctx, handle, messageId, groupName, topicName, invisibleTime, DEFAULT_TIMEOUT_MILLS);
}
CompletableFuture<AckResult> changeInvisibleTime(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
String groupName,
String topicName,
long invisibleTime,
long timeoutMillis
);
void registerProducer(
ProxyContext ctx,
String producerGroup,
ClientChannelInfo clientChannelInfo
);
void unRegisterProducer(
ProxyContext ctx,
String producerGroup,
ClientChannelInfo clientChannelInfo
);
Channel findProducerChannel(
ProxyContext ctx,
String producerGroup,
String clientId
);
void registerProducerListener(
ProducerChangeListener producerChangeListener
);
void registerConsumer(
ProxyContext ctx,
String consumerGroup,
ClientChannelInfo clientChannelInfo,
ConsumeType consumeType,
MessageModel messageModel,
ConsumeFromWhere consumeFromWhere,
Set<SubscriptionData> subList
);
ClientChannelInfo findConsumerChannel(
ProxyContext ctx,
String consumerGroup,
String clientId
);
void unRegisterConsumer(
ProxyContext ctx,
String consumerGroup,
ClientChannelInfo clientChannelInfo
);
void registerConsumerListener(
ConsumerIdsChangeListener consumerIdsChangeListener
);
void addTransactionSubscription(
ProxyContext ctx,
String producerGroup,
String topic
);
ProxyOutService getProxyOutService();
}
@@ -14,14 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.service.cluster;
package org.apache.rocketmq.proxy.processor;
import apache.rocketmq.v2.ReceiveMessageRequest;
import io.grpc.Context;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.SubscriptionData;
import org.apache.rocketmq.proxy.common.ProxyContext;
public interface ReadQueueSelector {
public interface PopMessageResultFilter {
SelectableMessageQueue select(Context ctx, ReceiveMessageRequest request, PopMessageRequestHeader requestHeader);
enum FilterResult {
TO_DLQ,
NO_MATCH,
MATCH
}
FilterResult filterMessage(ProxyContext ctx, String consumerGroup, SubscriptionData subscriptionData,
MessageExt messageExt);
}
@@ -0,0 +1,136 @@
/*
* 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.proxy.processor;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.MessageAccessor;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
import org.apache.rocketmq.proxy.service.ServiceManager;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ProducerProcessor extends AbstractProcessor {
public ProducerProcessor(MessagingProcessor messagingProcessor,
ServiceManager serviceManager) {
super(messagingProcessor, serviceManager);
}
public CompletableFuture<SendResult> sendMessage(ProxyContext ctx, QueueSelector queueSelector,
String producerGroup, List<MessageExt> messageExtList, long timeoutMillis) {
CompletableFuture<SendResult> future = new CompletableFuture<>();
try {
String topic = messageExtList.get(0).getTopic();
SelectableMessageQueue messageQueue = queueSelector.select(ctx,
this.serviceManager.getTopicRouteService().getCurrentMessageQueueView(topic));
if (messageQueue == null) {
throw new ProxyException(ProxyExceptionCode.FORBIDDEN, "no writable queue");
}
SendMessageRequestHeader requestHeader = buildSendMessageRequestHeader(messageExtList, producerGroup, messageQueue.getQueueId());
return this.serviceManager.getMessageService().sendMessage(
ctx,
messageQueue,
messageExtList,
requestHeader,
timeoutMillis);
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
protected SendMessageRequestHeader buildSendMessageRequestHeader(List<MessageExt> messageExtList,
String producerGroup, int queueId) {
SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
MessageExt message = messageExtList.get(0);
requestHeader.setProducerGroup(producerGroup);
requestHeader.setTopic(message.getTopic());
requestHeader.setDefaultTopic("");
requestHeader.setDefaultTopicQueueNums(0);
requestHeader.setQueueId(queueId);
requestHeader.setSysFlag(message.getSysFlag());
requestHeader.setBornTimestamp(System.currentTimeMillis());
requestHeader.setFlag(message.getFlag());
requestHeader.setProperties(MessageDecoder.messageProperties2String(message.getProperties()));
requestHeader.setReconsumeTimes(0);
if (messageExtList.size() > 1) {
requestHeader.setBatch(true);
}
if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
String reconsumeTimes = MessageAccessor.getReconsumeTime(message);
if (reconsumeTimes != null) {
requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
MessageAccessor.clearProperty(message, MessageConst.PROPERTY_RECONSUME_TIME);
}
String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(message);
if (maxReconsumeTimes != null) {
requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
MessageAccessor.clearProperty(message, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
}
}
return requestHeader;
}
public CompletableFuture<RemotingCommand> forwardMessageToDeadLetterQueue(ProxyContext ctx, ReceiptHandle handle,
String messageId, String groupName, String topicName, long timeoutMillis) {
CompletableFuture<RemotingCommand> future = new CompletableFuture<>();
try {
ConsumerSendMsgBackRequestHeader consumerSendMsgBackRequestHeader = new ConsumerSendMsgBackRequestHeader();
consumerSendMsgBackRequestHeader.setOffset(handle.getCommitLogOffset());
consumerSendMsgBackRequestHeader.setGroup(groupName);
consumerSendMsgBackRequestHeader.setDelayLevel(-1);
consumerSendMsgBackRequestHeader.setOriginMsgId(messageId);
consumerSendMsgBackRequestHeader.setOriginTopic(handle.getRealTopic(topicName, groupName));
consumerSendMsgBackRequestHeader.setMaxReconsumeTimes(0);
return this.serviceManager.getMessageService().sendMessageBack(
ctx,
handle,
messageId,
consumerSendMsgBackRequestHeader,
timeoutMillis
).whenComplete((remotingCommand, t) -> {
if (t == null && remotingCommand.getCode() == ResponseCode.SUCCESS) {
this.messagingProcessor.ackMessage(ctx, handle, messageId,
groupName, topicName, timeoutMillis);
}
});
} catch (Throwable t) {
future.completeExceptionally(t);
}
return future;
}
}
@@ -14,14 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.grpc.v2.service.cluster;
package org.apache.rocketmq.proxy.processor;
import apache.rocketmq.v2.QueryAssignmentRequest;
import io.grpc.Context;
import java.util.List;
import org.apache.rocketmq.proxy.connector.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.route.MessageQueueView;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
public interface AssignmentQueueSelector {
public interface QueueSelector {
List<SelectableMessageQueue> getAssignment(Context ctx, QueryAssignmentRequest request) throws Exception;
SelectableMessageQueue select(ProxyContext ctx, MessageQueueView messageQueueView);
}
@@ -0,0 +1,80 @@
/*
* 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.proxy.processor;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.ServiceManager;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.exception.RemotingException;
public class TransactionProcessor extends AbstractProcessor {
public TransactionProcessor(MessagingProcessor messagingProcessor,
ServiceManager serviceManager) {
super(messagingProcessor, serviceManager);
}
void endTransaction(ProxyContext ctx, TransactionId transactionId, String messageId,String producerGroup,
TransactionStatus transactionStatus, boolean fromTransactionCheck, long timeoutMillis) throws MQBrokerException, RemotingException, InterruptedException {
EndTransactionRequestHeader requestHeader = buildEndTransactionRequestHeader(transactionId, messageId,
producerGroup, transactionStatus, fromTransactionCheck);
this.serviceManager.getMessageService().endTransactionOneway(
ctx,
transactionId,
requestHeader,
timeoutMillis
);
}
protected EndTransactionRequestHeader buildEndTransactionRequestHeader(TransactionId transactionId, String messageId,String producerGroup,
TransactionStatus transactionStatus, boolean fromTransactionCheck) {
long transactionStateTableOffset = transactionId.getTranStateTableOffset();
long commitLogOffset = transactionId.getCommitLogOffset();
int commitOrRollback;
switch (transactionStatus) {
case COMMIT:
commitOrRollback = MessageSysFlag.TRANSACTION_COMMIT_TYPE;
break;
case ROLLBACK:
commitOrRollback = MessageSysFlag.TRANSACTION_ROLLBACK_TYPE;
break;
default:
commitOrRollback = MessageSysFlag.TRANSACTION_NOT_TYPE;
break;
}
EndTransactionRequestHeader endTransactionRequestHeader = new EndTransactionRequestHeader();
endTransactionRequestHeader.setProducerGroup(producerGroup);
endTransactionRequestHeader.setMsgId(messageId);
endTransactionRequestHeader.setTransactionId(transactionId.getBrokerTransactionId());
endTransactionRequestHeader.setTranStateTableOffset(transactionStateTableOffset);
endTransactionRequestHeader.setCommitLogOffset(commitLogOffset);
endTransactionRequestHeader.setCommitOrRollback(commitOrRollback);
endTransactionRequestHeader.setFromTransactionCheck(fromTransactionCheck);
return endTransactionRequestHeader;
}
public void addTransactionSubscription(ProxyContext ctx, String producerGroup, String topic) {
this.serviceManager.getTransactionService().addTransactionSubscription(producerGroup, topic);
}
}
@@ -0,0 +1,23 @@
/*
* 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.proxy.processor;
public enum TransactionStatus {
UNKNOWN,
COMMIT,
ROLLBACK
}
@@ -0,0 +1,152 @@
/*
* 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.proxy.service;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.broker.client.ClientChannelInfo;
import org.apache.rocketmq.broker.client.ConsumerGroupEvent;
import org.apache.rocketmq.broker.client.ConsumerIdsChangeListener;
import org.apache.rocketmq.broker.client.ConsumerManager;
import org.apache.rocketmq.broker.client.ProducerChangeListener;
import org.apache.rocketmq.broker.client.ProducerGroupEvent;
import org.apache.rocketmq.broker.client.ProducerManager;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.config.ProxyConfig;
import org.apache.rocketmq.proxy.service.mqclient.MQClientAPIFactory;
import org.apache.rocketmq.proxy.service.message.ClusterMessageService;
import org.apache.rocketmq.proxy.service.message.MessageService;
import org.apache.rocketmq.proxy.service.mqclient.DoNothingClientRemotingProcessor;
import org.apache.rocketmq.proxy.service.out.ClusterProxyOutService;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.route.ClusterTopicRouteService;
import org.apache.rocketmq.proxy.service.route.TopicRouteService;
import org.apache.rocketmq.proxy.service.transaction.ClusterTransactionService;
import org.apache.rocketmq.proxy.service.transaction.TransactionService;
import org.apache.rocketmq.remoting.RPCHook;
public class ClusterServiceManager extends ServiceManager {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
private final ClusterTransactionService clusterTransactionService;
private final ProducerManager producerManager;
private final ConsumerManager consumerManager;
private final TopicRouteService topicRouteService;
private final MessageService messageService;
private final ProxyOutService proxyOutService;
private final ScheduledExecutorService scheduledExecutorService;
private final MQClientAPIFactory mqClientAPIFactory;
public ClusterServiceManager(RPCHook rpcHook) {
this.scheduledExecutorService = Executors.newScheduledThreadPool(3);
this.producerManager = new ProducerManager();
this.consumerManager = new ConsumerManager(new ConsumerIdsChangeListenerImpl());
ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
double workerFactor = proxyConfig.getRocketmqMQClientWorkerFactor();
int threadCount = (int) Math.ceil(Runtime.getRuntime().availableProcessors() * workerFactor);
this.mqClientAPIFactory = new MQClientAPIFactory(
"CLUSTER_MQ_CLIENT_",
proxyConfig.getRocketmqMQClientNum(),
threadCount,
new DoNothingClientRemotingProcessor(null),
rpcHook,
scheduledExecutorService);
this.topicRouteService = new ClusterTopicRouteService(rpcHook);
this.messageService = new ClusterMessageService(this.topicRouteService, this.mqClientAPIFactory);
this.clusterTransactionService = new ClusterTransactionService(this.topicRouteService, this.producerManager, rpcHook);
this.proxyOutService = new ClusterProxyOutService();
this.init();
}
protected void init() {
this.producerManager.appendProducerChangeListener(new ProducerChangeListenerImpl());
this.scheduledExecutorService.scheduleAtFixedRate(() -> {
try {
producerManager.scanNotActiveChannel();
consumerManager.scanNotActiveChannel();
} catch (Throwable e) {
log.error("Error occurred when scan not active client channels.", e);
}
}, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS);
this.appendShutdown(scheduledExecutorService::shutdown);
this.appendStartAndShutdown(this.mqClientAPIFactory);
this.appendStartAndShutdown(this.topicRouteService);
this.appendStartAndShutdown(this.clusterTransactionService);
}
@Override
public MessageService getMessageService() {
return this.messageService;
}
@Override
public TopicRouteService getTopicRouteService() {
return topicRouteService;
}
@Override
public ProducerManager getProducerManager() {
return this.producerManager;
}
@Override
public ConsumerManager getConsumerManager() {
return this.consumerManager;
}
@Override
public TransactionService getTransactionService() {
return this.clusterTransactionService;
}
@Override
public ProxyOutService getProxyOutService() {
return this.proxyOutService;
}
protected static class ConsumerIdsChangeListenerImpl implements ConsumerIdsChangeListener {
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {
}
@Override
public void shutdown() {
}
}
protected class ProducerChangeListenerImpl implements ProducerChangeListener {
@Override
public void handle(ProducerGroupEvent event, String group, ClientChannelInfo clientChannelInfo) {
if (event == ProducerGroupEvent.GROUP_UNREGISTER) {
getTransactionService().unSubscribeAllTransactionTopic(group);
}
}
}
}
@@ -0,0 +1,84 @@
/*
* 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.proxy.service;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.client.ConsumerManager;
import org.apache.rocketmq.broker.client.ProducerManager;
import org.apache.rocketmq.proxy.service.message.LocalMessageService;
import org.apache.rocketmq.proxy.service.message.MessageService;
import org.apache.rocketmq.proxy.service.out.LocalProxyOutService;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.route.LocalTopicRouteService;
import org.apache.rocketmq.proxy.service.route.TopicRouteService;
import org.apache.rocketmq.proxy.service.transaction.LocalTransactionService;
import org.apache.rocketmq.proxy.service.transaction.TransactionService;
import org.apache.rocketmq.remoting.RPCHook;
public class LocalServiceManager extends ServiceManager {
private final BrokerController brokerController;
private final TopicRouteService topicRouteService;
private final MessageService messageService;
private final TransactionService transactionService;
private final ProxyOutService proxyOutService;
public LocalServiceManager(BrokerController brokerController, RPCHook rpcHook) {
this.brokerController = brokerController;
this.messageService = new LocalMessageService(brokerController, rpcHook);
this.topicRouteService = new LocalTopicRouteService(brokerController, rpcHook);
this.transactionService = new LocalTransactionService();
this.proxyOutService = new LocalProxyOutService(brokerController);
this.init();
}
protected void init() {
this.appendStartAndShutdown(this.topicRouteService);
}
@Override
public MessageService getMessageService() {
return this.messageService;
}
@Override
public TopicRouteService getTopicRouteService() {
return this.topicRouteService;
}
@Override
public ProducerManager getProducerManager() {
return this.brokerController.getProducerManager();
}
@Override
public ConsumerManager getConsumerManager() {
return this.brokerController.getConsumerManager();
}
@Override
public TransactionService getTransactionService() {
return this.transactionService;
}
@Override
public ProxyOutService getProxyOutService() {
return this.proxyOutService;
}
}
@@ -0,0 +1,58 @@
/*
* 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.proxy.service;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.client.ConsumerManager;
import org.apache.rocketmq.broker.client.ProducerManager;
import org.apache.rocketmq.proxy.common.AbstractStartAndShutdown;
import org.apache.rocketmq.proxy.service.message.MessageService;
import org.apache.rocketmq.proxy.service.out.ProxyOutService;
import org.apache.rocketmq.proxy.service.route.TopicRouteService;
import org.apache.rocketmq.proxy.service.transaction.TransactionService;
import org.apache.rocketmq.remoting.RPCHook;
public abstract class ServiceManager extends AbstractStartAndShutdown {
public static ServiceManager createForLocalMode(BrokerController brokerController) {
return createForLocalMode(brokerController, null);
}
public static ServiceManager createForLocalMode(BrokerController brokerController, RPCHook rpcHook) {
return new LocalServiceManager(brokerController, rpcHook);
}
public static ServiceManager createForClusterMode() {
return createForClusterMode(null);
}
public static ServiceManager createForClusterMode(RPCHook rpcHook) {
return new ClusterServiceManager(rpcHook);
}
public abstract MessageService getMessageService();
public abstract TopicRouteService getTopicRouteService();
public abstract ProducerManager getProducerManager();
public abstract ConsumerManager getConsumerManager();
public abstract TransactionService getTransactionService();
public abstract ProxyOutService getProxyOutService();
}
@@ -0,0 +1,44 @@
/*
* 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.proxy.service.message;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.common.sysflag.MessageSysFlag;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
public abstract class AbstractMessageService implements MessageService {
protected CompletableFuture<SendResult> processSendMessageResponseFuture(
String brokerName,
SendMessageRequestHeader requestHeader,
CompletableFuture<SendResult> future) {
return future.thenApply(sendResult -> {
int tranType = MessageSysFlag.getTransactionValue(requestHeader.getSysFlag());
if (SendStatus.SEND_OK.equals(sendResult.getSendStatus()) &&
tranType == MessageSysFlag.TRANSACTION_PREPARED_TYPE &&
StringUtils.isNotBlank(sendResult.getTransactionId())) {
TransactionId transactionId = TransactionId.genByBrokerTransactionId(brokerName, sendResult);
sendResult.setTransactionId(transactionId.getProxyTransactionId());
}
return sendResult;
});
}
}
@@ -0,0 +1,133 @@
/*
* 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.proxy.service.message;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.common.ProxyException;
import org.apache.rocketmq.proxy.common.ProxyExceptionCode;
import org.apache.rocketmq.proxy.service.mqclient.MQClientAPIFactory;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.service.route.TopicRouteService;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ClusterMessageService extends AbstractMessageService {
private final TopicRouteService topicRouteService;
private final MQClientAPIFactory mqClientAPIFactory;
public ClusterMessageService(TopicRouteService topicRouteService, MQClientAPIFactory mqClientAPIFactory) {
this.topicRouteService = topicRouteService;
this.mqClientAPIFactory = mqClientAPIFactory;
}
@Override
public CompletableFuture<SendResult> sendMessage(ProxyContext ctx, SelectableMessageQueue messageQueue,
List<? extends Message> msgList, SendMessageRequestHeader requestHeader, long timeoutMillis) {
CompletableFuture<SendResult> future;
if (msgList.size() == 1) {
future = this.mqClientAPIFactory.getClient().sendMessageAsync(
messageQueue.getBrokerAddr(),
messageQueue.getBrokerName(), msgList.get(0), requestHeader, timeoutMillis);
} else {
future = this.mqClientAPIFactory.getClient().sendMessageAsync(
messageQueue.getBrokerAddr(),
messageQueue.getBrokerName(), msgList, requestHeader, timeoutMillis);
}
return processSendMessageResponseFuture(messageQueue.getBrokerName(), requestHeader, future);
}
@Override
public CompletableFuture<RemotingCommand> sendMessageBack(ProxyContext ctx, ReceiptHandle handle, String messageId,
ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis) {
return this.mqClientAPIFactory.getClient().sendMessageBackAsync(
this.resolveBrokerAddr(handle),
requestHeader,
timeoutMillis
);
}
@Override
public void endTransactionOneway(ProxyContext ctx, TransactionId transactionId,
EndTransactionRequestHeader requestHeader, long timeoutMillis) throws MQBrokerException, RemotingException, InterruptedException {
this.mqClientAPIFactory.getClient().endTransactionOneway(
this.resolveBrokerAddr(transactionId.getBrokerName()),
requestHeader,
"end transaction from proxy",
timeoutMillis
);
}
@Override
public CompletableFuture<PopResult> popMessage(ProxyContext ctx, SelectableMessageQueue messageQueue,
PopMessageRequestHeader requestHeader, long timeoutMillis) {
return this.mqClientAPIFactory.getClient().popMessageAsync(
messageQueue.getBrokerAddr(),
messageQueue.getBrokerName(),
requestHeader,
timeoutMillis
);
}
@Override
public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId,
ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis) {
return this.mqClientAPIFactory.getClient().changeInvisibleTimeAsync(
this.resolveBrokerAddr(handle),
handle.getBrokerName(),
requestHeader,
timeoutMillis
);
}
@Override
public CompletableFuture<AckResult> ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId,
AckMessageRequestHeader requestHeader, long timeoutMillis) {
return this.mqClientAPIFactory.getClient().ackMessageAsync(
this.resolveBrokerAddr(handle),
requestHeader,
timeoutMillis
);
}
protected String resolveBrokerAddr(ReceiptHandle handle) {
return resolveBrokerAddr(handle.getBrokerName());
}
protected String resolveBrokerAddr(String brokerName) {
try {
return this.topicRouteService.getBrokerAddr(brokerName);
} catch (Throwable t) {
throw new ProxyException(ProxyExceptionCode.INVALID_BROKER_NAME, "broker " + brokerName + " cannot find", t);
}
}
}
@@ -0,0 +1,78 @@
/*
* 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.proxy.service.message;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class LocalMessageService extends AbstractMessageService {
private BrokerController brokerController;
public LocalMessageService(BrokerController brokerController, RPCHook rpcHook) {
this.brokerController = brokerController;
}
@Override public CompletableFuture<SendResult> sendMessage(ProxyContext ctx, SelectableMessageQueue messageQueue,
List<? extends Message> msgList, SendMessageRequestHeader requestHeader, long timeoutMillis) {
return null;
}
@Override
public CompletableFuture<RemotingCommand> sendMessageBack(ProxyContext ctx, ReceiptHandle handle, String messageId,
ConsumerSendMsgBackRequestHeader requestHeader, long timeoutMillis) {
return null;
}
@Override public void endTransactionOneway(ProxyContext ctx, TransactionId transactionId,
EndTransactionRequestHeader requestHeader, long timeoutMillis) {
}
@Override public CompletableFuture<PopResult> popMessage(ProxyContext ctx, SelectableMessageQueue messageQueue,
PopMessageRequestHeader requestHeader, long timeoutMillis) {
return null;
}
@Override
public CompletableFuture<AckResult> changeInvisibleTime(ProxyContext ctx, ReceiptHandle handle, String messageId,
ChangeInvisibleTimeRequestHeader requestHeader, long timeoutMillis) {
return null;
}
@Override public CompletableFuture<AckResult> ackMessage(ProxyContext ctx, ReceiptHandle handle, String messageId,
AckMessageRequestHeader requestHeader, long timeoutMillis) {
return null;
}
}
@@ -14,21 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.factory;
package org.apache.rocketmq.proxy.service.message;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
import org.apache.rocketmq.proxy.connector.processor.DoNothingClientRemotingProcessor;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.CommandCustomHeader;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class MQClientFactory extends AbstractMQClientFactory {
public class LocalRemotingCommand extends RemotingCommand {
public MQClientFactory(ScheduledExecutorService scheduledExecutorService, RPCHook rpcHook) {
super(scheduledExecutorService, rpcHook);
public static LocalRemotingCommand createRequestCommand(int code, CommandCustomHeader customHeader) {
LocalRemotingCommand cmd = new LocalRemotingCommand();
cmd.setCode(code);
cmd.writeCustomHeader(customHeader);
setCmdVersion(cmd);
return cmd;
}
@Override
protected ClientRemotingProcessor createClientRemotingProcessor() {
return new DoNothingClientRemotingProcessor(null);
public <T extends CommandCustomHeader> T decodeCommandCustomHeader(
Class<T> classHeader) throws RemotingCommandException {
return classHeader.cast(readCustomHeader());
}
}
@@ -0,0 +1,86 @@
/*
* 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.proxy.service.message;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.client.consumer.AckResult;
import org.apache.rocketmq.client.consumer.PopResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.consumer.ReceiptHandle;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.protocol.header.AckMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.ChangeInvisibleTimeRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumerSendMsgBackRequestHeader;
import org.apache.rocketmq.common.protocol.header.EndTransactionRequestHeader;
import org.apache.rocketmq.common.protocol.header.PopMessageRequestHeader;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.common.ProxyContext;
import org.apache.rocketmq.proxy.service.route.SelectableMessageQueue;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface MessageService {
CompletableFuture<SendResult> sendMessage(
ProxyContext ctx,
SelectableMessageQueue messageQueue,
List<? extends Message> msgList,
SendMessageRequestHeader requestHeader,
long timeoutMillis
);
CompletableFuture<RemotingCommand> sendMessageBack(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
ConsumerSendMsgBackRequestHeader requestHeader,
long timeoutMillis
);
void endTransactionOneway(
ProxyContext ctx,
TransactionId transactionId,
EndTransactionRequestHeader requestHeader,
long timeoutMillis
) throws MQBrokerException, RemotingException, InterruptedException;
CompletableFuture<PopResult> popMessage(
ProxyContext ctx,
SelectableMessageQueue messageQueue,
PopMessageRequestHeader requestHeader,
long timeoutMillis
);
CompletableFuture<AckResult> changeInvisibleTime(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
ChangeInvisibleTimeRequestHeader requestHeader,
long timeoutMillis
);
CompletableFuture<AckResult> ackMessage(
ProxyContext ctx,
ReceiptHandle handle,
String messageId,
AckMessageRequestHeader requestHeader,
long timeoutMillis
);
}
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.processor;
package org.apache.rocketmq.proxy.service.mqclient;
import io.netty.channel.ChannelHandlerContext;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.client;
package org.apache.rocketmq.proxy.service.mqclient;
import java.nio.ByteBuffer;
import java.util.Collections;
@@ -60,7 +60,11 @@ import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.exception.RemotingConnectException;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.apache.rocketmq.remoting.exception.RemotingSendRequestException;
import org.apache.rocketmq.remoting.exception.RemotingTimeoutException;
import org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException;
import org.apache.rocketmq.remoting.netty.NettyClientConfig;
import org.apache.rocketmq.remoting.netty.ResponseFuture;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
@@ -102,6 +106,17 @@ public class MQClientAPIExt extends MQClientAPIImpl {
return ex;
}
public void sendHeartbeatOneway(
String brokerAddr,
HeartbeatData heartbeatData,
long timeoutMillis
) throws RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException, InterruptedException, RemotingTooMuchRequestException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.HEART_BEAT, null);
request.setLanguage(clientConfig.getLanguage());
request.setBody(heartbeatData.encode());
this.getRemotingClient().invokeOneway(brokerAddr, request, timeoutMillis);
}
public CompletableFuture<Integer> sendHeartbeatAsync(
String brokerAddr,
HeartbeatData heartbeatData,
@@ -164,7 +179,7 @@ public class MQClientAPIExt extends MQClientAPIImpl {
public CompletableFuture<SendResult> sendMessageAsync(
String brokerAddr,
String brokerName,
List<Message> msgList,
List<? extends Message> msgList,
SendMessageRequestHeader requestHeader,
long timeoutMillis
) {
@@ -0,0 +1,115 @@
/*
* 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.proxy.service.mqclient;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.ClientConfig;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.proxy.common.StartAndShutdown;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.proxy.config.ProxyConfig;
import org.apache.rocketmq.remoting.RPCHook;
import org.apache.rocketmq.remoting.netty.NettyClientConfig;
public class MQClientAPIFactory implements StartAndShutdown {
private MQClientAPIExt[] clients;
private final String namePrefix;
private final int clientNum;
private final int bootstrapWorkerThreads;
private final ClientRemotingProcessor clientRemotingProcessor;
private final RPCHook rpcHook;
private final ScheduledExecutorService scheduledExecutorService;
public MQClientAPIFactory(String namePrefix, int clientNum, int bootstrapWorkerThreads,
ClientRemotingProcessor clientRemotingProcessor,
RPCHook rpcHook, ScheduledExecutorService scheduledExecutorService) {
this.namePrefix = namePrefix;
this.clientNum = clientNum;
this.bootstrapWorkerThreads = bootstrapWorkerThreads;
this.clientRemotingProcessor = clientRemotingProcessor;
this.rpcHook = rpcHook;
this.scheduledExecutorService = scheduledExecutorService;
this.init();
}
protected void init() {
System.setProperty(ClientConfig.SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "false");
ProxyConfig proxyConfig = ConfigurationManager.getProxyConfig();
if (StringUtils.isEmpty(proxyConfig.getNameSrvDomain())) {
System.setProperty(MixAll.NAMESRV_ADDR_PROPERTY, proxyConfig.getNameSrvAddr());
} else {
System.setProperty("rocketmq.namesrv.domain", proxyConfig.getNameSrvDomain());
System.setProperty("rocketmq.namesrv.domain.subgroup", proxyConfig.getNameSrvDomainSubgroup());
}
}
public MQClientAPIExt getClient() {
if (clients.length == 1) {
return this.clients[0];
}
int index = ThreadLocalRandom.current().nextInt(this.clients.length);
return this.clients[index];
}
@Override
public void start() throws Exception {
this.clients = new MQClientAPIExt[this.clientNum];
for (int i = 0; i < this.clientNum; i++) {
clients[i] = createAndStart(this.namePrefix + "N_" + i);
}
}
@Override
public void shutdown() throws Exception {
for (int i = 0; i < this.clientNum; i++) {
clients[i].shutdown();
}
}
protected MQClientAPIExt createAndStart(String instanceName) {
ClientConfig clientConfig = new ClientConfig();
clientConfig.setInstanceName(instanceName);
NettyClientConfig nettyClientConfig = new NettyClientConfig();
nettyClientConfig.setBootstrapWorkerThreads(bootstrapWorkerThreads);
nettyClientConfig.setDisableNettyWorkerGroup(true);
nettyClientConfig.setDisableCallbackExecutor(true);
MQClientAPIExt mqClientAPIExt = new MQClientAPIExt(clientConfig, nettyClientConfig,
clientRemotingProcessor,
rpcHook);
if (!mqClientAPIExt.updateNameServerAddressList()) {
this.scheduledExecutorService.scheduleAtFixedRate(
mqClientAPIExt::fetchNameServerAddr,
Duration.ofSeconds(10).toMillis(),
Duration.ofMinutes(2).toMillis(),
TimeUnit.MILLISECONDS
);
}
mqClientAPIExt.start();
return mqClientAPIExt;
}
}
@@ -14,28 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.processor;
package org.apache.rocketmq.proxy.service.mqclient;
import io.netty.channel.ChannelHandlerContext;
import java.nio.ByteBuffer;
import org.apache.rocketmq.broker.client.ProducerManager;
import org.apache.rocketmq.client.impl.ClientRemotingProcessor;
import org.apache.rocketmq.common.message.MessageConst;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader;
import org.apache.rocketmq.proxy.connector.transaction.TransactionId;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateCheckRequest;
import org.apache.rocketmq.proxy.connector.transaction.TransactionStateChecker;
import org.apache.rocketmq.remoting.exception.RemotingCommandException;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class ProxyClientRemotingProcessor extends ClientRemotingProcessor {
private final TransactionStateChecker transactionStateChecker;
private final ProducerManager producerManager;
public ProxyClientRemotingProcessor(TransactionStateChecker transactionStateChecker) {
public ProxyClientRemotingProcessor(ProducerManager producerManager) {
super(null);
this.transactionStateChecker = transactionStateChecker;
this.producerManager = producerManager;
}
@Override
@@ -48,30 +46,16 @@ public class ProxyClientRemotingProcessor extends ClientRemotingProcessor {
}
@Override
public RemotingCommand checkTransactionState(ChannelHandlerContext ctx, RemotingCommand request)
throws RemotingCommandException {
final CheckTransactionStateRequestHeader requestHeader =
(CheckTransactionStateRequestHeader) request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class);
public RemotingCommand checkTransactionState(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
final ByteBuffer byteBuffer = ByteBuffer.wrap(request.getBody());
final MessageExt messageExt = MessageDecoder.decode(byteBuffer, true, false, false);
if (messageExt != null) {
final String group = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
if (group != null) {
transactionStateChecker.checkTransactionState(
new TransactionStateCheckRequest(
group,
requestHeader.getTranStateTableOffset(),
requestHeader.getCommitLogOffset(),
requestHeader.getMsgId(),
TransactionId.genByBrokerTransactionId(
ctx.channel().remoteAddress(),
requestHeader.getTransactionId(),
requestHeader.getCommitLogOffset(),
requestHeader.getTranStateTableOffset()),
requestHeader.getBrokerName(),
messageExt
)
);
CheckTransactionStateRequestHeader requestHeader =
request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class);
request.writeCustomHeader(requestHeader);
this.producerManager.getAvailableChannel(group).writeAndFlush(request);
}
}
return null;
@@ -0,0 +1,42 @@
/*
* 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.proxy.service.out;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
/**
* not implement yet
*/
public class ClusterProxyOutService implements ProxyOutService {
@Override
public CompletableFuture<ConsumerRunningInfo> processGetConsumerRunningInfo(RemotingCommand command,
GetConsumerRunningInfoRequestHeader header) {
return new CompletableFuture<>();
}
@Override
public CompletableFuture<ConsumeMessageDirectlyResult> processConsumeMessageDirectly(RemotingCommand command,
ConsumeMessageDirectlyResultRequestHeader header) {
return new CompletableFuture<>();
}
}
@@ -0,0 +1,63 @@
/*
* 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.proxy.service.out;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.remoting.RemotingServer;
import org.apache.rocketmq.remoting.netty.NettyRemotingAbstract;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public class LocalProxyOutService implements ProxyOutService {
private final BrokerController brokerController;
public LocalProxyOutService(BrokerController brokerController) {
this.brokerController = brokerController;
}
@Override
public CompletableFuture<ConsumerRunningInfo> processGetConsumerRunningInfo(RemotingCommand command,
GetConsumerRunningInfoRequestHeader header) {
CompletableFuture<ConsumerRunningInfo> future = new CompletableFuture<>();
future.thenAccept(consumerRunningInfo -> {
RemotingServer remotingServer = this.brokerController.getRemotingServer();
if (remotingServer instanceof NettyRemotingAbstract) {
NettyRemotingAbstract nettyRemotingAbstract = (NettyRemotingAbstract) remotingServer;
RemotingCommand remotingCommand = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, "from gRPC client");
remotingCommand.setOpaque(command.getOpaque());
ConsumerRunningInfo runningInfo = new ConsumerRunningInfo();
runningInfo.setJstack(consumerRunningInfo.getJstack());
remotingCommand.setBody(runningInfo.encode());
// nettyRemotingAbstract.processResponseCommand(new SimpleChannelHandlerContext(channelManager.createChannel(ctx)), remotingCommand);
}
});
return future;
}
@Override
public CompletableFuture<ConsumeMessageDirectlyResult> processConsumeMessageDirectly(RemotingCommand command,
ConsumeMessageDirectlyResultRequestHeader header) {
return null;
}
}
@@ -0,0 +1,171 @@
/*
* 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.proxy.service.out;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelMetadata;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.channel.DefaultChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.message.MessageDecoder;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.CheckTransactionStateRequestHeader;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.logging.InternalLogger;
import org.apache.rocketmq.logging.InternalLoggerFactory;
import org.apache.rocketmq.proxy.service.transaction.TransactionId;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public abstract class ProxyChannel extends AbstractChannel {
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.PROXY_LOGGER_NAME);
protected final ProxyOutService proxyOutService;
protected ProxyChannel(ProxyOutService proxyOutService, Channel parent) {
super(parent);
this.proxyOutService = proxyOutService;
}
protected ProxyChannel(ProxyOutService proxyOutService, Channel parent, ChannelId id) {
super(parent, id);
this.proxyOutService = proxyOutService;
}
@Override
public ChannelFuture writeAndFlush(Object msg) {
CompletableFuture<Void> processFuture = new CompletableFuture<>();
try {
if (msg instanceof RemotingCommand) {
RemotingCommand command = (RemotingCommand) msg;
switch (command.getCode()) {
case RequestCode.CHECK_TRANSACTION_STATE: {
CheckTransactionStateRequestHeader header = (CheckTransactionStateRequestHeader) command.readCustomHeader();
MessageExt messageExt = MessageDecoder.decode(ByteBuffer.wrap(command.getBody()), true, false, false);
TransactionId transactionId = TransactionId.genByBrokerTransactionId(header.getBrokerName(),
header.getTransactionId(), messageExt.getCommitLogOffset(), messageExt.getQueueOffset());
processFuture = this.processCheckTransaction(header, messageExt, transactionId);
break;
}
case RequestCode.GET_CONSUMER_RUNNING_INFO: {
GetConsumerRunningInfoRequestHeader header = (GetConsumerRunningInfoRequestHeader) command.readCustomHeader();
processFuture = this.processGetConsumerRunningInfo(command, header,
this.proxyOutService.processGetConsumerRunningInfo(command, header));
break;
}
case RequestCode.CONSUME_MESSAGE_DIRECTLY: {
ConsumeMessageDirectlyResultRequestHeader header = (ConsumeMessageDirectlyResultRequestHeader) command.readCustomHeader();
MessageExt messageExt = MessageDecoder.decode(ByteBuffer.wrap(command.getBody()), true, false, false);
processFuture = this.processConsumeMessageDirectly(command, header, messageExt,
this.proxyOutService.processConsumeMessageDirectly(command, header));
break;
}
default:
break;
}
} else {
processFuture = processOtherMessage(msg);
}
} catch (Throwable t) {
log.error("process failed. msg:{}", msg, t);
processFuture.completeExceptionally(t);
}
DefaultChannelPromise promise = new DefaultChannelPromise(this, GlobalEventExecutor.INSTANCE);
processFuture.thenAccept(ignore -> promise.setSuccess())
.exceptionally(t -> {
promise.setFailure(t);
return null;
});
return promise;
}
protected abstract CompletableFuture<Void> processOtherMessage(Object msg);
protected abstract CompletableFuture<Void> processCheckTransaction(CheckTransactionStateRequestHeader header,
MessageExt messageExt, TransactionId transactionId);
protected abstract CompletableFuture<Void> processGetConsumerRunningInfo(
RemotingCommand command,
GetConsumerRunningInfoRequestHeader header,
CompletableFuture<ConsumerRunningInfo> responseFuture);
protected abstract CompletableFuture<Void> processConsumeMessageDirectly(
RemotingCommand command,
ConsumeMessageDirectlyResultRequestHeader header,
MessageExt messageExt,
CompletableFuture<ConsumeMessageDirectlyResult> responseFuture);
@Override
public ChannelConfig config() {
return null;
}
@Override
public ChannelMetadata metadata() {
return null;
}
@Override
protected AbstractUnsafe newUnsafe() {
return null;
}
@Override
protected boolean isCompatible(EventLoop loop) {
return false;
}
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
}
@Override
protected void doDisconnect() throws Exception {
}
@Override
protected void doClose() throws Exception {
}
@Override
protected void doBeginRead() throws Exception {
}
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
}
}
@@ -0,0 +1,37 @@
/*
* 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.proxy.service.out;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.common.protocol.body.ConsumeMessageDirectlyResult;
import org.apache.rocketmq.common.protocol.body.ConsumerRunningInfo;
import org.apache.rocketmq.common.protocol.header.ConsumeMessageDirectlyResultRequestHeader;
import org.apache.rocketmq.common.protocol.header.GetConsumerRunningInfoRequestHeader;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
public interface ProxyOutService {
CompletableFuture<ConsumerRunningInfo> processGetConsumerRunningInfo(
RemotingCommand command,
GetConsumerRunningInfoRequestHeader header
);
CompletableFuture<ConsumeMessageDirectlyResult> processConsumeMessageDirectly(
RemotingCommand command,
ConsumeMessageDirectlyResultRequestHeader header
);
}
@@ -0,0 +1,66 @@
/*
* 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.proxy.service.route;
import java.util.List;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.proxy.common.Address;
import org.apache.rocketmq.remoting.RPCHook;
public class ClusterTopicRouteService extends TopicRouteService {
public ClusterTopicRouteService(RPCHook rpcHook) {
super(rpcHook);
}
@Override
public MessageQueueView getCurrentMessageQueueView(String topicName) throws Exception {
return getAllMessageQueueView(topicName);
}
@Override
public ProxyTopicRouteData getTopicRouteForProxy(List<Address> requestHostAndPortList,
String topicName) throws Exception {
TopicRouteData topicRouteData = getAllMessageQueueView(topicName).getTopicRouteData();
ProxyTopicRouteData proxyTopicRouteData = new ProxyTopicRouteData();
proxyTopicRouteData.setQueueDatas(topicRouteData.getQueueDatas());
for (BrokerData brokerData : topicRouteData.getBrokerDatas()) {
ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData();
proxyBrokerData.setCluster(brokerData.getCluster());
proxyBrokerData.setBrokerName(brokerData.getBrokerName());
for (Long brokerId : brokerData.getBrokerAddrs().keySet()) {
proxyBrokerData.getBrokerAddrs().put(brokerId, requestHostAndPortList);
}
proxyTopicRouteData.getBrokerDatas().add(proxyBrokerData);
}
return proxyTopicRouteData;
}
@Override
public String getBrokerAddr(String brokerName) throws Exception {
List<BrokerData> brokerDataList = getAllMessageQueueView(brokerName).getTopicRouteData().getBrokerDatas();
if (brokerDataList.isEmpty()) {
return null;
}
return brokerDataList.get(0).getBrokerAddrs().get(MixAll.MASTER_ID);
}
}
@@ -0,0 +1,102 @@
/*
* 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.proxy.service.route;
import com.google.common.collect.Lists;
import com.google.common.net.HostAndPort;
import java.util.HashMap;
import java.util.List;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.common.BrokerConfig;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.TopicConfig;
import org.apache.rocketmq.common.protocol.route.BrokerData;
import org.apache.rocketmq.common.protocol.route.QueueData;
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
import org.apache.rocketmq.proxy.common.Address;
import org.apache.rocketmq.proxy.config.ConfigurationManager;
import org.apache.rocketmq.remoting.RPCHook;
public class LocalTopicRouteService extends TopicRouteService {
private final BrokerController brokerController;
private final List<BrokerData> brokerDataList;
private final int grpcPort;
public LocalTopicRouteService(BrokerController brokerController, RPCHook rpcHook) {
super(rpcHook);
this.brokerController = brokerController;
BrokerConfig brokerConfig = this.brokerController.getBrokerConfig();
HashMap<Long, String> brokerAddrs = new HashMap<>();
brokerAddrs.put(MixAll.MASTER_ID, this.brokerController.getBrokerAddr());
this.brokerDataList = Lists.newArrayList(
new BrokerData(brokerConfig.getBrokerClusterName(), brokerConfig.getBrokerName(), brokerAddrs)
);
this.grpcPort = ConfigurationManager.getProxyConfig().getGrpcServerPort();
}
@Override
public MessageQueueView getCurrentMessageQueueView(String topic) throws Exception {
TopicConfig topicConfig = this.brokerController.getTopicConfigManager().getTopicConfigTable().get(topic);
return new MessageQueueView(topic, toTopicRouteData(topicConfig));
}
@Override
public ProxyTopicRouteData getTopicRouteForProxy(List<Address> requestHostAndPortList, String topicName) throws Exception {
MessageQueueView messageQueueView = getAllMessageQueueView(topicName);
TopicRouteData topicRouteData = messageQueueView.getTopicRouteData();
ProxyTopicRouteData proxyTopicRouteData = new ProxyTopicRouteData();
proxyTopicRouteData.setQueueDatas(topicRouteData.getQueueDatas());
for (BrokerData brokerData : topicRouteData.getBrokerDatas()) {
ProxyTopicRouteData.ProxyBrokerData proxyBrokerData = new ProxyTopicRouteData.ProxyBrokerData();
proxyBrokerData.setCluster(brokerData.getCluster());
proxyBrokerData.setBrokerName(brokerData.getBrokerName());
for (Long brokerId : brokerData.getBrokerAddrs().keySet()) {
String brokerAddr = brokerData.getBrokerAddrs().get(brokerId);
HostAndPort brokerHostAndPort = HostAndPort.fromString(brokerAddr);
HostAndPort grpcHostAndPort = HostAndPort.fromParts(brokerHostAndPort.getHost(), grpcPort);
proxyBrokerData.getBrokerAddrs().put(brokerId, Lists.newArrayList(new Address(Address.AddressScheme.IPv4, grpcHostAndPort)));
}
proxyTopicRouteData.getBrokerDatas().add(proxyBrokerData);
}
return proxyTopicRouteData;
}
@Override
public String getBrokerAddr(String brokerName) throws Exception {
return this.brokerController.getBrokerAddr();
}
protected TopicRouteData toTopicRouteData(TopicConfig topicConfig) {
TopicRouteData topicRouteData = new TopicRouteData();
topicRouteData.setBrokerDatas(brokerDataList);
QueueData queueData = new QueueData();
queueData.setPerm(topicConfig.getPerm());
queueData.setReadQueueNums(topicConfig.getReadQueueNums());
queueData.setWriteQueueNums(topicConfig.getWriteQueueNums());
queueData.setTopicSysFlag(topicConfig.getTopicSysFlag());
queueData.setBrokerName(this.brokerController.getBrokerConfig().getBrokerName());
topicRouteData.setQueueDatas(Lists.newArrayList(queueData));
return topicRouteData;
}
}
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rocketmq.proxy.connector.route;
package org.apache.rocketmq.proxy.service.route;
import java.util.ArrayList;
import java.util.Collections;

Some files were not shown because too many files have changed in this diff Show More