[ISSUE #3949] Add heartbeat in LocalGrpcService

This commit is contained in:
zhouxiang
2022-07-13 11:29:09 +08:00
parent 9e2dc9329c
commit f5df799397
5 changed files with 216 additions and 13 deletions
@@ -0,0 +1,29 @@
/*
* 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.adapter.channel;
public enum ChannelType {
/**
* The channel connect to current proxy directly
*/
LOCAL,
/**
* The channel sync from other proxy
*/
REMOTE
}
@@ -17,27 +17,50 @@
package org.apache.rocketmq.proxy.grpc.common;
import apache.rocketmq.v1.ConsumeMessageType;
import apache.rocketmq.v1.ConsumeModel;
import apache.rocketmq.v1.ConsumePolicy;
import apache.rocketmq.v1.ConsumerData;
import apache.rocketmq.v1.Encoding;
import apache.rocketmq.v1.FilterExpression;
import apache.rocketmq.v1.FilterType;
import apache.rocketmq.v1.HeartbeatRequest;
import apache.rocketmq.v1.Message;
import apache.rocketmq.v1.MessageType;
import apache.rocketmq.v1.ProducerData;
import apache.rocketmq.v1.Resource;
import apache.rocketmq.v1.SendMessageRequest;
import apache.rocketmq.v1.SubscriptionEntry;
import apache.rocketmq.v1.SystemAttribute;
import com.google.common.collect.Maps;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Durations;
import com.google.protobuf.util.Timestamps;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
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.protocol.NamespaceUtil;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
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.sysflag.MessageSysFlag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Converter {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerName.GRPC_LOGGER_NAME);
public static String getResourceNameWithNamespace(Resource resource) {
return NamespaceUtil.wrapNamespace(resource.getResourceNamespace(), resource.getName());
}
@@ -149,4 +172,94 @@ public class Converter {
}
return messageWithHeader.getProperties();
}
public static String buildExpressionType(FilterType filterType) {
switch (filterType) {
case SQL:
return ExpressionType.SQL92;
case TAG:
default:
return ExpressionType.TAG;
}
}
public static HeartbeatData buildHeartbeatData(HeartbeatRequest request) {
HeartbeatData heartbeatData = new HeartbeatData();
heartbeatData.setClientID(request.getClientId());
Set<org.apache.rocketmq.common.protocol.heartbeat.ProducerData> producerDataSet = new HashSet<>();
producerDataSet.add(buildProducerData(request.getProducerData()));
heartbeatData.setProducerDataSet(producerDataSet);
Set<org.apache.rocketmq.common.protocol.heartbeat.ConsumerData> consumerDataSet = new HashSet<>();
consumerDataSet.add(buildConsumerData(request.getConsumerData()));
heartbeatData.setConsumerDataSet(consumerDataSet);
return heartbeatData;
}
public static org.apache.rocketmq.common.protocol.heartbeat.ProducerData buildProducerData(ProducerData producerData) {
org.apache.rocketmq.common.protocol.heartbeat.ProducerData buildProducerData = new org.apache.rocketmq.common.protocol.heartbeat.ProducerData();
buildProducerData.setGroupName(getResourceNameWithNamespace(producerData.getGroup()));
return buildProducerData;
}
public static org.apache.rocketmq.common.protocol.heartbeat.ConsumerData buildConsumerData(ConsumerData consumerData) {
org.apache.rocketmq.common.protocol.heartbeat.ConsumerData buildConsumerData = new org.apache.rocketmq.common.protocol.heartbeat.ConsumerData();
buildConsumerData.setGroupName(getResourceNameWithNamespace(consumerData.getGroup()));
buildConsumerData.setConsumeType(buildConsumeType(consumerData.getConsumeType()));
buildConsumerData.setMessageModel(buildMessageModel(consumerData.getConsumeModel()));
buildConsumerData.setConsumeFromWhere(buildConsumeFromWhere(consumerData.getConsumePolicy()));
Set<SubscriptionData> subscriptionDataSet = buildSubscriptionDataSet(consumerData.getSubscriptionsList());
buildConsumerData.setSubscriptionDataSet(subscriptionDataSet);
return buildConsumerData;
}
public static ConsumeType buildConsumeType(ConsumeMessageType consumeMessageType) {
switch (consumeMessageType) {
case ACTIVE:
return ConsumeType.CONSUME_ACTIVELY;
case PASSIVE:
default:
return ConsumeType.CONSUME_PASSIVELY;
}
}
public static MessageModel buildMessageModel(ConsumeModel consumeModel) {
switch (consumeModel) {
case BROADCASTING:
return MessageModel.BROADCASTING;
case CLUSTERING:
default:
return MessageModel.CLUSTERING;
}
}
public static ConsumeFromWhere buildConsumeFromWhere(ConsumePolicy policy) {
switch (policy) {
case PLAYBACK:
return ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET;
case DISCARD:
return ConsumeFromWhere.CONSUME_FROM_MAX_OFFSET;
case TARGET_TIMESTAMP:
return ConsumeFromWhere.CONSUME_FROM_TIMESTAMP;
case RESUME:
default:
return ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET;
}
}
public static Set<SubscriptionData> buildSubscriptionDataSet(List<SubscriptionEntry> subscriptionEntryList) {
Set<SubscriptionData> subscriptionDataSet = new HashSet<>();
for (SubscriptionEntry sub : subscriptionEntryList) {
String topicName = Converter.getResourceNameWithNamespace(sub.getTopic());
FilterExpression filterExpression = sub.getExpression();
String expression = filterExpression.getExpression();
String expressionType = Converter.buildExpressionType(filterExpression.getType());
try {
SubscriptionData subscriptionData = FilterAPI.build(topicName, expression, expressionType);
subscriptionDataSet.add(subscriptionData);
} catch (Exception e) {
throw new IllegalArgumentException("Build subscription failed when apply heartbeat", e);
}
}
return subscriptionDataSet;
}
}
@@ -17,6 +17,7 @@
package org.apache.rocketmq.proxy.grpc.common;
import apache.rocketmq.v1.HeartbeatResponse;
import apache.rocketmq.v1.ResponseCommon;
import apache.rocketmq.v1.SendMessageResponse;
import com.google.rpc.Code;
@@ -49,6 +50,12 @@ public class ResponseBuilder {
.build();
}
public static HeartbeatResponse buildHeartbeatResponse(RemotingCommand command) {
return HeartbeatResponse.newBuilder()
.setCommon(buildCommon(command.getCode(), command.getRemark()))
.build();
}
public static SendMessageResponse buildSendMessageResponse(RemotingCommand command) {
SendMessageResponseHeader responseHeader = (SendMessageResponseHeader) command.readCustomHeader();
String messageId = "";
@@ -59,17 +59,22 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.common.MQVersion;
import org.apache.rocketmq.common.ThreadFactoryImpl;
import org.apache.rocketmq.common.constant.LoggerName;
import org.apache.rocketmq.common.protocol.RequestCode;
import org.apache.rocketmq.common.protocol.header.SendMessageRequestHeader;
import org.apache.rocketmq.proxy.grpc.adapter.InvocationContext;
import org.apache.rocketmq.common.protocol.heartbeat.HeartbeatData;
import org.apache.rocketmq.proxy.channel.ChannelManager;
import org.apache.rocketmq.proxy.grpc.adapter.channel.SendMessageChannel;
import org.apache.rocketmq.proxy.channel.SimpleChannel;
import org.apache.rocketmq.proxy.channel.SimpleChannelHandlerContext;
import org.apache.rocketmq.proxy.grpc.adapter.InvocationContext;
import org.apache.rocketmq.proxy.grpc.adapter.channel.SendMessageChannel;
import org.apache.rocketmq.proxy.grpc.adapter.handler.SendMessageResponseHandler;
import org.apache.rocketmq.proxy.grpc.common.Converter;
import org.apache.rocketmq.proxy.grpc.common.InterceptorConstants;
import org.apache.rocketmq.proxy.grpc.common.ResponseBuilder;
import org.apache.rocketmq.remoting.protocol.LanguageCode;
import org.apache.rocketmq.remoting.protocol.RemotingCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -80,11 +85,11 @@ public class LocalGrpcService implements GrpcForwardService {
private final BrokerController brokerController;
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryImpl("LocalGrpcServiceScheduledThread"));
private final ChannelManager sendChannelManager;
private final ChannelManager channelManager;
public LocalGrpcService(BrokerController brokerController) {
this.brokerController = brokerController;
this.sendChannelManager = new ChannelManager();
this.channelManager = new ChannelManager();
}
@Override public CompletableFuture<QueryRouteResponse> queryRoute(Context ctx, QueryRouteRequest request) {
@@ -92,8 +97,23 @@ public class LocalGrpcService implements GrpcForwardService {
}
@Override public CompletableFuture<HeartbeatResponse> heartbeat(Context ctx, HeartbeatRequest request) {
LanguageCode languageCode;
String language = InterceptorConstants.METADATA.get(Context.current()).get(InterceptorConstants.LANGUAGE);
languageCode = LanguageCode.valueOf(language);
HeartbeatData heartbeatData = Converter.buildHeartbeatData(request);
return null;
SimpleChannel channel = channelManager.createChannel();
SimpleChannelHandlerContext simpleChannelHandlerContext = new SimpleChannelHandlerContext(channel);
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();
RemotingCommand response = this.brokerController.getClientManageProcessor()
.heartBeat(simpleChannelHandlerContext, command);
HeartbeatResponse heartbeatResponse = ResponseBuilder.buildHeartbeatResponse(response);
return CompletableFuture.completedFuture(heartbeatResponse);
}
@Override public CompletableFuture<HealthCheckResponse> healthCheck(Context ctx, HealthCheckRequest request) {
@@ -113,7 +133,7 @@ public class LocalGrpcService implements GrpcForwardService {
command.makeCustomHeaderToNet();
SendMessageResponseHandler handler = new SendMessageResponseHandler(message.getSystemAttribute().getMessageId());
SendMessageChannel channel = SendMessageChannel.create(sendChannelManager.createChannel(), handler);
SendMessageChannel channel = SendMessageChannel.create(channelManager.createChannel(), handler);
SimpleChannelHandlerContext channelHandlerContext = new SimpleChannelHandlerContext(channel);
CompletableFuture<SendMessageResponse> future = new CompletableFuture<>();
InvocationContext<SendMessageRequest, SendMessageResponse> context
@@ -205,6 +225,6 @@ public class LocalGrpcService implements GrpcForwardService {
}
private void scanAndCleanChannels() {
this.sendChannelManager.scanAndCleanChannels();
this.channelManager.scanAndCleanChannels();
}
}
@@ -17,6 +17,8 @@
package org.apache.rocketmq.proxy.grpc.service;
import apache.rocketmq.v1.HeartbeatRequest;
import apache.rocketmq.v1.HeartbeatResponse;
import apache.rocketmq.v1.Message;
import apache.rocketmq.v1.SendMessageRequest;
import apache.rocketmq.v1.SendMessageResponse;
@@ -27,6 +29,7 @@ import io.grpc.Metadata;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.CompletableFuture;
import org.apache.rocketmq.broker.BrokerController;
import org.apache.rocketmq.broker.processor.ClientManageProcessor;
import org.apache.rocketmq.broker.processor.SendMessageProcessor;
import org.apache.rocketmq.common.protocol.ResponseCode;
import org.apache.rocketmq.proxy.configuration.InitConfigurationTest;
@@ -46,14 +49,48 @@ import static org.assertj.core.api.Assertions.assertThat;
public class LocalGrpcServiceTest extends InitConfigurationTest {
private LocalGrpcService localGrpcService;
@Mock
SendMessageProcessor sendMessageProcessorMock;
private SendMessageProcessor sendMessageProcessorMock;
@Mock
private BrokerController brokerControllerMock;
private Metadata metadata;
@Before
public void setUp() throws Exception {
super.before();
BrokerController brokerControllerMock = Mockito.mock(BrokerController.class);
String mockProxyHome = "/mock/rmq/proxy/home";
URL mockProxyHomeURL = getClass().getClassLoader().getResource("rmq-proxy-home");
if (mockProxyHomeURL != null) {
mockProxyHome = mockProxyHomeURL.toURI().getPath();
}
System.setProperty(RMQ_PROXY_HOME, mockProxyHome);
ConfigurationManager.initEnv();
ConfigurationManager.intConfig();
ConfigurationManager.initEnv();
ConfigurationManager.intConfig();
Mockito.when(brokerControllerMock.getSendMessageProcessor()).thenReturn(sendMessageProcessorMock);
localGrpcService = new LocalGrpcService(brokerControllerMock);
metadata = new Metadata();
metadata.put(InterceptorConstants.REMOTE_ADDRESS, "1.1.1.1");
metadata.put(InterceptorConstants.LOCAL_ADDRESS, "0.0.0.0");
metadata.put(InterceptorConstants.LANGUAGE, "JAVA");
}
@Test
public void testHeartbeat() {
RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.SUCCESS, null);
ClientManageProcessor clientManageProcessorMock = Mockito.mock(ClientManageProcessor.class);
Mockito.when(clientManageProcessorMock.heartBeat(Mockito.any(ChannelHandlerContext.class), Mockito.any(RemotingCommand.class)))
.thenReturn(response);
Mockito.when(brokerControllerMock.getClientManageProcessor()).thenReturn(clientManageProcessorMock);
HeartbeatRequest request = HeartbeatRequest.newBuilder().getDefaultInstanceForType();
CompletableFuture<HeartbeatResponse> grpcFuture = localGrpcService.heartbeat(
Context.current().withValue(InterceptorConstants.METADATA, metadata).attach(), request);
grpcFuture.thenAccept(r -> {
assertThat(r.getCommon().getStatus().getCode())
.isEqualTo(Code.OK.getNumber());
assertThat(r.getCommon().getStatus().getMessage())
.isEqualTo(null);
});
}
@Test
@@ -70,9 +107,6 @@ public class LocalGrpcServiceTest extends InitConfigurationTest {
.build())
.build())
.build();
Metadata metadata = new Metadata();
metadata.put(InterceptorConstants.REMOTE_ADDRESS, "1.1.1.1");
metadata.put(InterceptorConstants.LOCAL_ADDRESS, "0.0.0.0");
CompletableFuture<SendMessageResponse> grpcFuture = localGrpcService.sendMessage(
Context.current().withValue(InterceptorConstants.METADATA, metadata).attach(), request);