Browse Source

Implementation of Queue Interfaces

pull/2566/head
Andrii Shvaika 7 years ago
parent
commit
23c017567b
  1. 21
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  2. 14
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  3. 2
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 83
      application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
  5. 27
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java
  6. 230
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  7. 135
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
  8. 29
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java
  9. 31
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java
  10. 35
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java
  11. 29
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java
  12. 27
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java
  13. 30
      application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java
  14. 21
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  15. 15
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  16. 4
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  17. 213
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  18. 3
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  19. 55
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java
  20. 33
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java
  21. 28
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java
  22. 67
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java
  23. 47
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java
  24. 24
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java
  25. 330
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
  26. 35
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java
  27. 153
      application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java
  28. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  29. 40
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  30. 59
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
  31. 29
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
  32. 91
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  33. 38
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  34. 25
      application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java
  35. 9
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java
  36. 237
      application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
  37. 3
      application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java
  38. 31
      application/src/main/java/org/thingsboard/server/service/transport/TransportApiRequestDecoder.java
  39. 30
      application/src/main/java/org/thingsboard/server/service/transport/TransportApiResponseEncoder.java
  40. 3
      application/src/main/resources/thingsboard.yml
  41. 71
      application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java
  42. 10
      common/queue/src/main/java/org/thingsboard/server/TbQueueProducer.java
  43. 5
      common/queue/src/main/java/org/thingsboard/server/common/DefaultTbQueueRequestTemplate.java
  44. 3
      common/queue/src/main/java/org/thingsboard/server/common/DefaultTbQueueResponseTemplate.java
  45. 18
      common/queue/src/main/java/org/thingsboard/server/discovery/ConsistentHashCircle.java
  46. 221
      common/queue/src/main/java/org/thingsboard/server/discovery/ConsistentHashPartitionService.java
  47. 11
      common/queue/src/main/java/org/thingsboard/server/discovery/DefaultTbServiceInfoProvider.java
  48. 32
      common/queue/src/main/java/org/thingsboard/server/discovery/DummyDiscoveryService.java
  49. 9
      common/queue/src/main/java/org/thingsboard/server/discovery/PartitionDiscoveryService.java
  50. 16
      common/queue/src/main/java/org/thingsboard/server/discovery/PartitionService.java
  51. 23
      common/queue/src/main/java/org/thingsboard/server/discovery/TopicPartitionInfo.java
  52. 122
      common/queue/src/main/java/org/thingsboard/server/discovery/ZkPartitionDiscoveryService.java
  53. 4
      common/queue/src/main/java/org/thingsboard/server/environment/EnvironmentLogService.java
  54. 50
      common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
  55. 17
      common/queue/src/main/java/org/thingsboard/server/memory/InMemoryTbQueueProducer.java
  56. 12
      common/queue/src/main/java/org/thingsboard/server/provider/InMemoryMonolithQueueProvider.java
  57. 119
      common/queue/src/main/java/org/thingsboard/server/provider/KafkaMonolithQueueProvider.java
  58. 33
      common/queue/src/main/java/org/thingsboard/server/provider/KafkaTbCoreQueueProvider.java
  59. 86
      common/queue/src/main/java/org/thingsboard/server/provider/KafkaTbRuleEngineQueueProvider.java
  60. 8
      common/queue/src/main/java/org/thingsboard/server/provider/KafkaTransportQueueProvider.java
  61. 61
      common/queue/src/main/java/org/thingsboard/server/provider/TbRuleEngineQueueProvider.java
  62. 4
      common/transport/transport-api/pom.xml
  63. 38
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

21
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -65,8 +65,6 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.kafka.TbNodeIdProvider; import org.thingsboard.server.kafka.TbNodeIdProvider;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService;
@ -108,19 +106,11 @@ public class ActorSystemContext {
@Setter @Setter
private ActorService actorService; private ActorService actorService;
@Autowired
@Getter
private DiscoveryService discoveryService;
@Autowired @Autowired
@Getter @Getter
@Setter @Setter
private ComponentDiscoveryService componentService; private ComponentDiscoveryService componentService;
@Autowired
@Getter
private ClusterRoutingService routingService;
@Autowired @Autowired
@Getter @Getter
private DataDecodingEncodingService encodingService; private DataDecodingEncodingService encodingService;
@ -368,7 +358,8 @@ public class ActorSystemContext {
event.setTenantId(tenantId); event.setTenantId(tenantId);
event.setEntityId(entityId); event.setEntityId(entityId);
event.setType(DataConstants.ERROR); event.setType(DataConstants.ERROR);
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e))); //TODO 2.5
// event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), method, toString(e)));
persistEvent(event); persistEvent(event);
} }
@ -377,7 +368,8 @@ public class ActorSystemContext {
event.setTenantId(tenantId); event.setTenantId(tenantId);
event.setEntityId(entityId); event.setEntityId(entityId);
event.setType(DataConstants.LC_EVENT); event.setType(DataConstants.LC_EVENT);
event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e))); //TODO 2.5
// event.setBody(toBodyJson(discoveryService.getCurrentServer().getServerAddress(), lcEvent, Optional.ofNullable(e)));
persistEvent(event); persistEvent(event);
} }
@ -406,8 +398,11 @@ public class ActorSystemContext {
return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body); return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
} }
public String getServerAddress() { public String getServerAddress() {
return discoveryService.getCurrentServer().getServerAddress().toString(); //TODO 2.5
// return discoveryService.getCurrentServer().getServerAddress().toString();
return null;
} }
public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) { public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, String relationType) {

14
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -134,13 +134,15 @@ public class AppActor extends RuleChainManagerActor {
} }
private void onPossibleClusterMsg(SendToClusterMsg msg) { private void onPossibleClusterMsg(SendToClusterMsg msg) {
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId()); //TODO 2.5
if (address.isPresent()) { // Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(msg.getEntityId());
systemContext.getRpcService().tell( // if (address.isPresent()) {
systemContext.getEncodingService().convertToProtoDataMessage(address.get(), msg.getMsg()));
} else { // systemContext.getRpcService().tell(
// systemContext.getEncodingService().convertToProtoDataMessage(address.get(), msg.getMsg()));
// } else {
self().tell(msg.getMsg(), ActorRef.noSender()); self().tell(msg.getMsg(), ActorRef.noSender());
} // }
} }
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {

2
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -260,7 +260,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
} }
//TODO: 2.5 move this as a notification to the queue; //TODO 2.5 move this as a notification to the queue;
private void reportLogicalDeviceActivity() { private void reportLogicalDeviceActivity() {
systemContext.getDeviceStateService().onDeviceActivity(deviceId); systemContext.getDeviceStateService().onDeviceActivity(deviceId);
} }

83
application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java

@ -1,83 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class BasicRpcSessionListener implements GrpcSessionListener {
private final ClusterRpcCallbackExecutorService callbackExecutorService;
private final ActorService service;
private final ActorRef manager;
private final ActorRef self;
BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) {
this.service = context.getActorService();
this.callbackExecutorService = context.getClusterRpcCallbackExecutor();
this.manager = manager;
this.self = self;
}
@Override
public void onConnected(GrpcSession session) {
log.info("[{}][{}] session started", session.getRemoteServer(), getType(session));
if (!session.isClient()) {
manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self);
}
}
@Override
public void onDisconnected(GrpcSession session) {
log.info("[{}][{}] session closed", session.getRemoteServer(), getType(session));
manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self);
}
@Override
public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) {
log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage);
callbackExecutorService.execute(() -> {
try {
service.onReceivedMsg(session.getRemoteServer(), clusterMessage);
} catch (Exception e) {
log.debug("[{}][{}] Failed to process cluster message: {}", session.getRemoteServer(), getType(session), clusterMessage, e);
}
});
}
@Override
public void onError(GrpcSession session, Throwable t) {
log.warn("[{}][{}] session got error -> {}", session.getRemoteServer(), getType(session), t);
manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self);
session.close();
}
private static String getType(GrpcSession session) {
return session.isClient() ? "Client" : "Server";
}
}

27
application/src/main/java/org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcBroadcastMsg {
private final ClusterAPIProtos.ClusterMessage msg;
}

230
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -1,230 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import akka.actor.ActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import scala.concurrent.duration.Duration;
import java.util.*;
/**
* @author Andrew Shvayka
*/
public class RpcManagerActor extends ContextAwareActor {
private final Map<ServerAddress, SessionActorInfo> sessionActors;
private final Map<ServerAddress, Queue<ClusterAPIProtos.ClusterMessage>> pendingMsgs;
private final ServerAddress instance;
private RpcManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>();
this.pendingMsgs = new HashMap<>();
this.instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
systemContext.getDiscoveryService().getOtherServers().stream()
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0)
.forEach(otherServer -> onCreateSessionRequest(
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null)));
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) {
if (msg instanceof ClusterAPIProtos.ClusterMessage) {
onMsg((ClusterAPIProtos.ClusterMessage) msg);
} else if (msg instanceof RpcBroadcastMsg) {
onMsg((RpcBroadcastMsg) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
onCreateSessionRequest((RpcSessionCreateRequestMsg) msg);
} else if (msg instanceof RpcSessionConnectedMsg) {
onSessionConnected((RpcSessionConnectedMsg) msg);
} else if (msg instanceof RpcSessionDisconnectedMsg) {
onSessionDisconnected((RpcSessionDisconnectedMsg) msg);
} else if (msg instanceof RpcSessionClosedMsg) {
onSessionClosed((RpcSessionClosedMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEvent((ClusterEventMsg) msg);
}
}
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
sessionActors.keySet().forEach(address -> {
ClusterAPIProtos.ClusterMessage msgWithServerAddress = msg.getMsg()
.toBuilder()
.setServerAddress(ClusterAPIProtos.ServerAddress
.newBuilder()
.setHost(address.getHost())
.setPort(address.getPort())
.build())
.build();
onMsg(msgWithServerAddress);
});
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}
private void onMsg(ClusterAPIProtos.ClusterMessage msg) {
if (msg.hasServerAddress()) {
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE);
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
log.debug("{} Forwarding msg to session actor: {}", address, msg);
session.getActor().tell(msg, ActorRef.noSender());
} else {
log.debug("{} Storing msg to pending queue: {}", address, msg);
Queue<ClusterAPIProtos.ClusterMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
pendingMsgs.put(new ServerAddress(
msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE), queue);
}
queue.add(msg);
}
} else {
log.warn("Cluster msg doesn't have server address [{}]", msg);
}
}
@Override
public void postStop() {
sessionActors.clear();
pendingMsgs.clear();
}
private void onClusterEvent(ClusterEventMsg msg) {
ServerAddress server = msg.getServerAddress();
if (server.compareTo(instance) > 0) {
if (msg.isAdded()) {
onCreateSessionRequest(new RpcSessionCreateRequestMsg(UUID.randomUUID(), server, null));
} else {
onSessionClose(false, server);
}
}
}
private void onSessionConnected(RpcSessionConnectedMsg msg) {
register(msg.getRemoteAddress(), msg.getId(), context().sender());
}
private void onSessionDisconnected(RpcSessionDisconnectedMsg msg) {
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
onSessionClose(reconnect, msg.getRemoteAddress());
}
private void onSessionClosed(RpcSessionClosedMsg msg) {
boolean reconnect = msg.isClient() && isRegistered(msg.getRemoteAddress());
onSessionClose(reconnect, msg.getRemoteAddress());
}
private boolean isRegistered(ServerAddress address) {
for (ServerInstance server : systemContext.getDiscoveryService().getOtherServers()) {
if (server.getServerAddress().equals(address)) {
return true;
}
}
return false;
}
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) {
context().stop(sessionRef.actor);
sessionActors.remove(remoteAddress);
pendingMsgs.remove(remoteAddress);
if (reconnect) {
onCreateSessionRequest(new RpcSessionCreateRequestMsg(sessionRef.sessionId, remoteAddress, null));
}
}
}
private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) {
if (msg.getRemoteAddress() != null) {
if (!sessionActors.containsKey(msg.getRemoteAddress())) {
ActorRef actorRef = createSessionActor(msg);
register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef);
}
} else {
createSessionActor(msg);
}
}
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) {
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender));
log.info("[{}][{}] Registering session actor.", remoteAddress, uuid);
Queue<ClusterAPIProtos.ClusterMessage> data = pendingMsgs.remove(remoteAddress);
if (data != null) {
log.info("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(msg), ActorRef.noSender()));
} else {
log.info("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
}
}
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) {
log.info("[{}] Creating session actor.", msg.getMsgUid());
ActorRef actor = context().actorOf(
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid()))
.withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME));
actor.tell(msg, context().self());
return actor;
}
public static class ActorCreator extends ContextBasedCreator<RpcManagerActor> {
private static final long serialVersionUID = 1L;
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public RpcManagerActor create() {
return new RpcManagerActor(context);
}
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
log.warn("Unknown failure", t);
return SupervisorStrategy.resume();
});
}

135
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java

@ -1,135 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
import org.thingsboard.server.service.cluster.rpc.GrpcSession;
import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
import java.util.UUID;
import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE;
/**
* @author Andrew Shvayka
*/
@Slf4j
public class RpcSessionActor extends ContextAwareActor {
private final UUID sessionId;
private GrpcSession session;
private GrpcSessionListener listener;
private RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) {
super(systemContext);
this.sessionId = sessionId;
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) {
if (msg instanceof ClusterAPIProtos.ClusterMessage) {
tell((ClusterAPIProtos.ClusterMessage) msg);
} else if (msg instanceof RpcSessionCreateRequestMsg) {
initSession((RpcSessionCreateRequestMsg) msg);
}
}
private void tell(ClusterAPIProtos.ClusterMessage msg) {
if (session != null) {
session.sendMsg(msg);
} else {
log.trace("Failed to send message due to missing session!");
}
}
@Override
public void postStop() {
if (session != null) {
log.info("Closing session -> {}", session.getRemoteServer());
try {
session.close();
} catch (RuntimeException e) {
log.trace("Failed to close session!", e);
}
}
}
private void initSession(RpcSessionCreateRequestMsg msg) {
log.info("[{}] Initializing session", context().self());
ServerAddress remoteServer = msg.getRemoteAddress();
listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self());
if (msg.getRemoteAddress() == null) {
// Server session
session = new GrpcSession(listener);
session.setOutputStream(msg.getResponseObserver());
session.initInputStream();
session.initOutputStream();
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream());
} else {
// Client session
ManagedChannel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext().build();
session = new GrpcSession(remoteServer, listener, channel);
session.initInputStream();
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);
StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream = stub.handleMsgs(session.getInputStream());
session.setOutputStream(outputStream);
session.initOutputStream();
outputStream.onNext(toConnectMsg());
}
}
public static class ActorCreator extends ContextBasedCreator<RpcSessionActor> {
private static final long serialVersionUID = 1L;
private final UUID sessionId;
public ActorCreator(ActorSystemContext context, UUID sessionId) {
super(context);
this.sessionId = sessionId;
}
@Override
public RpcSessionActor create() {
return new RpcSessionActor(context, sessionId);
}
}
private ClusterAPIProtos.ClusterMessage toConnectMsg() {
ServerAddress instance = systemContext.getDiscoveryService().getCurrentServer().getServerAddress();
return ClusterAPIProtos.ClusterMessage.newBuilder().setMessageType(CONNECT_RPC_MESSAGE).setServerAddress(
ClusterAPIProtos.ServerAddress.newBuilder().setHost(instance.getHost())
.setPort(instance.getPort()).build()).build();
}
}

29
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java

@ -1,29 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionClosedMsg {
private final boolean client;
private final ServerAddress remoteAddress;
}

31
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java

@ -1,31 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionConnectedMsg {
private final ServerAddress remoteAddress;
private final UUID id;
}

35
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionCreateRequestMsg.java

@ -1,35 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionCreateRequestMsg {
private final UUID msgUid;
private final ServerAddress remoteAddress;
private final StreamObserver<ClusterAPIProtos.ClusterMessage> responseObserver;
}

29
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionDisconnectedMsg.java

@ -1,29 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionDisconnectedMsg {
private final boolean client;
private final ServerAddress remoteAddress;
}

27
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import lombok.Data;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
/**
* @author Andrew Shvayka
*/
@Data
public final class RpcSessionTellMsg {
private final ClusterAPIProtos.ClusterMessage msg;
}

30
application/src/main/java/org/thingsboard/server/actors/rpc/SessionActorInfo.java

@ -1,30 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rpc;
import akka.actor.ActorRef;
import lombok.Data;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@Data
public final class SessionActorInfo {
protected final UUID sessionId;
protected final ActorRef actor;
}

21
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -121,8 +121,10 @@ class DefaultTbContext implements TbContext {
@Override @Override
public boolean isLocalEntity(EntityId entityId) { public boolean isLocalEntity(EntityId entityId) {
Optional<ServerAddress> address = mainCtx.getRoutingService().resolveById(entityId); //TODO 2.5
return !address.isPresent(); // Optional<ServerAddress> address = mainCtx.getRoutingService().resolveById(entityId);
// return !address.isPresent();
return true;
} }
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) { private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
@ -353,13 +355,14 @@ class DefaultTbContext implements TbContext {
src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody())); src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
mainCtx.getDeviceRpcService().forwardServerSideRPCRequestToDeviceActor(request, response -> { mainCtx.getDeviceRpcService().forwardServerSideRPCRequestToDeviceActor(request, response -> {
if (src.isRestApiCall()) { if (src.isRestApiCall()) {
ServerAddress requestOriginAddress; //TODO 2.5
if (!StringUtils.isEmpty(src.getOriginHost())) { // ServerAddress requestOriginAddress;
requestOriginAddress = new ServerAddress(src.getOriginHost(), src.getOriginPort(), ServerType.CORE); // if (!StringUtils.isEmpty(src.getOriginHost())) {
} else { // requestOriginAddress = new ServerAddress(src.getOriginHost(), src.getOriginPort(), ServerType.CORE);
requestOriginAddress = mainCtx.getRoutingService().getCurrentServer(); // } else {
} // requestOriginAddress = mainCtx.getRoutingService().getCurrentServer();
mainCtx.getDeviceRpcService().processResponseToServerSideRPCRequestFromRuleEngine(requestOriginAddress, response); // }
// mainCtx.getDeviceRpcService().processResponseToServerSideRPCRequestFromRuleEngine(requestOriginAddress, response);
} }
consumer.accept(RuleEngineDeviceRpcResponse.builder() consumer.accept(RuleEngineDeviceRpcResponse.builder()
.deviceId(src.getDeviceId()) .deviceId(src.getDeviceId())

15
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -230,20 +230,21 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
checkActive(); checkActive();
TbMsg msg = envelope.getMsg(); TbMsg msg = envelope.getMsg();
EntityId originatorEntityId = msg.getOriginator(); EntityId originatorEntityId = msg.getOriginator();
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(originatorEntityId); //TODO 2.5
// Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(originatorEntityId);
if (address.isPresent()) { // if (address.isPresent()) {
onRemoteTellNext(address.get(), envelope); // onRemoteTellNext(address.get(), envelope);
} else { // } else {
onLocalTellNext(envelope); onLocalTellNext(envelope);
} // }
} }
private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) { private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) {
TbMsg msg = envelope.getMsg(); TbMsg msg = envelope.getMsg();
log.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator()); log.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator());
envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId); envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId);
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope)); //TODO 2.5
// systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope));
} }
private void onLocalTellNext(RuleNodeToRuleChainTellNextMsg envelope) { private void onLocalTellNext(RuleNodeToRuleChainTellNextMsg envelope) {

4
application/src/main/java/org/thingsboard/server/actors/service/ActorService.java

@ -21,10 +21,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
public interface ActorService extends SessionMsgProcessor, RpcMsgListener, DiscoveryServiceListener { public interface ActorService extends SessionMsgProcessor {
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);

213
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -19,7 +19,6 @@ import akka.actor.ActorRef;
import akka.actor.ActorSystem; import akka.actor.ActorSystem;
import akka.actor.Props; import akka.actor.Props;
import akka.actor.Terminated; import akka.actor.Terminated;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -32,25 +31,16 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.app.AppActor; import org.thingsboard.server.actors.app.AppActor;
import org.thingsboard.server.actors.app.AppInitMsg; import org.thingsboard.server.actors.app.AppInitMsg;
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
import org.thingsboard.server.actors.rpc.RpcManagerActor;
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.stats.StatsActor; import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import scala.concurrent.Await; import scala.concurrent.Await;
import scala.concurrent.Future; import scala.concurrent.Future;
@ -60,8 +50,6 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE;
@Service @Service
@Slf4j @Slf4j
public class DefaultActorService implements ActorService { public class DefaultActorService implements ActorService {
@ -77,12 +65,6 @@ public class DefaultActorService implements ActorService {
@Autowired @Autowired
private ActorSystemContext actorContext; private ActorSystemContext actorContext;
@Autowired
private ClusterRpcService rpcService;
@Autowired
private DiscoveryService discoveryService;
@Autowired @Autowired
private DeviceStateService deviceStateService; private DeviceStateService deviceStateService;
@ -102,13 +84,9 @@ public class DefaultActorService implements ActorService {
appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor"); appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor");
actorContext.setAppActor(appActor); actorContext.setAppActor(appActor);
rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME),
"rpcManagerActor");
ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor"); ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor");
actorContext.setStatsActor(statsActor); actorContext.setStatsActor(statsActor);
rpcService.init(this);
log.info("Actor system initialized."); log.info("Actor system initialized.");
} }
@ -134,22 +112,23 @@ public class DefaultActorService implements ActorService {
appActor.tell(msg, ActorRef.noSender()); appActor.tell(msg, ActorRef.noSender());
} }
@Override //TODO 2.5
public void onServerAdded(ServerInstance server) { // @Override
log.trace("Processing onServerAdded msg: {}", server); // public void onServerAdded(ServerInstance server) {
broadcast(new ClusterEventMsg(server.getServerAddress(), true)); // log.trace("Processing onServerAdded msg: {}", server);
} // broadcast(new ClusterEventMsg(server.getServerAddress(), true));
// }
@Override //
public void onServerUpdated(ServerInstance server) { // @Override
//Do nothing // public void onServerUpdated(ServerInstance server) {
} // //Do nothing
// }
@Override //
public void onServerRemoved(ServerInstance server) { // @Override
log.trace("Processing onServerRemoved msg: {}", server); // public void onServerRemoved(ServerInstance server) {
broadcast(new ClusterEventMsg(server.getServerAddress(), false)); // log.trace("Processing onServerRemoved msg: {}", server);
} // broadcast(new ClusterEventMsg(server.getServerAddress(), false));
// }
@Override @Override
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
@ -172,12 +151,13 @@ public class DefaultActorService implements ActorService {
public void broadcast(ToAllNodesMsg msg) { public void broadcast(ToAllNodesMsg msg) {
actorContext.getEncodingService().encode(msg); actorContext.getEncodingService().encode(msg);
rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage //TODO 2.5
.newBuilder() // rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage
.setPayload(ByteString // .newBuilder()
.copyFrom(actorContext.getEncodingService().encode(msg))) // .setPayload(ByteString
.setMessageType(CLUSTER_ACTOR_MESSAGE) // .copyFrom(actorContext.getEncodingService().encode(msg)))
.build())); // .setMessageType(CLUSTER_ACTOR_MESSAGE)
// .build()));
appActor.tell(msg, ActorRef.noSender()); appActor.tell(msg, ActorRef.noSender());
} }
@ -204,79 +184,78 @@ public class DefaultActorService implements ActorService {
} }
} }
@Override //TODO 2.5
public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) { // @Override
if (statsEnabled) { // public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) {
receivedClusterMsgs.incrementAndGet(); // if (statsEnabled) {
} // receivedClusterMsgs.incrementAndGet();
ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort(), source.getServerType()); // }
if (log.isDebugEnabled()) { // ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort(), source.getServerType());
log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress); // if (log.isDebugEnabled()) {
log.info("MSG: {}", msg); // log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
} // log.info("MSG: {}", msg);
switch (msg.getMessageType()) { // }
case CLUSTER_ACTOR_MESSAGE: // switch (msg.getMessageType()) {
java.util.Optional<TbActorMsg> decodedMsg = actorContext.getEncodingService() // case CLUSTER_ACTOR_MESSAGE:
.decode(msg.getPayload().toByteArray()); // java.util.Optional<TbActorMsg> decodedMsg = actorContext.getEncodingService()
if (decodedMsg.isPresent()) { // .decode(msg.getPayload().toByteArray());
appActor.tell(decodedMsg.get(), ActorRef.noSender()); // if (decodedMsg.isPresent()) {
} else { // appActor.tell(decodedMsg.get(), ActorRef.noSender());
log.error("Error during decoding cluster proto message"); // } else {
} // log.error("Error during decoding cluster proto message");
break; // }
case TO_ALL_NODES_MSG: // break;
//TODO // case TO_ALL_NODES_MSG:
break; // //TODO
case CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE: // break;
actorContext.getTsSubService().onNewRemoteSubscription(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE:
break; // actorContext.getTsSubService().onNewRemoteSubscription(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE: // break;
actorContext.getTsSubService().onRemoteSubscriptionUpdate(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE:
break; // actorContext.getTsSubService().onRemoteSubscriptionUpdate(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE: // break;
actorContext.getTsSubService().onRemoteSubscriptionClose(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE:
break; // actorContext.getTsSubService().onRemoteSubscriptionClose(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE: // break;
actorContext.getTsSubService().onRemoteSessionClose(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE:
break; // actorContext.getTsSubService().onRemoteSessionClose(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE: // break;
actorContext.getTsSubService().onRemoteAttributesUpdate(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE:
break; // actorContext.getTsSubService().onRemoteAttributesUpdate(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE: // break;
actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE:
break; // actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE: // break;
actorContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromRemoteServer(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
break; // actorContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromRemoteServer(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE: // break;
actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE:
break; // actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray());
case CLUSTER_TRANSACTION_SERVICE_MESSAGE: // break;
actorContext.getRuleChainTransactionService().onRemoteTransactionMsg(serverAddress, msg.getPayload().toByteArray()); // case CLUSTER_TRANSACTION_SERVICE_MESSAGE:
break; // actorContext.getRuleChainTransactionService().onRemoteTransactionMsg(serverAddress, msg.getPayload().toByteArray());
} // break;
} // }
// }
@Override // @Override
public void onSendMsg(ClusterAPIProtos.ClusterMessage msg) { // public void onSendMsg(ClusterAPIProtos.ClusterMessage msg) {
if (statsEnabled) { // if (statsEnabled) {
sentClusterMsgs.incrementAndGet(); // sentClusterMsgs.incrementAndGet();
} // }
rpcManagerActor.tell(msg, ActorRef.noSender()); // rpcManagerActor.tell(msg, ActorRef.noSender());
} // }
//
@Override // @Override
public void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg) { // public void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg) {
if (statsEnabled) { // if (statsEnabled) {
sentClusterMsgs.incrementAndGet(); // sentClusterMsgs.incrementAndGet();
} // }
rpcManagerActor.tell(msg, ActorRef.noSender()); // rpcManagerActor.tell(msg, ActorRef.noSender());
} // }
// @Override
@Override // public void onBroadcastMsg(RpcBroadcastMsg msg) {
public void onBroadcastMsg(RpcBroadcastMsg msg) { // rpcManagerActor.tell(msg, ActorRef.noSender());
rpcManagerActor.tell(msg, ActorRef.noSender()); // }
}
@Override @Override
public void onDeviceAdded(Device device) { public void onDeviceAdded(Device device) {

3
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -58,7 +58,8 @@ public class StatsActor extends ContextAwareActor {
event.setEntityId(msg.getEntityId()); event.setEntityId(msg.getEntityId());
event.setTenantId(msg.getTenantId()); event.setTenantId(msg.getTenantId());
event.setType(DataConstants.STATS); event.setType(DataConstants.STATS);
event.setBody(toBodyJson(systemContext.getDiscoveryService().getCurrentServer().getServerAddress(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); //TODO 2.5
// event.setBody(toBodyJson(systemContext.getDiscoveryService().getCurrentServer().getServerAddress(), msg.getMessagesProcessed(), msg.getErrorsOccurred()));
systemContext.getEventService().save(event); systemContext.getEventService().save(event);
} }

55
application/src/main/java/org/thingsboard/server/service/cluster/discovery/CurrentServerInstanceService.java

@ -1,55 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import javax.annotation.PostConstruct;
import static org.thingsboard.server.utils.MiscUtils.missingProperty;
/**
* @author Andrew Shvayka
*/
@Service
@Slf4j
public class CurrentServerInstanceService implements ServerInstanceService {
@Value("${rpc.bind_host}")
private String rpcHost;
@Value("${rpc.bind_port}")
private Integer rpcPort;
private ServerInstance self;
@PostConstruct
public void init() {
Assert.hasLength(rpcHost, missingProperty("rpc.bind_host"));
Assert.notNull(rpcPort, missingProperty("rpc.bind_port"));
self = new ServerInstance(new ServerAddress(rpcHost, rpcPort, ServerType.CORE));
log.info("Current server instance: [{};{}]", self.getHost(), self.getPort());
}
@Override
public ServerInstance getSelf() {
return self;
}
}

33
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java

@ -1,33 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
import java.util.List;
/**
* @author Andrew Shvayka
*/
public interface DiscoveryService {
void publishCurrentServer();
void unpublishCurrentServer();
ServerInstance getCurrentServer();
List<ServerInstance> getOtherServers();
}

28
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryServiceListener.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
/**
* @author Andrew Shvayka
*/
public interface DiscoveryServiceListener {
void onServerAdded(ServerInstance server);
void onServerUpdated(ServerInstance server);
void onServerRemoved(ServerInstance server);
}

67
application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java

@ -1,67 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
/**
* @author Andrew Shvayka
*/
@Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true)
@Slf4j
@DependsOn("environmentLogService")
public class DummyDiscoveryService implements DiscoveryService {
@Autowired
private ServerInstanceService serverInstance;
@PostConstruct
public void init() {
log.info("Initializing...");
}
@Override
public void publishCurrentServer() {
//Do nothing
}
@Override
public void unpublishCurrentServer() {
//Do nothing
}
@Override
public ServerInstance getCurrentServer() {
return serverInstance.getSelf();
}
@Override
public List<ServerInstance> getOtherServers() {
return Collections.emptyList();
}
}

47
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstance.java

@ -1,47 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
/**
* @author Andrew Shvayka
*/
@ToString
@EqualsAndHashCode(exclude = {"serverInfo", "serverAddress"})
public final class ServerInstance implements Comparable<ServerInstance> {
@Getter
private final String host;
@Getter
private final int port;
@Getter
private final ServerAddress serverAddress;
public ServerInstance(ServerAddress serverAddress) {
this.serverAddress = serverAddress;
this.host = serverAddress.getHost();
this.port = serverAddress.getPort();
}
@Override
public int compareTo(ServerInstance o) {
return this.serverAddress.compareTo(o.serverAddress);
}
}

24
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ServerInstanceService.java

@ -1,24 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
/**
* @author Andrew Shvayka
*/
public interface ServerInstanceService {
ServerInstance getSelf();
}

330
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java

@ -1,330 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.discovery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.SerializationException;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.RetryForever;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
/**
* @author Andrew Shvayka
*/
@Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false)
@Slf4j
public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener {
@Value("${zk.url}")
private String zkUrl;
@Value("${zk.retry_interval_ms}")
private Integer zkRetryInterval;
@Value("${zk.connection_timeout_ms}")
private Integer zkConnectionTimeout;
@Value("${zk.session_timeout_ms}")
private Integer zkSessionTimeout;
@Value("${zk.zk_dir}")
private String zkDir;
private String zkNodesDir;
@Autowired
private ServerInstanceService serverInstance;
@Autowired
@Lazy
private TelemetrySubscriptionService tsSubService;
@Autowired
@Lazy
private DeviceStateService deviceStateService;
@Autowired
@Lazy
private ActorService actorService;
@Autowired
@Lazy
private ClusterRoutingService routingService;
private ExecutorService reconnectExecutorService;
private CuratorFramework client;
private PathChildrenCache cache;
private String nodePath;
private volatile boolean stopped = true;
@PostConstruct
public void init() {
log.info("Initializing...");
Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url"));
Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms"));
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
reconnectExecutorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("zk-discovery"));
log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
zkNodesDir = zkDir + "/nodes";
initZkClient();
}
private void initZkClient() {
try {
client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval));
client.start();
client.blockUntilConnected();
cache = new PathChildrenCache(client, zkNodesDir, true);
cache.getListenable().addListener(this);
cache.start();
stopped = false;
log.info("ZK client connected");
} catch (Exception e) {
log.error("Failed to connect to ZK: {}", e.getMessage(), e);
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
throw new RuntimeException(e);
}
}
private void destroyZkClient() {
stopped = true;
try {
unpublishCurrentServer();
} catch (Exception e) {}
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
log.info("ZK client disconnected");
}
@PreDestroy
public void destroy() {
destroyZkClient();
reconnectExecutorService.shutdownNow();
log.info("Stopped discovery service");
}
@Override
public synchronized void publishCurrentServer() {
ServerInstance self = this.serverInstance.getSelf();
if (currentServerExists()) {
log.info("[{}:{}] ZK node for current instance already exists, NOT created new one: {}", self.getHost(), self.getPort(), nodePath);
} else {
try {
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort());
nodePath = client.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress()));
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
client.getConnectionStateListenable().addListener(checkReconnect(self));
} catch (Exception e) {
log.error("Failed to create ZK node", e);
throw new RuntimeException(e);
}
}
}
private boolean currentServerExists() {
if (nodePath == null) {
return false;
}
try {
ServerInstance self = this.serverInstance.getSelf();
ServerAddress registeredServerAdress = null;
registeredServerAdress = SerializationUtils.deserialize(client.getData().forPath(nodePath));
if (self.getServerAddress() != null && self.getServerAddress().equals(registeredServerAdress)) {
return true;
}
} catch (KeeperException.NoNodeException e) {
log.info("ZK node does not exist: {}", nodePath);
} catch (Exception e) {
log.error("Couldn't check if ZK node exists", e);
}
return false;
}
private ConnectionStateListener checkReconnect(ServerInstance self) {
return (client, newState) -> {
log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
if (newState == ConnectionState.LOST) {
reconnectExecutorService.submit(this::reconnect);
}
};
}
private volatile boolean reconnectInProgress = false;
private synchronized void reconnect() {
if (!reconnectInProgress) {
reconnectInProgress = true;
try {
destroyZkClient();
initZkClient();
publishCurrentServer();
} catch (Exception e) {
log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
} finally {
reconnectInProgress = false;
}
}
}
@Override
public void unpublishCurrentServer() {
try {
if (nodePath != null) {
client.delete().forPath(nodePath);
}
} catch (Exception e) {
log.error("Failed to delete ZK node {}", nodePath, e);
throw new RuntimeException(e);
}
}
@Override
public ServerInstance getCurrentServer() {
return serverInstance.getSelf();
}
@Override
public List<ServerInstance> getOtherServers() {
return cache.getCurrentData().stream()
.filter(cd -> !cd.getPath().equals(nodePath))
.map(cd -> {
try {
return new ServerInstance((ServerAddress) SerializationUtils.deserialize(cd.getData()));
} catch (NoSuchElementException e) {
log.error("Failed to decode ZK node", e);
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
log.info("Received application ready event. Starting current ZK node.");
if (stopped) {
log.debug("Ignoring application ready event. Service is stopped.");
return;
}
if (client.getState() != CuratorFrameworkState.STARTED) {
log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState());
return;
}
publishCurrentServer();
getOtherServers().forEach(
server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort())
);
}
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
if (stopped) {
log.debug("Ignoring {}. Service is stopped.", pathChildrenCacheEvent);
return;
}
if (client.getState() != CuratorFrameworkState.STARTED) {
log.debug("Ignoring {}, ZK client is not started, ZK client state [{}]", pathChildrenCacheEvent, client.getState());
return;
}
ChildData data = pathChildrenCacheEvent.getData();
if (data == null) {
log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent);
return;
} else if (data.getData() == null) {
log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent);
return;
} else if (nodePath != null && nodePath.equals(data.getPath())) {
if (pathChildrenCacheEvent.getType() == CHILD_REMOVED) {
log.info("ZK node for current instance is somehow deleted.");
publishCurrentServer();
}
log.debug("Ignoring event about current server {}", pathChildrenCacheEvent);
return;
}
ServerInstance instance;
try {
ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
instance = new ServerInstance(serverAddress);
} catch (SerializationException e) {
log.error("Failed to decode server instance for node {}", data.getPath(), e);
throw e;
}
log.info("Processing [{}] event for [{}:{}]", pathChildrenCacheEvent.getType(), instance.getHost(), instance.getPort());
switch (pathChildrenCacheEvent.getType()) {
case CHILD_ADDED:
routingService.onServerAdded(instance);
tsSubService.onClusterUpdate();
deviceStateService.onClusterUpdate();
actorService.onServerAdded(instance);
break;
case CHILD_UPDATED:
routingService.onServerUpdated(instance);
actorService.onServerUpdated(instance);
break;
case CHILD_REMOVED:
routingService.onServerRemoved(instance);
tsSubService.onClusterUpdate();
deviceStateService.onClusterUpdate();
actorService.onServerRemoved(instance);
break;
default:
break;
}
}
}

35
application/src/main/java/org/thingsboard/server/service/cluster/routing/ClusterRoutingService.java

@ -1,35 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.routing;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import java.util.Optional;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
public interface ClusterRoutingService extends DiscoveryServiceListener {
ServerAddress getCurrentServer();
Optional<ServerAddress> resolveById(EntityId entityId);
}

153
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingService.java

@ -1,153 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cluster.routing;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import org.thingsboard.server.utils.MiscUtils;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
/**
* Cluster service implementation based on consistent hash ring
*/
@Service
@Slf4j
public class ConsistentClusterRoutingService implements ClusterRoutingService {
@Autowired
private DiscoveryService discoveryService;
@Value("${cluster.hash_function_name}")
private String hashFunctionName;
@Value("${cluster.vitrual_nodes_size}")
private Integer virtualNodesSize;
private ServerInstance currentServer;
private HashFunction hashFunction;
private ConsistentHashCircle[] circles;
private ConsistentHashCircle rootCircle;
@PostConstruct
public void init() {
log.info("Initializing Cluster routing service!");
this.hashFunction = MiscUtils.forName(hashFunctionName);
this.currentServer = discoveryService.getCurrentServer();
this.circles = new ConsistentHashCircle[ServerType.values().length];
for (ServerType serverType : ServerType.values()) {
circles[serverType.ordinal()] = new ConsistentHashCircle();
}
rootCircle = circles[ServerType.CORE.ordinal()];
addNode(discoveryService.getCurrentServer());
for (ServerInstance instance : discoveryService.getOtherServers()) {
addNode(instance);
}
logCircle();
log.info("Cluster routing service initialized!");
}
@Override
public ServerAddress getCurrentServer() {
return discoveryService.getCurrentServer().getServerAddress();
}
@Override
public Optional<ServerAddress> resolveById(EntityId entityId) {
return resolveByUuid(rootCircle, entityId.getId());
}
private Optional<ServerAddress> resolveByUuid(ConsistentHashCircle circle, UUID uuid) {
Assert.notNull(uuid);
if (circle.isEmpty()) {
return Optional.empty();
}
Long hash = hashFunction.newHasher().putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits()).hash().asLong();
if (!circle.containsKey(hash)) {
ConcurrentNavigableMap<Long, ServerInstance> tailMap =
circle.tailMap(hash);
hash = tailMap.isEmpty() ?
circle.firstKey() : tailMap.firstKey();
}
ServerInstance result = circle.get(hash);
if (!currentServer.equals(result)) {
return Optional.of(result.getServerAddress());
} else {
return Optional.empty();
}
}
@Override
public void onServerAdded(ServerInstance server) {
log.info("On server added event: {}", server);
addNode(server);
logCircle();
}
@Override
public void onServerUpdated(ServerInstance server) {
log.debug("Ignoring server onUpdate event: {}", server);
}
@Override
public void onServerRemoved(ServerInstance server) {
log.info("On server removed event: {}", server);
removeNode(server);
logCircle();
}
private void addNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circles[instance.getServerAddress().getServerType().ordinal()].put(hash(instance, i).asLong(), instance);
}
}
private void removeNode(ServerInstance instance) {
for (int i = 0; i < virtualNodesSize; i++) {
circles[instance.getServerAddress().getServerType().ordinal()].remove(hash(instance, i).asLong());
}
}
private HashCode hash(ServerInstance instance, int i) {
return hashFunction.newHasher().putString(instance.getHost(), MiscUtils.UTF8).putInt(instance.getPort()).putInt(i).hash();
}
private void logCircle() {
log.trace("Consistent Hash Circle Start");
Arrays.asList(circles).forEach(ConsistentHashCircle::log);
log.trace("Consistent Hash Circle End");
}
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -45,7 +45,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core')") @ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
@Slf4j @Slf4j
public class DefaultTbCoreConsumerService implements TbCoreConsumerService { public class DefaultTbCoreConsumerService implements TbCoreConsumerService {

40
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -29,6 +29,7 @@ import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.TbProtoQueueMsg; import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.provider.TbCoreQueueProvider; import org.thingsboard.server.provider.TbCoreQueueProvider;
import org.thingsboard.server.provider.TbRuleEngineQueueProvider;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -44,26 +45,26 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-rule-engine')") @ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-rule-engine'")
@Slf4j @Slf4j
public class DefaultTbRuleEngineConsumerService implements TbRuleEngineConsumerService { public class DefaultTbRuleEngineConsumerService implements TbRuleEngineConsumerService {
@Value("${queue.rule-engine.poll_interval}") @Value("${queue.rule_engine.poll_interval}")
private long pollDuration; private long pollDuration;
@Value("${queue.rule-engine.pack_processing_timeout}") @Value("${queue.rule_engine.pack_processing_timeout}")
private long packProcessingTimeout; private long packProcessingTimeout;
@Value("${queue.rule-engine.stats.enabled:false}") @Value("${queue.rule_engine.stats.enabled:false}")
private boolean statsEnabled; private boolean statsEnabled;
private final ActorSystemContext actorContext; private final ActorSystemContext actorContext;
private final TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToCoreMsg>> consumer; private final TbQueueConsumer<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> consumer;
private final TbCoreConsumerStats stats = new TbCoreConsumerStats(); private final TbCoreConsumerStats stats = new TbCoreConsumerStats();
private volatile ExecutorService mainConsumerExecutor; private volatile ExecutorService mainConsumerExecutor;
private volatile boolean stopped = false; private volatile boolean stopped = false;
public DefaultTbRuleEngineConsumerService(TbCoreQueueProvider tbCoreQueueProvider, ActorSystemContext actorContext) { public DefaultTbRuleEngineConsumerService(TbRuleEngineQueueProvider tbRuleEngineQueueProvider, ActorSystemContext actorContext) {
this.consumer = tbCoreQueueProvider.getToCoreMsgConsumer(); this.consumer = tbRuleEngineQueueProvider.getToRuleEngineMsgConsumer();
this.actorContext = actorContext; this.actorContext = actorContext;
} }
@ -78,17 +79,17 @@ public class DefaultTbRuleEngineConsumerService implements TbRuleEngineConsumerS
mainConsumerExecutor.execute(() -> { mainConsumerExecutor.execute(() -> {
while (!stopped) { while (!stopped) {
try { try {
List<TbProtoQueueMsg<TransportProtos.ToCoreMsg>> msgs = consumer.poll(pollDuration); List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs = consumer.poll(pollDuration);
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToCoreMsg>> ackMap = msgs.stream().collect( ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> ackMap = msgs.stream().collect(
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity()));
CountDownLatch processingTimeoutLatch = new CountDownLatch(1); CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
ackMap.forEach((id, msg) -> { ackMap.forEach((id, msg) -> {
TbMsgCallback callback = new MsgPackCallback<>(id, processingTimeoutLatch, ackMap); TbMsgCallback callback = new MsgPackCallback<>(id, processingTimeoutLatch, ackMap);
try { try {
TransportProtos.ToCoreMsg toCoreMsg = msg.getValue(); TransportProtos.ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
log.trace("Forwarding message to rule engine {}", toCoreMsg); log.trace("Forwarding message to rule engine {}", toRuleEngineMsg);
if (toCoreMsg.hasToDeviceActorMsg()) { if (toRuleEngineMsg.hasToRuleEngineMsg()) {
forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); forwardToRuleEngineActor(toRuleEngineMsg.getToRuleEngineMsg(), callback);
} else { } else {
callback.onSuccess(); callback.onSuccess();
} }
@ -112,11 +113,12 @@ public class DefaultTbRuleEngineConsumerService implements TbRuleEngineConsumerS
}); });
} }
private void forwardToDeviceActor(TransportProtos.TransportToDeviceActorMsg toDeviceActorMsg, TbMsgCallback callback) { //TODO 2.5
if (statsEnabled) { private void forwardToRuleEngineActor(TransportProtos.TransportToRuleEngineMsg toDeviceActorMsg, TbMsgCallback callback) {
stats.log(toDeviceActorMsg); // if (statsEnabled) {
} // stats.log(toDeviceActorMsg);
actorContext.getAppActor().tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback), ActorRef.noSender()); // }
// actorContext.getAppActor().tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback), ActorRef.noSender());
} }
@Scheduled(fixedDelayString = "${queue.core.stats.print_interval_ms}") @Scheduled(fixedDelayString = "${queue.core.stats.print_interval_ms}")

59
application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -24,60 +24,29 @@ import java.util.concurrent.atomic.AtomicInteger;
public class TbRuleEngineConsumerStats { public class TbRuleEngineConsumerStats {
private final AtomicInteger totalCounter = new AtomicInteger(0); private final AtomicInteger totalCounter = new AtomicInteger(0);
private final AtomicInteger sessionEventCounter = new AtomicInteger(0); private final AtomicInteger postTelemetryCounter = new AtomicInteger(0);
// private final AtomicInteger postTelemetryCounter = new AtomicInteger(0); private final AtomicInteger postAttributesCounter = new AtomicInteger(0);
// private final AtomicInteger postAttributesCounter = new AtomicInteger(0); private final AtomicInteger toServerRPCCallRequestCounter = new AtomicInteger(0);
private final AtomicInteger getAttributesCounter = new AtomicInteger(0);
private final AtomicInteger subscribeToAttributesCounter = new AtomicInteger(0);
private final AtomicInteger subscribeToRPCCounter = new AtomicInteger(0);
private final AtomicInteger toDeviceRPCCallResponseCounter = new AtomicInteger(0);
// private final AtomicInteger toServerRPCCallRequestCounter = new AtomicInteger(0);
private final AtomicInteger subscriptionInfoCounter = new AtomicInteger(0);
private final AtomicInteger claimDeviceCounter = new AtomicInteger(0);
public void log(TransportProtos.TransportToDeviceActorMsg msg) { public void log(TransportProtos.TransportToRuleEngineMsg msg) {
totalCounter.incrementAndGet(); totalCounter.incrementAndGet();
if (msg.hasSessionEvent()) { if (msg.hasPostTelemetry()) {
sessionEventCounter.incrementAndGet(); postTelemetryCounter.incrementAndGet();
} }
// if (msg.hasPostTelemetry()) { if (msg.hasPostAttributes()) {
// postTelemetryCounter.incrementAndGet(); postAttributesCounter.incrementAndGet();
// }
// if (msg.hasPostAttributes()) {
// postAttributesCounter.incrementAndGet();
// }
if (msg.hasGetAttributes()) {
getAttributesCounter.incrementAndGet();
} }
if (msg.hasSubscribeToAttributes()) { if (msg.hasToServerRPCCallRequest()) {
subscribeToAttributesCounter.incrementAndGet(); toServerRPCCallRequestCounter.incrementAndGet();
}
if (msg.hasSubscribeToRPC()) {
subscribeToRPCCounter.incrementAndGet();
}
if (msg.hasToDeviceRPCCallResponse()) {
toDeviceRPCCallResponseCounter.incrementAndGet();
}
// if (msg.hasToServerRPCCallRequest()) {
// toServerRPCCallRequestCounter.incrementAndGet();
// }
if (msg.hasSubscriptionInfo()) {
subscriptionInfoCounter.incrementAndGet();
}
if (msg.hasClaimDevice()) {
claimDeviceCounter.incrementAndGet();
} }
} }
public void printStats() { public void printStats() {
int total = totalCounter.getAndSet(0); int total = totalCounter.getAndSet(0);
if (total > 0) { if (total > 0) {
log.info("Transport total [{}] sessionEvents [{}] telemetry [{}] attributes [{}] getAttr [{}] subToAttr [{}] subToRpc [{}] toDevRpc [{}] " + log.info("Transport total [{}] telemetry [{}] attributes [{}] toServerRpc [{}]",
"toServerRpc [{}] subInfo [{}] claimDevice [{}] ", total, postTelemetryCounter.getAndSet(0),
total, sessionEventCounter.getAndSet(0), postTelemetryCounter.getAndSet(0), postAttributesCounter.getAndSet(0), toServerRPCCallRequestCounter.getAndSet(0));
postAttributesCounter.getAndSet(0), getAttributesCounter.getAndSet(0), subscribeToAttributesCounter.getAndSet(0),
subscribeToRPCCounter.getAndSet(0), toDeviceRPCCallResponseCounter.getAndSet(0),
toServerRPCCallRequestCounter.getAndSet(0), subscriptionInfoCounter.getAndSet(0), claimDeviceCounter.getAndSet(0));
} }
} }
} }

29
application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java

@ -42,8 +42,6 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -64,12 +62,6 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
private static final ObjectMapper json = new ObjectMapper(); private static final ObjectMapper json = new ObjectMapper();
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService rpcService;
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@ -106,7 +98,8 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
@Override @Override
public void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response) { public void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response) {
log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), requestOriginAddress); log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), requestOriginAddress);
if (routingService.getCurrentServer().equals(requestOriginAddress)) { //TODO 2.5
if (true) {//routingService.getCurrentServer().equals(requestOriginAddress)
UUID requestId = response.getId(); UUID requestId = response.getId();
Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId); Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId);
if (consumer != null) { if (consumer != null) {
@ -124,7 +117,8 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
} else { } else {
builder.setError(-1); builder.setError(-1);
} }
rpcService.tell(requestOriginAddress, ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// rpcService.tell(requestOriginAddress, ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray());
} }
} }
@ -159,7 +153,8 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
} }
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), proto.getResponse(), error); FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), proto.getResponse(), error);
processResponseToServerSideRPCRequestFromRuleEngine(routingService.getCurrentServer(), response); //TODO 2.5
// processResponseToServerSideRPCRequestFromRuleEngine(routingService.getCurrentServer(), response);
} }
@Override @Override
@ -172,8 +167,9 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
ObjectNode entityNode = json.createObjectNode(); ObjectNode entityNode = json.createObjectNode();
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("requestUUID", msg.getId().toString()); metaData.putValue("requestUUID", msg.getId().toString());
metaData.putValue("originHost", routingService.getCurrentServer().getHost()); //TODO 2.5
metaData.putValue("originPort", Integer.toString(routingService.getCurrentServer().getPort())); // metaData.putValue("originHost", routingService.getCurrentServer().getHost());
// metaData.putValue("originPort", Integer.toString(routingService.getCurrentServer().getPort()));
metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime()));
metaData.putValue("oneway", Boolean.toString(msg.isOneway())); metaData.putValue("oneway", Boolean.toString(msg.isOneway()));
@ -197,9 +193,10 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
} }
private void sendRpcRequestToDevice(ToDeviceRpcRequest msg) { private void sendRpcRequestToDevice(ToDeviceRpcRequest msg) {
ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(routingService.getCurrentServer(), msg); //TODO 2.5
log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg); // ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(routingService.getCurrentServer(), msg);
forward(msg.getDeviceId(), rpcMsg); // log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg);
// forward(msg.getDeviceId(), rpcMsg);
} }
private <T extends ToDeviceActorNotificationMsg> void forward(DeviceId deviceId, T msg) { private <T extends ToDeviceActorNotificationMsg> void forward(DeviceId deviceId, T msg) {

91
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -57,21 +57,31 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.DataConstants.*; import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT;
import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
/** /**
* Created by ashvayka on 01.05.18. * Created by ashvayka on 01.05.18.
@ -111,12 +121,6 @@ public class DefaultDeviceStateService implements DeviceStateService {
@Autowired @Autowired
private TelemetrySubscriptionService tsSubService; private TelemetrySubscriptionService tsSubService;
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService clusterRpcService;
@Value("${state.defaultInactivityTimeoutInSec}") @Value("${state.defaultInactivityTimeoutInSec}")
@Getter @Getter
private long defaultInactivityTimeoutInSec; private long defaultInactivityTimeoutInSec;
@ -235,19 +239,20 @@ public class DefaultDeviceStateService implements DeviceStateService {
TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
pageLink = page.getNextPageLink(); pageLink = page.getNextPageLink();
for (Device device : page.getData()) { for (Device device : page.getData()) {
if (!routingService.resolveById(device.getId()).isPresent()) { //TODO 2.5
// if (!routingService.resolveById(device.getId()).isPresent()) {
if (!deviceStates.containsKey(device.getId())) { if (!deviceStates.containsKey(device.getId())) {
fetchFutures.add(fetchDeviceState(device)); fetchFutures.add(fetchDeviceState(device));
} }
} else { // } else {
Set<DeviceId> tenantDeviceSet = tenantDevices.get(tenant.getId()); // Set<DeviceId> tenantDeviceSet = tenantDevices.get(tenant.getId());
if (tenantDeviceSet != null) { // if (tenantDeviceSet != null) {
tenantDeviceSet.remove(device.getId()); // tenantDeviceSet.remove(device.getId());
} // }
deviceStates.remove(device.getId()); // deviceStates.remove(device.getId());
deviceLastReportedActivity.remove(device.getId()); // deviceLastReportedActivity.remove(device.getId());
deviceLastSavedActivity.remove(device.getId()); // deviceLastSavedActivity.remove(device.getId());
} // }
} }
try { try {
Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
@ -268,9 +273,10 @@ public class DefaultDeviceStateService implements DeviceStateService {
TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); TextPageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
pageLink = page.getNextPageLink(); pageLink = page.getNextPageLink();
for (Device device : page.getData()) { for (Device device : page.getData()) {
if (!routingService.resolveById(device.getId()).isPresent()) { //TODO 2.5
// if (!routingService.resolveById(device.getId()).isPresent()) {
fetchFutures.add(fetchDeviceState(device)); fetchFutures.add(fetchDeviceState(device));
} // }
} }
try { try {
Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
@ -356,7 +362,8 @@ public class DefaultDeviceStateService implements DeviceStateService {
private DeviceStateData getOrFetchDeviceStateData(DeviceId deviceId) { private DeviceStateData getOrFetchDeviceStateData(DeviceId deviceId) {
DeviceStateData deviceStateData = deviceStates.get(deviceId); DeviceStateData deviceStateData = deviceStates.get(deviceId);
if (deviceStateData == null) { if (deviceStateData == null) {
if (!routingService.resolveById(deviceId).isPresent()) { //TODO 2.5
// if (!routingService.resolveById(deviceId).isPresent()) {
Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId); Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
if (device != null) { if (device != null) {
try { try {
@ -366,7 +373,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
log.debug("[{}] Failed to fetch device state!", deviceId, e); log.debug("[{}] Failed to fetch device state!", deviceId, e);
} }
} }
} // }
} }
return deviceStateData; return deviceStateData;
} }
@ -389,8 +396,9 @@ public class DefaultDeviceStateService implements DeviceStateService {
} }
private void onDeviceAddedSync(Device device) { private void onDeviceAddedSync(Device device) {
Optional<ServerAddress> address = routingService.resolveById(device.getId()); //TODO 2.5
if (!address.isPresent()) { // Optional<ServerAddress> address = routingService.resolveById(device.getId());
// if (!address.isPresent()) {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() { Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
@Override @Override
public void onSuccess(@Nullable DeviceStateData state) { public void onSuccess(@Nullable DeviceStateData state) {
@ -402,9 +410,9 @@ public class DefaultDeviceStateService implements DeviceStateService {
log.warn("Failed to register device to the state service", t); log.warn("Failed to register device to the state service", t);
} }
}); });
} else { // } else {
sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), true, false, false); // sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), true, false, false);
} // }
} }
private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, ServerAddress address, boolean added, boolean updated, boolean deleted) { private void sendDeviceEvent(TenantId tenantId, DeviceId deviceId, ServerAddress address, boolean added, boolean updated, boolean deleted) {
@ -417,12 +425,14 @@ public class DefaultDeviceStateService implements DeviceStateService {
builder.setAdded(added); builder.setAdded(added);
builder.setUpdated(updated); builder.setUpdated(updated);
builder.setDeleted(deleted); builder.setDeleted(deleted);
clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_DEVICE_STATE_SERVICE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_DEVICE_STATE_SERVICE_MESSAGE, builder.build().toByteArray());
} }
private void onDeviceUpdatedSync(Device device) { private void onDeviceUpdatedSync(Device device) {
Optional<ServerAddress> address = routingService.resolveById(device.getId()); //TODO 2.5
if (!address.isPresent()) { // Optional<ServerAddress> address = routingService.resolveById(device.getId());
// if (!address.isPresent()) {
DeviceStateData stateData = getOrFetchDeviceStateData(device.getId()); DeviceStateData stateData = getOrFetchDeviceStateData(device.getId());
if (stateData != null) { if (stateData != null) {
TbMsgMetaData md = new TbMsgMetaData(); TbMsgMetaData md = new TbMsgMetaData();
@ -430,14 +440,15 @@ public class DefaultDeviceStateService implements DeviceStateService {
md.putValue("deviceType", device.getType()); md.putValue("deviceType", device.getType());
stateData.setMetaData(md); stateData.setMetaData(md);
} }
} else { // } else {
sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), false, true, false); // sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), false, true, false);
} // }
} }
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
Optional<ServerAddress> address = routingService.resolveById(deviceId); //TODO 2.5
if (!address.isPresent()) { // Optional<ServerAddress> address = routingService.resolveById(deviceId);
// if (!address.isPresent()) {
deviceStates.remove(deviceId); deviceStates.remove(deviceId);
deviceLastReportedActivity.remove(deviceId); deviceLastReportedActivity.remove(deviceId);
deviceLastSavedActivity.remove(deviceId); deviceLastSavedActivity.remove(deviceId);
@ -448,9 +459,9 @@ public class DefaultDeviceStateService implements DeviceStateService {
tenantDevices.remove(tenantId); tenantDevices.remove(tenantId);
} }
} }
} else { // } else {
sendDeviceEvent(tenantId, deviceId, address.get(), false, false, true); // sendDeviceEvent(tenantId, deviceId, address.get(), false, false, true);
} // }
} }
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) { private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {

38
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -56,8 +56,6 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.Subscription; import org.thingsboard.server.service.telemetry.sub.Subscription;
@ -102,12 +100,6 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Autowired @Autowired
private TimeseriesService tsService; private TimeseriesService tsService;
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService rpcService;
@Autowired @Autowired
private EntityViewService entityViewService; private EntityViewService entityViewService;
@ -152,7 +144,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
endTime = entityView.getEndTimeMs(); endTime = entityView.getEndTimeMs();
sub = getUpdatedSubscriptionState(entityId, sub, entityView); sub = getUpdatedSubscriptionState(entityId, sub, entityView);
} }
Optional<ServerAddress> server = routingService.resolveById(entityId); //TODO 2.5
Optional<ServerAddress> server = Optional.empty();//routingService.resolveById(entityId);
Subscription subscription; Subscription subscription;
if (server.isPresent()) { if (server.isPresent()) {
ServerAddress address = server.get(); ServerAddress address = server.get();
@ -340,7 +333,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
while (deviceIterator.hasNext()) { while (deviceIterator.hasNext()) {
Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next(); Map.Entry<EntityId, Set<Subscription>> e = deviceIterator.next();
Set<Subscription> subscriptions = e.getValue(); Set<Subscription> subscriptions = e.getValue();
Optional<ServerAddress> newAddressOptional = routingService.resolveById(e.getKey()); //TODO 2.5
Optional<ServerAddress> newAddressOptional = Optional.empty();// routingService.resolveById(e.getKey());
if (newAddressOptional.isPresent()) { if (newAddressOptional.isPresent()) {
newAddressOptional.ifPresent(serverAddress -> checkSubscriptionsNewAddress(serverAddress, subscriptions)); newAddressOptional.ifPresent(serverAddress -> checkSubscriptionsNewAddress(serverAddress, subscriptions));
} else { } else {
@ -424,7 +418,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
} }
private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) { private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
Optional<ServerAddress> serverAddress = routingService.resolveById(entityId); //TODO 2.5
Optional<ServerAddress> serverAddress = Optional.empty();//routingService.resolveById(entityId);
if (!serverAddress.isPresent()) { if (!serverAddress.isPresent()) {
onLocalAttributesUpdate(entityId, scope, attributes); onLocalAttributesUpdate(entityId, scope, attributes);
if (entityId.getEntityType() == EntityType.DEVICE && DataConstants.SERVER_SCOPE.equalsIgnoreCase(scope)) { if (entityId.getEntityType() == EntityType.DEVICE && DataConstants.SERVER_SCOPE.equalsIgnoreCase(scope)) {
@ -440,7 +435,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
} }
private void onTimeseriesUpdate(EntityId entityId, List<TsKvEntry> ts) { private void onTimeseriesUpdate(EntityId entityId, List<TsKvEntry> ts) {
Optional<ServerAddress> serverAddress = routingService.resolveById(entityId); //TODO 2.5
Optional<ServerAddress> serverAddress = Optional.empty();//routingService.resolveById(entityId);
if (!serverAddress.isPresent()) { if (!serverAddress.isPresent()) {
onLocalTimeseriesUpdate(entityId, ts); onLocalTimeseriesUpdate(entityId, ts);
} else { } else {
@ -632,7 +628,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
} }
sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates( sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(
ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build())); ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray());
} }
private void tellRemoteSubUpdate(ServerAddress address, String sessionId, SubscriptionUpdate update) { private void tellRemoteSubUpdate(ServerAddress address, String sessionId, SubscriptionUpdate update) {
@ -657,7 +654,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
builder.addData(dataBuilder.build()); builder.addData(dataBuilder.build());
} }
); );
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_UPDATE_MESSAGE, builder.build().toByteArray());
} }
private void tellRemoteAttributesUpdate(ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { private void tellRemoteAttributesUpdate(ServerAddress address, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
@ -666,7 +664,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
builder.setEntityType(entityId.getEntityType().name()); builder.setEntityType(entityId.getEntityType().name());
builder.setScope(scope); builder.setScope(scope);
attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build())); attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build()));
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_ATTR_UPDATE_MESSAGE, builder.build().toByteArray());
} }
private void tellRemoteTimeseriesUpdate(ServerAddress address, EntityId entityId, List<TsKvEntry> ts) { private void tellRemoteTimeseriesUpdate(ServerAddress address, EntityId entityId, List<TsKvEntry> ts) {
@ -674,17 +673,20 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
builder.setEntityId(entityId.getId().toString()); builder.setEntityId(entityId.getId().toString());
builder.setEntityType(entityId.getEntityType().name()); builder.setEntityType(entityId.getEntityType().name());
ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build())); ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build()));
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE, builder.build().toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_TS_UPDATE_MESSAGE, builder.build().toByteArray());
} }
private void tellRemoteSessionClose(ServerAddress address, String sessionId) { private void tellRemoteSessionClose(ServerAddress address, String sessionId) {
ClusterAPIProtos.SessionCloseProto proto = ClusterAPIProtos.SessionCloseProto.newBuilder().setSessionId(sessionId).build(); ClusterAPIProtos.SessionCloseProto proto = ClusterAPIProtos.SessionCloseProto.newBuilder().setSessionId(sessionId).build();
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE, proto.toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SESSION_CLOSE_MESSAGE, proto.toByteArray());
} }
private void tellRemoteSubClose(ServerAddress address, String sessionId, int subscriptionId) { private void tellRemoteSubClose(ServerAddress address, String sessionId, int subscriptionId) {
ClusterAPIProtos.SubscriptionCloseProto proto = ClusterAPIProtos.SubscriptionCloseProto.newBuilder().setSessionId(sessionId).setSubscriptionId(subscriptionId).build(); ClusterAPIProtos.SubscriptionCloseProto proto = ClusterAPIProtos.SubscriptionCloseProto.newBuilder().setSessionId(sessionId).setSubscriptionId(subscriptionId).build();
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE, proto.toByteArray()); //TODO 2.5
// rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CLOSE_MESSAGE, proto.toByteArray());
} }
private ClusterAPIProtos.KeyValueProto.Builder toKeyValueProto(long ts, KvEntry attr) { private ClusterAPIProtos.KeyValueProto.Builder toKeyValueProto(long ts, KvEntry attr) {

25
application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java

@ -24,9 +24,6 @@ import org.thingsboard.rule.engine.api.RuleChainTransactionService;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -50,12 +47,6 @@ import java.util.function.Consumer;
@Slf4j @Slf4j
public class BaseRuleChainTransactionService implements RuleChainTransactionService { public class BaseRuleChainTransactionService implements RuleChainTransactionService {
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService clusterRpcService;
@Autowired @Autowired
private DbCallbackExecutorService callbackExecutor; private DbCallbackExecutorService callbackExecutor;
@ -110,13 +101,14 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ
@Override @Override
public void endTransaction(TbMsg msg, Consumer<TbMsg> onSuccess, Consumer<Throwable> onFailure) { public void endTransaction(TbMsg msg, Consumer<TbMsg> onSuccess, Consumer<Throwable> onFailure) {
Optional<ServerAddress> address = routingService.resolveById(msg.getTransactionData().getOriginatorId()); //TODO 2.5
if (address.isPresent()) { // Optional<ServerAddress> address = routingService.resolveById(msg.getTransactionData().getOriginatorId());
sendTransactionEventToRemoteServer(msg, address.get()); // if (address.isPresent()) {
executeOnSuccess(onSuccess, msg); // sendTransactionEventToRemoteServer(msg, address.get());
} else { // executeOnSuccess(onSuccess, msg);
// } else {
endLocalTransaction(msg, onSuccess, onFailure); endLocalTransaction(msg, onSuccess, onFailure);
} // }
} }
@Override @Override
@ -237,6 +229,7 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ
private void sendTransactionEventToRemoteServer(TbMsg msg, ServerAddress address) { private void sendTransactionEventToRemoteServer(TbMsg msg, ServerAddress address) {
log.trace("[{}][{}] Originator is monitored on other server: {}", msg.getTransactionData().getOriginatorId(), msg.getTransactionData().getTransactionId(), address); log.trace("[{}][{}] Originator is monitored on other server: {}", msg.getTransactionData().getOriginatorId(), msg.getTransactionData().getTransactionId(), address);
clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TRANSACTION_SERVICE_MESSAGE, TbMsg.toByteArray(msg)); //TODO 2.5
// clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TRANSACTION_SERVICE_MESSAGE, TbMsg.toByteArray(msg));
} }
} }

9
application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -23,6 +23,7 @@ import org.thingsboard.server.TbQueueCallback;
import org.thingsboard.server.TbQueueMsgMetadata; import org.thingsboard.server.TbQueueMsgMetadata;
import org.thingsboard.server.TbQueueProducer; import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.common.TbProtoQueueMsg; import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.provider.TbCoreQueueProvider; import org.thingsboard.server.provider.TbCoreQueueProvider;
@ -34,17 +35,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Slf4j @Slf4j
@Service @Service
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core')") @ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class DefaultTbCoreToTransportService implements TbCoreToTransportService { public class DefaultTbCoreToTransportService implements TbCoreToTransportService {
private final TbCoreQueueProvider tbCoreQueueProvider;
private final TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> tbTransportProducer; private final TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> tbTransportProducer;
@Value("${queue.notifications.topic}") @Value("${queue.notifications.topic}")
private String notificationsTopic; private String notificationsTopic;
public DefaultTbCoreToTransportService(TbCoreQueueProvider tbCoreQueueProvider) { public DefaultTbCoreToTransportService(TbCoreQueueProvider tbCoreQueueProvider) {
this.tbCoreQueueProvider = tbCoreQueueProvider;
this.tbTransportProducer = tbCoreQueueProvider.getTransportMsgProducer(); this.tbTransportProducer = tbCoreQueueProvider.getTransportMsgProducer();
} }
@ -60,7 +59,7 @@ public class DefaultTbCoreToTransportService implements TbCoreToTransportService
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build();
log.trace("[{}][{}] Pushing session data to topic: {}", topic, sessionId, transportMsg); log.trace("[{}][{}] Pushing session data to topic: {}", topic, sessionId, transportMsg);
TbProtoQueueMsg<ToTransportMsg> queueMsg = new TbProtoQueueMsg<>(NULL_UUID, transportMsg); TbProtoQueueMsg<ToTransportMsg> queueMsg = new TbProtoQueueMsg<>(NULL_UUID, transportMsg);
tbTransportProducer.send(topic, queueMsg, new QueueCallbackAdaptor(onSuccess, onFailure)); tbTransportProducer.send(TopicPartitionInfo.builder().topic(topic).build(), queueMsg, new QueueCallbackAdaptor(onSuccess, onFailure));
} }
private static class QueueCallbackAdaptor implements TbQueueCallback { private static class QueueCallbackAdaptor implements TbQueueCallback {

237
application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java

@ -1,237 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.transport;
import akka.actor.ActorRef;
import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.BlockingBucket;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.local.LocalBucket;
import io.github.bucket4j.local.LocalBucketBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.TbQueueCallback;
import org.thingsboard.server.TbQueueConsumer;
import org.thingsboard.server.TbQueueMsgMetadata;
import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
import org.thingsboard.server.provider.TbCoreQueueProvider;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.queue.TbCoreConsumerStats;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Created by ashvayka on 09.10.18.
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote")
public class RemoteRuleEngineTransportService {
@Value("${transport.remote.rule_engine.topic}")
private String ruleEngineTopic;
@Value("${transport.remote.notifications.topic}")
private String notificationsTopic;
@Value("${transport.remote.rule_engine.poll_interval}")
private int pollDuration;
@Value("${transport.remote.rule_engine.auto_commit_interval}")
private int autoCommitInterval;
@Value("${transport.remote.rule_engine.poll_records_pack_size}")
private int pollRecordsPackSize;
@Value("${transport.remote.rule_engine.max_poll_records_per_second}")
private long pollRecordsPerSecond;
@Value("${transport.remote.rule_engine.max_poll_records_per_minute}")
private long pollRecordsPerMinute;
@Value("${transport.remote.rule_engine.stats.enabled:false}")
private boolean statsEnabled;
@Autowired
private ActorSystemContext actorContext;
//TODO: completely replace this routing with the Kafka routing by partition ids.
@Autowired
private ClusterRoutingService routingService;
@Autowired
private ClusterRpcService rpcService;
@Autowired
private DataDecodingEncodingService encodingService;
@Autowired
private TbCoreQueueProvider coreQueueProvider;
private TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineConsumer;
private TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> notificationsProducer;
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-main-consumer"));
private volatile boolean stopped = false;
private final TbCoreConsumerStats stats = new TbCoreConsumerStats();
@PostConstruct
public void init() {
notificationsProducer = coreQueueProvider.getTransportMsgProducer();
notificationsProducer.init();
//TODO: 2.5
// ruleEngineConsumer =
// ruleEngineConsumer.subscribe();
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
log.info("Received application ready event. Starting polling for events.");
LocalBucketBuilder builder = Bucket4j.builder();
builder.addLimit(Bandwidth.simple(pollRecordsPerSecond, Duration.ofSeconds(1)));
builder.addLimit(Bandwidth.simple(pollRecordsPerMinute, Duration.ofMinutes(1)));
LocalBucket pollRateBucket = builder.build();
BlockingBucket blockingPollRateBucket = pollRateBucket.asScheduler();
mainConsumerExecutor.execute(() -> {
while (!stopped) {
try {
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = ruleEngineConsumer.poll(pollDuration);
int recordsCount = msgs.size();
if (recordsCount > 0) {
while (!blockingPollRateBucket.tryConsume(recordsCount, TimeUnit.SECONDS.toNanos(5))) {
log.info("Rule Engine consumer is busy. Required tokens: [{}]. Available tokens: [{}].", recordsCount, pollRateBucket.getAvailableTokens());
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}
log.trace("Processing {} records", recordsCount);
}
msgs.forEach(msg -> {
try {
ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
log.trace("Forwarding message to rule engine {}", toRuleEngineMsg);
if (toRuleEngineMsg.hasToDeviceActorMsg()) {
forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg());
}
} catch (Throwable e) {
log.warn("Failed to process the notification.", e);
}
});
} catch (Exception e) {
log.warn("Failed to obtain messages from queue.", e);
try {
Thread.sleep(pollDuration);
} catch (InterruptedException e2) {
log.trace("Failed to wait until the server has capacity to handle new requests", e2);
}
}
}
});
}
@Scheduled(fixedDelayString = "${transport.remote.rule_engine.stats.print_interval_ms}")
public void printStats() {
if (statsEnabled) {
stats.printStats();
}
}
@Override
public void process(String nodeId, DeviceActorToTransportMsg msg) {
process(nodeId, msg, null, null);
}
@Override
public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) {
String topic = notificationsTopic + "." + nodeId;
UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB());
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build();
log.trace("[{}][{}] Pushing session data to topic: {}", topic, sessionId, transportMsg);
//TODO: 2.5 id
TbProtoQueueMsg<ToTransportMsg> queueMsg = new TbProtoQueueMsg<>(sessionId, transportMsg);
notificationsProducer.send(topic, queueMsg, new QueueCallbackAdaptor(onSuccess, onFailure));
}
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) {
if (statsEnabled) {
stats.log(toDeviceActorMsg);
}
TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg);
Optional<ServerAddress> address = routingService.resolveById(wrapper.getDeviceId());
if (address.isPresent()) {
log.trace("[{}] Pushing message to remote server: {}", address.get(), toDeviceActorMsg);
rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper));
} else {
log.trace("Pushing message to local server: {}", toDeviceActorMsg);
actorContext.getAppActor().tell(wrapper, ActorRef.noSender());
}
}
@PreDestroy
public void destroy() {
stopped = true;
if (ruleEngineConsumer != null) {
ruleEngineConsumer.unsubscribe();
}
if (mainConsumerExecutor != null) {
mainConsumerExecutor.shutdownNow();
}
}
private static class QueueCallbackAdaptor implements TbQueueCallback {
private final Runnable onSuccess;
private final Consumer<Throwable> onFailure;
QueueCallbackAdaptor(Runnable onSuccess, Consumer<Throwable> onFailure) {
this.onSuccess = onSuccess;
this.onFailure = onFailure;
}
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
if (onSuccess != null) {
onSuccess.run();
}
}
@Override
public void onFailure(Throwable t) {
if (onFailure != null) {
onFailure.accept(t);
}
}
}
}

3
application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java

@ -44,7 +44,8 @@ import java.util.concurrent.*;
*/ */
@Slf4j @Slf4j
@Service @Service
@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote") //TODO 2.5: This Confitional annotation should be removed, and Service renamed to something meaningful
//@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote")
public class RemoteTransportApiService { public class RemoteTransportApiService {
private final TbCoreQueueProvider tbCoreQueueProvider; private final TbCoreQueueProvider tbCoreQueueProvider;

31
application/src/main/java/org/thingsboard/server/service/transport/TransportApiRequestDecoder.java

@ -1,31 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.transport;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.kafka.TbKafkaDecoder;
import java.io.IOException;
/**
* Created by ashvayka on 05.10.18.
*/
public class TransportApiRequestDecoder implements TbKafkaDecoder<TransportApiRequestMsg> {
@Override
public TransportApiRequestMsg decode(byte[] data) throws IOException {
return TransportApiRequestMsg.parseFrom(data);
}
}

30
application/src/main/java/org/thingsboard/server/service/transport/TransportApiResponseEncoder.java

@ -1,30 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.transport;
import org.thingsboard.server.kafka.TbKafkaEncoder;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
/**
* Created by ashvayka on 05.10.18.
*/
public class TransportApiResponseEncoder implements TbKafkaEncoder<TransportApiResponseMsg> {
@Override
public byte[] encode(TransportApiResponseMsg value) {
return value.toByteArray();
}
}

3
application/src/main/resources/thingsboard.yml

@ -528,7 +528,7 @@ swagger:
version: "${SWAGGER_VERSION:2.0}" version: "${SWAGGER_VERSION:2.0}"
queue: queue:
type: "${TB_QUEUE_TYPE:kafka}" type: "${TB_QUEUE_TYPE:in-memory}" # kafka or in-memory
kafka: kafka:
bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
acks: "${TB_KAFKA_ACKS:all}" acks: "${TB_KAFKA_ACKS:all}"
@ -556,6 +556,7 @@ queue:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb.rule-engine}" topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb.rule-engine}"
poll_interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" poll_interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RULE_ENGINE_PARTITIONS:100}" partitions: "${TB_QUEUE_RULE_ENGINE_PARTITIONS:100}"
pack_processing_timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
stats: stats:
enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:false}" enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:false}"
print_interval_ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}" print_interval_ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}"

71
application/src/test/java/org/thingsboard/server/service/cluster/routing/ConsistentClusterRoutingServiceTest.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -21,83 +21,92 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ServerType; import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.discovery.ConsistentHashPartitionService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance; import org.thingsboard.server.discovery.ServiceType;
import org.thingsboard.server.discovery.TbServiceInfoProvider;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Slf4j @Slf4j
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ConsistentClusterRoutingServiceTest { public class ConsistentClusterRoutingServiceTest {
private ConsistentClusterRoutingService clusterRoutingService; public static final int ITERATIONS = 1000000;
private ConsistentHashPartitionService clusterRoutingService;
private DiscoveryService discoveryService; private TbServiceInfoProvider discoveryService;
private String hashFunctionName = "murmur3_128"; private String hashFunctionName = "murmur3_128";
private Integer virtualNodesSize = 1024*4; private Integer virtualNodesSize = 16;
private ServerAddress currentServer = new ServerAddress(" 100.96.1.0", 9001, ServerType.CORE);
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
discoveryService = mock(DiscoveryService.class); discoveryService = mock(TbServiceInfoProvider.class);
clusterRoutingService = new ConsistentClusterRoutingService(); clusterRoutingService = new ConsistentHashPartitionService(discoveryService);
ReflectionTestUtils.setField(clusterRoutingService, "discoveryService", discoveryService); ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core");
ReflectionTestUtils.setField(clusterRoutingService, "corePartitions", 3);
ReflectionTestUtils.setField(clusterRoutingService, "ruleEngineTopic", "tb.rule-engine");
ReflectionTestUtils.setField(clusterRoutingService, "ruleEnginePartitions", 100);
ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName); ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName);
ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize); ReflectionTestUtils.setField(clusterRoutingService, "virtualNodesSize", virtualNodesSize);
when(discoveryService.getCurrentServer()).thenReturn(new ServerInstance(currentServer)); TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder()
List<ServerInstance> otherServers = new ArrayList<>(); .setServiceId("100.96.1.1")
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build();
// when(discoveryService.getServiceInfo()).thenReturn(currentServer);
List<TransportProtos.ServiceInfo> otherServers = new ArrayList<>();
for (int i = 1; i < 30; i++) { for (int i = 1; i < 30; i++) {
otherServers.add(new ServerInstance(new ServerAddress(" 100.96." + i*2 + "." + i, 9001, ServerType.CORE))); otherServers.add(TransportProtos.ServiceInfo.newBuilder()
.setServiceId("100.96." + i * 2 + "." + i)
.addAllServiceTypes(Collections.singletonList(ServiceType.TB_CORE.name()))
.build());
} }
when(discoveryService.getOtherServers()).thenReturn(otherServers);
clusterRoutingService.init(); clusterRoutingService.init();
clusterRoutingService.recalculatePartitions(currentServer, otherServers);
} }
@Test @Test
public void testDispersionOnMillionDevices() { public void testDispersionOnMillionDevices() {
List<DeviceId> devices = new ArrayList<>(); List<DeviceId> devices = new ArrayList<>();
for (int i = 0; i < 1000000; i++) { for (int i = 0; i < ITERATIONS; i++) {
devices.add(new DeviceId(UUIDs.timeBased())); devices.add(new DeviceId(UUIDs.timeBased()));
} }
testDevicesDispersion(devices); testDevicesDispersion(devices);
} }
private void testDevicesDispersion(List<DeviceId> devices) { private void testDevicesDispersion(List<DeviceId> devices) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
Map<ServerAddress, Integer> map = new HashMap<>(); Map<Integer, Integer> map = new HashMap<>();
for (DeviceId deviceId : devices) { for (DeviceId deviceId : devices) {
ServerAddress address = clusterRoutingService.resolveById(deviceId).orElse(currentServer); TopicPartitionInfo address = clusterRoutingService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, deviceId);
map.put(address, map.getOrDefault(address, 0) + 1); Integer partition = address.getPartition().get();
map.put(partition, map.getOrDefault(partition, 0) + 1);
} }
List<Map.Entry<ServerAddress, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); List<Map.Entry<Integer, Integer>> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList());
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + (data.get(data.size() - 1).getValue() - data.get(0).getValue())); double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue());
System.out.println("Size: " + virtualNodesSize + " Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", (diff/ITERATIONS) * 100.0) + "%)");
for (Map.Entry<ServerAddress, Integer> entry : data) { for (Map.Entry<Integer, Integer> entry : data) {
// System.out.println(entry.getKey().getHost() + ": " + entry.getValue()); System.out.println(entry.getKey() + ": " + entry.getValue());
} }
} }

10
common/queue/src/main/java/org/thingsboard/server/TbQueueProducer.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,7 +15,7 @@
*/ */
package org.thingsboard.server; package org.thingsboard.server;
import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.discovery.TopicPartitionInfo;
public interface TbQueueProducer<T extends TbQueueMsg> { public interface TbQueueProducer<T extends TbQueueMsg> {
@ -23,10 +23,6 @@ public interface TbQueueProducer<T extends TbQueueMsg> {
String getDefaultTopic(); String getDefaultTopic();
void send(T msg, TbQueueCallback callback); void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback);
void send(String topic, T msg, TbQueueCallback callback);
ListenableFuture<TbQueueMsgMetadata> send(String topic, int partition, T msg, TbQueueCallback callback);
} }

5
common/queue/src/main/java/org/thingsboard/server/common/DefaultTbQueueRequestTemplate.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -28,6 +28,7 @@ import org.thingsboard.server.TbQueueMsg;
import org.thingsboard.server.TbQueueMsgMetadata; import org.thingsboard.server.TbQueueMsgMetadata;
import org.thingsboard.server.TbQueueProducer; import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.TbQueueRequestTemplate; import org.thingsboard.server.TbQueueRequestTemplate;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -160,7 +161,7 @@ public class DefaultTbQueueRequestTemplate<Request extends TbQueueMsg, Response
ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); ResponseMetaData<Response> responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future);
pendingRequests.putIfAbsent(requestId, responseMetaData); pendingRequests.putIfAbsent(requestId, responseMetaData);
log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime); log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime);
requestTemplate.send(request, new TbQueueCallback() { requestTemplate.send(TopicPartitionInfo.builder().topic(requestTemplate.getDefaultTopic()).build(), request, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(TbQueueMsgMetadata metadata) {
log.trace("[{}] Request sent: {}", requestId, metadata); log.trace("[{}] Request sent: {}", requestId, metadata);

3
common/queue/src/main/java/org/thingsboard/server/common/DefaultTbQueueResponseTemplate.java

@ -23,6 +23,7 @@ import org.thingsboard.server.TbQueueHandler;
import org.thingsboard.server.TbQueueMsg; import org.thingsboard.server.TbQueueMsg;
import org.thingsboard.server.TbQueueProducer; import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.TbQueueResponseTemplate; import org.thingsboard.server.TbQueueResponseTemplate;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -108,7 +109,7 @@ public class DefaultTbQueueResponseTemplate<Request extends TbQueueMsg, Response
response -> { response -> {
pendingRequestCount.decrementAndGet(); pendingRequestCount.decrementAndGet();
response.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId)); response.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId));
responseTemplate.send(responseTopic, response, null); responseTemplate.send(TopicPartitionInfo.builder().topic(responseTopic).build(), response, null);
}, },
e -> { e -> {
pendingRequestCount.decrementAndGet(); pendingRequestCount.decrementAndGet();

18
application/src/main/java/org/thingsboard/server/service/cluster/routing/ConsistentHashCircle.java → common/queue/src/main/java/org/thingsboard/server/discovery/ConsistentHashCircle.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.cluster.routing; package org.thingsboard.server.discovery;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListMap;
@ -25,11 +24,10 @@ import java.util.concurrent.ConcurrentSkipListMap;
* Created by ashvayka on 23.09.18. * Created by ashvayka on 23.09.18.
*/ */
@Slf4j @Slf4j
public class ConsistentHashCircle { public class ConsistentHashCircle<T> {
private final ConcurrentNavigableMap<Long, ServerInstance> circle = private final ConcurrentNavigableMap<Long, T> circle = new ConcurrentSkipListMap<>();
new ConcurrentSkipListMap<>();
public void put(long hash, ServerInstance instance) { public void put(long hash, T instance) {
circle.put(hash, instance); circle.put(hash, instance);
} }
@ -45,7 +43,7 @@ public class ConsistentHashCircle {
return circle.containsKey(hash); return circle.containsKey(hash);
} }
public ConcurrentNavigableMap<Long, ServerInstance> tailMap(Long hash) { public ConcurrentNavigableMap<Long, T> tailMap(Long hash) {
return circle.tailMap(hash); return circle.tailMap(hash);
} }
@ -53,11 +51,11 @@ public class ConsistentHashCircle {
return circle.firstKey(); return circle.firstKey();
} }
public ServerInstance get(Long hash) { public T get(Long hash) {
return circle.get(hash); return circle.get(hash);
} }
public void log() { public void log() {
circle.entrySet().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress())); circle.forEach((key, value) -> log.debug("{} -> {}", key, value));
} }
} }

221
common/queue/src/main/java/org/thingsboard/server/discovery/ConsistentHashPartitionService.java

@ -0,0 +1,221 @@
package org.thingsboard.server.discovery;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
@Service
@Slf4j
public class ConsistentHashPartitionService implements PartitionService {
@Value("${queue.core.topic}")
private String coreTopic;
@Value("${queue.core.partitions:100}")
private Integer corePartitions;
@Value("${queue.rule_engine.topic}")
private String ruleEngineTopic;
@Value("${queue.rule_engine.partitions:100}")
private Integer ruleEnginePartitions;
@Value("${queue.partitions.hash_function_name:murmur3_32}")
private String hashFunctionName;
@Value("${queue.partitions.virtual_nodes_size:16}")
private Integer virtualNodesSize;
private final TbServiceInfoProvider serviceInfoProvider;
private final ConcurrentMap<ServiceType, String> partitionTopics = new ConcurrentHashMap<>();
private final ConcurrentMap<ServiceType, Integer> partitionSizes = new ConcurrentHashMap<>();
private ConcurrentMap<ServiceKey, List<Integer>> myPartitions = new ConcurrentHashMap<>();
//TODO: Fetch this from the database, together with size of partitions for each service for each tenant.
private ConcurrentMap<TenantId, Set<ServiceType>> isolatedTenants = new ConcurrentHashMap<>();
private HashFunction hashFunction;
public ConsistentHashPartitionService(TbServiceInfoProvider serviceInfoProvider) {
this.serviceInfoProvider = serviceInfoProvider;
}
@PostConstruct
public void init() {
this.hashFunction = forName(hashFunctionName);
partitionSizes.put(ServiceType.TB_CORE, corePartitions);
partitionSizes.put(ServiceType.TB_RULE_ENGINE, ruleEnginePartitions);
partitionTopics.put(ServiceType.TB_CORE, coreTopic);
partitionTopics.put(ServiceType.TB_RULE_ENGINE, ruleEngineTopic);
}
@Override
public List<TopicPartitionInfo> getCurrentPartitions(ServiceType serviceType) {
ServiceInfo currentService = serviceInfoProvider.getServiceInfo();
TenantId tenantId = getTenantId(currentService);
ServiceKey serviceKey = new ServiceKey(serviceType, tenantId);
List<Integer> partitions = myPartitions.get(serviceKey);
List<TopicPartitionInfo> topicPartitions = new ArrayList<>();
for (Integer partition : partitions) {
TopicPartitionInfo.TopicPartitionInfoBuilder tpi = TopicPartitionInfo.builder();
tpi.topic(partitionTopics.get(serviceType));
tpi.partition(partition);
if (!tenantId.isNullUid()) {
tpi.tenantId(tenantId);
}
topicPartitions.add(tpi.build());
}
return topicPartitions;
}
@Override
public TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId) {
boolean isolated = isolatedTenants.get(tenantId) != null && isolatedTenants.get(tenantId).contains(serviceType);
int hash = hashFunction.newHasher()
.putLong(entityId.getId().getMostSignificantBits())
.putLong(entityId.getId().getLeastSignificantBits()).hash().asInt();
int partition = Math.abs(hash % partitionSizes.get(serviceType));
TopicPartitionInfo.TopicPartitionInfoBuilder tpi = TopicPartitionInfo.builder();
tpi.topic(partitionTopics.get(serviceType));
tpi.partition(partition);
if (isolated) {
tpi.tenantId(tenantId);
}
return tpi.build();
}
@Override
public void recalculatePartitions(ServiceInfo currentService, List<ServiceInfo> otherServices) {
logServiceInfo(currentService);
otherServices.forEach(this::logServiceInfo);
Map<ServiceType, ConsistentHashCircle<ServiceInfo>> newCircles = new HashMap<>(ServiceType.values().length);
for (ServiceType serverType : ServiceType.values()) {
newCircles.put(serverType, new ConsistentHashCircle<>());
}
addNode(newCircles, currentService);
for (ServiceInfo other : otherServices) {
addNode(newCircles, other);
TenantId tenantId = getTenantId(other);
if (!tenantId.isNullUid()) {
isolatedTenants.putIfAbsent(tenantId, new HashSet<>());
for (String serviceType : other.getServiceTypesList()) {
isolatedTenants.get(tenantId).add(ServiceType.valueOf(serviceType.toUpperCase()));
}
}
}
ConcurrentMap<ServiceKey, List<Integer>> oldPartitions = myPartitions;
myPartitions = new ConcurrentHashMap<>();
partitionSizes.forEach((type, size) -> {
for (int i = 0; i < size; i++) {
ServiceInfo serviceInfo = resolveByPartitionIdx(newCircles.get(type), i);
if (currentService.equals(serviceInfo)) {
myPartitions.putIfAbsent(new ServiceKey(type, getTenantId(serviceInfo)), new ArrayList<>());
}
}
});
myPartitions.forEach((serviceKey, partitions) -> {
if (!partitions.equals(oldPartitions.get(serviceKey))) {
log.info("[{}] NEW PARTITIONS: {}", serviceKey, partitions);
}
});
}
private void logServiceInfo(TransportProtos.ServiceInfo server) {
TenantId tenantId = getTenantId(server);
if (tenantId.isNullUid()) {
log.info("[{}] Found common server: [{}]", server.getServiceId(), server.getServiceTypesList());
} else {
log.info("[{}][{}] Found specific server: [{}]", server.getServiceId(), tenantId, server.getServiceTypesList());
}
}
private TenantId getTenantId(TransportProtos.ServiceInfo serviceInfo) {
return new TenantId(new UUID(serviceInfo.getTenantIdMSB(), serviceInfo.getTenantIdLSB()));
}
private void addNode(Map<ServiceType, ConsistentHashCircle<ServiceInfo>> circles, ServiceInfo instance) {
for (String serviceTypeStr : instance.getServiceTypesList()) {
ServiceType serviceType = ServiceType.valueOf(serviceTypeStr.toUpperCase());
for (int i = 0; i < virtualNodesSize; i++) {
circles.get(serviceType).put(hash(instance, i).asLong(), instance);
}
}
}
private ServiceInfo resolveByPartitionIdx(ConsistentHashCircle<ServiceInfo> circle, Integer partitionIdx) {
if (circle.isEmpty()) {
return null;
}
Long hash = hashFunction.newHasher().putInt(partitionIdx).hash().asLong();
if (!circle.containsKey(hash)) {
ConcurrentNavigableMap<Long, ServiceInfo> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ?
circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
private HashCode hash(ServiceInfo instance, int i) {
return hashFunction.newHasher().putString(instance.getServiceId(), StandardCharsets.UTF_8).putInt(i).hash();
}
private static class ServiceKey {
@Getter
private final ServiceType serviceType;
@Getter
private final TenantId tenantId;
public ServiceKey(ServiceType serviceType, TenantId tenantId) {
this.serviceType = serviceType;
this.tenantId = tenantId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServiceKey that = (ServiceKey) o;
return serviceType == that.serviceType &&
Objects.equals(tenantId, that.tenantId);
}
@Override
public int hashCode() {
return Objects.hash(serviceType, tenantId);
}
}
public static HashFunction forName(String name) {
switch (name) {
case "murmur3_32":
return Hashing.murmur3_32();
case "murmur3_128":
return Hashing.murmur3_128();
case "crc32":
return Hashing.crc32();
case "md5":
return Hashing.md5();
default:
throw new IllegalArgumentException("Can't find hash function with name " + name);
}
}
}

11
common/queue/src/main/java/org/thingsboard/server/discovery/DefaultTbServiceInfoProvider.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -69,11 +69,14 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
ServiceInfo.Builder builder = ServiceInfo.newBuilder() ServiceInfo.Builder builder = ServiceInfo.newBuilder()
.setServiceId(serviceId) .setServiceId(serviceId)
.addAllServiceTypes(serviceTypes.stream().map(ServiceType::name).collect(Collectors.toList())); .addAllServiceTypes(serviceTypes.stream().map(ServiceType::name).collect(Collectors.toList()));
UUID tenantId;
if (!StringUtils.isEmpty(tenantIdStr)) { if (!StringUtils.isEmpty(tenantIdStr)) {
UUID tenantId = UUID.fromString(tenantIdStr); tenantId = UUID.fromString(tenantIdStr);
builder.setTenantIdMSB(tenantId.getMostSignificantBits()); } else {
builder.setTenantIdLSB(tenantId.getLeastSignificantBits()); tenantId = TenantId.NULL_UUID;
} }
builder.setTenantIdMSB(tenantId.getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getLeastSignificantBits());
serviceInfo = builder.build(); serviceInfo = builder.build();
} }

32
common/queue/src/main/java/org/thingsboard/server/discovery/DummyDiscoveryService.java

@ -0,0 +1,32 @@
package org.thingsboard.server.discovery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "false", matchIfMissing = true)
@Slf4j
@DependsOn("environmentLogService")
public class DummyDiscoveryService implements PartitionDiscoveryService {
private final TbServiceInfoProvider serviceInfoProvider;
private final PartitionService partitionService;
public DummyDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) {
this.serviceInfoProvider = serviceInfoProvider;
this.partitionService = partitionService;
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationEvent(ApplicationReadyEvent event) {
partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), Collections.emptyList());
}
}

9
common/queue/src/main/java/org/thingsboard/server/discovery/PartitionDiscoveryService.java

@ -15,15 +15,6 @@
*/ */
package org.thingsboard.server.discovery; package org.thingsboard.server.discovery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
public interface PartitionDiscoveryService { public interface PartitionDiscoveryService {
List<TopicPartitionInfo> getCurrentPartitions(ServiceType serviceType);
TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId);
} }

16
common/queue/src/main/java/org/thingsboard/server/discovery/PartitionService.java

@ -0,0 +1,16 @@
package org.thingsboard.server.discovery;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List;
public interface PartitionService {
List<TopicPartitionInfo> getCurrentPartitions(ServiceType serviceType);
TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId);
void recalculatePartitions(TransportProtos.ServiceInfo currentService, List<TransportProtos.ServiceInfo> otherServices);
}

23
common/queue/src/main/java/org/thingsboard/server/discovery/TopicPartitionInfo.java

@ -15,12 +15,27 @@
*/ */
package org.thingsboard.server.discovery; package org.thingsboard.server.discovery;
import lombok.Data; import lombok.Builder;
import org.thingsboard.server.common.data.id.TenantId;
@Data import java.util.Optional;
@Builder
public class TopicPartitionInfo { public class TopicPartitionInfo {
private String topic; private final String topic;
private int partition; private final TenantId tenantId;
private final Integer partition;
public String getTopic() {
return topic;
}
public Optional<TenantId> getTenantId() {
return Optional.ofNullable(tenantId);
}
public Optional<Integer> getPartition() {
return Optional.ofNullable(partition);
}
} }

122
common/queue/src/main/java/org/thingsboard/server/discovery/ZkPartitionDiscoveryService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -15,11 +15,13 @@
*/ */
package org.thingsboard.server.discovery; package org.thingsboard.server.discovery;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState; import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
@ -39,16 +41,21 @@ import org.springframework.util.Assert;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.gen.transport.TransportProtos;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
@Service @Service
@ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false)
@ -66,16 +73,8 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
@Value("${zk.zk_dir}") @Value("${zk.zk_dir}")
private String zkDir; private String zkDir;
@Value("${queue.core.partitions:100}") private final TbServiceInfoProvider serviceInfoProvider;
private Integer corePartitions; private final PartitionService partitionService;
@Value("${queue.rule_engine.partitions:100}")
private Integer ruleEnginePartitions;
@Autowired
private TbServiceInfoProvider serviceIdProvider;
private final ConcurrentMap<ServiceType, Integer> partitionSizes = new ConcurrentHashMap<>();
private final ConcurrentMap<ServiceType, List<Integer>> myPartitions = new ConcurrentHashMap<>();
private ExecutorService reconnectExecutorService; private ExecutorService reconnectExecutorService;
private CuratorFramework client; private CuratorFramework client;
@ -85,14 +84,9 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
private volatile boolean stopped = true; private volatile boolean stopped = true;
@Override public ZkPartitionDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) {
public List<TopicPartitionInfo> getCurrentPartitions(ServiceType serviceType) { this.serviceInfoProvider = serviceInfoProvider;
return Collections.emptyList(); this.partitionService = partitionService;
}
@Override
public TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId) {
} }
@PostConstruct @PostConstruct
@ -105,15 +99,26 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
reconnectExecutorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); reconnectExecutorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("zk-discovery"));
partitionSizes.put(ServiceType.TB_CORE, corePartitions);
partitionSizes.put(ServiceType.TB_RULE_ENGINE, ruleEnginePartitions);
log.info("Initializing discovery service using ZK connect string: {}", zkUrl); log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
zkNodesDir = zkDir + "/nodes"; zkNodesDir = zkDir + "/nodes";
initZkClient(); initZkClient();
} }
private List<TransportProtos.ServiceInfo> getOtherServers() {
return cache.getCurrentData().stream()
.filter(cd -> !cd.getPath().equals(nodePath))
.map(cd -> {
try {
return TransportProtos.ServiceInfo.parseFrom(cd.getData());
} catch (NoSuchElementException | InvalidProtocolBufferException e) {
log.error("Failed to decode ZK node", e);
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void onApplicationEvent(ApplicationReadyEvent event) { public void onApplicationEvent(ApplicationReadyEvent event) {
if (stopped) { if (stopped) {
@ -127,23 +132,20 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
return; return;
} }
publishCurrentServer(); publishCurrentServer();
getOtherServers().forEach( partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers());
server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort())
);
} }
@Override
public synchronized void publishCurrentServer() { public synchronized void publishCurrentServer() {
ServerInstance self = this.serverInstance.getSelf(); TransportProtos.ServiceInfo self = serviceInfoProvider.getServiceInfo();
if (currentServerExists()) { if (currentServerExists()) {
log.info("[{}:{}] ZK node for current instance already exists, NOT created new one: {}", self.getHost(), self.getPort(), nodePath); log.info("[{}] ZK node for current instance already exists, NOT created new one: {}", self.getServiceId(), nodePath);
} else { } else {
try { try {
log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); log.info("[{}] Creating ZK node for current instance", self.getServiceId());
nodePath = client.create() nodePath = client.create()
.creatingParentsIfNeeded() .creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress())); .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", self.toByteArray());
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); log.info("[{}] Created ZK node for current instance: {}", self.getServiceId(), nodePath);
client.getConnectionStateListenable().addListener(checkReconnect(self)); client.getConnectionStateListenable().addListener(checkReconnect(self));
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to create ZK node", e); log.error("Failed to create ZK node", e);
@ -157,10 +159,10 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
return false; return false;
} }
try { try {
ServerInstance self = this.serverInstance.getSelf(); TransportProtos.ServiceInfo self = serviceInfoProvider.getServiceInfo();
ServerAddress registeredServerAdress = null; TransportProtos.ServiceInfo registeredServerInfo = null;
registeredServerAdress = SerializationUtils.deserialize(client.getData().forPath(nodePath)); registeredServerInfo = TransportProtos.ServiceInfo.parseFrom(client.getData().forPath(nodePath));
if (self.getServerAddress() != null && self.getServerAddress().equals(registeredServerAdress)) { if (self.equals(registeredServerInfo)) {
return true; return true;
} }
} catch (KeeperException.NoNodeException e) { } catch (KeeperException.NoNodeException e) {
@ -171,9 +173,9 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
return false; return false;
} }
private ConnectionStateListener checkReconnect(ServerInstance self) { private ConnectionStateListener checkReconnect(TransportProtos.ServiceInfo self) {
return (client, newState) -> { return (client, newState) -> {
log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState); log.info("[{}] ZK state changed: {}", self.getServiceId(), newState);
if (newState == ConnectionState.LOST) { if (newState == ConnectionState.LOST) {
reconnectExecutorService.submit(this::reconnect); reconnectExecutorService.submit(this::reconnect);
} }
@ -250,6 +252,46 @@ public class ZkPartitionDiscoveryService implements PartitionDiscoveryService, P
@Override @Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
if (stopped) {
log.debug("Ignoring {}. Service is stopped.", pathChildrenCacheEvent);
return;
}
if (client.getState() != CuratorFrameworkState.STARTED) {
log.debug("Ignoring {}, ZK client is not started, ZK client state [{}]", pathChildrenCacheEvent, client.getState());
return;
}
ChildData data = pathChildrenCacheEvent.getData();
if (data == null) {
log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent);
return;
} else if (data.getData() == null) {
log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent);
return;
} else if (nodePath != null && nodePath.equals(data.getPath())) {
if (pathChildrenCacheEvent.getType() == CHILD_REMOVED) {
log.info("ZK node for current instance is somehow deleted.");
publishCurrentServer();
}
log.debug("Ignoring event about current server {}", pathChildrenCacheEvent);
return;
}
TransportProtos.ServiceInfo instance;
try {
instance = TransportProtos.ServiceInfo.parseFrom(data.getData());
} catch (InvalidProtocolBufferException e) {
log.error("Failed to decode server instance for node {}", data.getPath(), e);
throw e;
}
log.info("Processing [{}] event for [{}]", pathChildrenCacheEvent.getType(), instance.getServiceId());
switch (pathChildrenCacheEvent.getType()) {
case CHILD_ADDED:
case CHILD_UPDATED:
case CHILD_REMOVED:
partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers());
break;
default:
break;
}
} }
} }

4
application/src/main/java/org/thingsboard/server/service/environment/EnvironmentLogService.java → common/queue/src/main/java/org/thingsboard/server/environment/EnvironmentLogService.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.environment; package org.thingsboard.server.environment;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.Environment; import org.apache.zookeeper.Environment;
@ -33,7 +33,7 @@ public class EnvironmentLogService {
@PostConstruct @PostConstruct
public void init() { public void init() {
Environment.logEnv("Thingsboard server environment: ", log); Environment.logEnv("ThingsBoard server environment: ", log);
} }
} }

50
common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -29,6 +29,7 @@ import org.springframework.util.StringUtils;
import org.thingsboard.server.TbQueueCallback; import org.thingsboard.server.TbQueueCallback;
import org.thingsboard.server.TbQueueMsg; import org.thingsboard.server.TbQueueMsg;
import org.thingsboard.server.TbQueueProducer; import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -46,10 +47,6 @@ public class TBKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
private final KafkaProducer<String, byte[]> producer; private final KafkaProducer<String, byte[]> producer;
private final TbKafkaPartitioner<T> partitioner;
private ConcurrentMap<String, List<PartitionInfo>> partitionInfoMap;
@Getter @Getter
private final String defaultTopic; private final String defaultTopic;
@ -66,41 +63,28 @@ public class TBKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
} }
this.settings = settings; this.settings = settings;
this.producer = new KafkaProducer<>(props); this.producer = new KafkaProducer<>(props);
this.partitioner = partitioner;
this.defaultTopic = defaultTopic; this.defaultTopic = defaultTopic;
} }
public void init() {
this.partitionInfoMap = new ConcurrentHashMap<>();
if (!StringUtils.isEmpty(defaultTopic)) {
try {
TBKafkaAdmin admin = new TBKafkaAdmin(this.settings);
admin.waitForTopic(defaultTopic, 30, TimeUnit.SECONDS);
log.info("[{}] Topic exists.", defaultTopic);
} catch (Exception e) {
log.info("[{}] Failed to wait for topic: {}", defaultTopic, e.getMessage(), e);
throw new RuntimeException(e);
}
//Maybe this should not be cached, but we don't plan to change size of partitions
this.partitionInfoMap.putIfAbsent(defaultTopic, producer.partitionsFor(defaultTopic));
}
}
@Override @Override
public void send(T msg, TbQueueCallback callback) { public void init() {
send(defaultTopic, msg, callback);
} }
@Override @Override
public void send(String topic, T msg, TbQueueCallback callback) { public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) {
String key = msg.getKey().toString(); String key = msg.getKey().toString();
byte[] data = msg.getData(); byte[] data = msg.getData();
ProducerRecord<String, byte[]> record; ProducerRecord<String, byte[]> record;
Iterable<Header> headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); Iterable<Header> headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList());
StringBuilder topic = new StringBuilder().append(tpi.getTopic());
Integer partition = getPartition(topic, msg); if (tpi.getTenantId().isPresent()) {
record = new ProducerRecord<>(topic, partition, key, data, headers); topic.append(".").append(tpi.getTenantId().get().getId().toString());
Future<RecordMetadata> result = producer.send(record, (metadata, exception) -> { }
if (tpi.getPartition().isPresent()) {
topic.append(".").append(tpi.getPartition().get());
}
record = new ProducerRecord<>(topic.toString(), null, key, data, headers);
producer.send(record, (metadata, exception) -> {
if (exception == null) { if (exception == null) {
callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata)); callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata));
} else { } else {
@ -108,12 +92,4 @@ public class TBKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
} }
}); });
} }
private Integer getPartition(String topic, T value) {
if (partitioner == null) {
return null;
} else {
return partitioner.partition(topic, value.getKey().toString(), value, value.getData(), partitionInfoMap.computeIfAbsent(topic, producer::partitionsFor));
}
}
} }

17
common/queue/src/main/java/org/thingsboard/server/memory/InMemoryTbQueueProducer.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -19,6 +19,7 @@ import lombok.Data;
import org.thingsboard.server.TbQueueCallback; import org.thingsboard.server.TbQueueCallback;
import org.thingsboard.server.TbQueueMsg; import org.thingsboard.server.TbQueueMsg;
import org.thingsboard.server.TbQueueProducer; import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.discovery.TopicPartitionInfo;
@Data @Data
public class InMemoryTbQueueProducer<T extends TbQueueMsg> implements TbQueueProducer<T> { public class InMemoryTbQueueProducer<T extends TbQueueMsg> implements TbQueueProducer<T> {
@ -37,18 +38,8 @@ public class InMemoryTbQueueProducer<T extends TbQueueMsg> implements TbQueuePro
} }
@Override @Override
public String getDefaultTopic() { public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) {
return defaultTopic; boolean result = storage.put(tpi.getTopic(), msg);
}
@Override
public void send(T msg, TbQueueCallback callback) {
send(defaultTopic, msg, callback);
}
@Override
public void send(String topic, T msg, TbQueueCallback callback) {
boolean result = storage.put(topic, msg);
if (result) { if (result) {
callback.onSuccess(null); callback.onSuccess(null);
} else { } else {

12
common/queue/src/main/java/org/thingsboard/server/provider/InMemoryTbCoreQueueProvider.java → common/queue/src/main/java/org/thingsboard/server/provider/InMemoryMonolithQueueProvider.java

@ -32,12 +32,12 @@ import org.thingsboard.server.memory.InMemoryTbQueueProducer;
@Slf4j @Slf4j
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${queue.type:null}'=='in-memory'") @ConditionalOnExpression("'${queue.type:null}'=='in-memory' && '${service.type:null}'=='monolith'")
public class InMemoryTbCoreQueueProvider implements TbCoreQueueProvider { public class InMemoryMonolithQueueProvider implements TbCoreQueueProvider, TbRuleEngineQueueProvider {
private final TbQueueCoreSettings coreSettings; private final TbQueueCoreSettings coreSettings;
public InMemoryTbCoreQueueProvider(TbQueueCoreSettings coreSettings) { public InMemoryMonolithQueueProvider(TbQueueCoreSettings coreSettings) {
this.coreSettings = coreSettings; this.coreSettings = coreSettings;
} }
@ -59,6 +59,12 @@ public class InMemoryTbCoreQueueProvider implements TbCoreQueueProvider {
return producer; return producer;
} }
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> getToRuleEngineMsgConsumer() {
InMemoryTbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer = new InMemoryTbQueueConsumer<>(coreSettings.getTopic());
return consumer;
}
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> getToCoreMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> getToCoreMsgConsumer() {
InMemoryTbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> consumer = new InMemoryTbQueueConsumer<>(coreSettings.getTopic()); InMemoryTbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> consumer = new InMemoryTbQueueConsumer<>(coreSettings.getTopic());

119
common/queue/src/main/java/org/thingsboard/server/provider/KafkaMonolithQueueProvider.java

@ -0,0 +1,119 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.provider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.TbQueueConsumer;
import org.thingsboard.server.TbQueueCoreSettings;
import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.kafka.TbNodeIdProvider;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='monolith'")
public class KafkaMonolithQueueProvider implements TbCoreQueueProvider, TbRuleEngineQueueProvider {
private final TbKafkaSettings kafkaSettings;
private final TbNodeIdProvider nodeIdProvider;
private final TbQueueCoreSettings coreSettings;
public KafkaMonolithQueueProvider(TbKafkaSettings kafkaSettings, TbNodeIdProvider nodeIdProvider, TbQueueCoreSettings coreSettings) {
this.kafkaSettings = kafkaSettings;
this.nodeIdProvider = nodeIdProvider;
this.coreSettings = coreSettings;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> getTransportMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-transport-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> getRuleEngineMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-rule-engine-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> getTbCoreMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-core-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> getToRuleEngineMsgConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> responseBuilder = TBKafkaConsumerTemplate.builder();
responseBuilder.settings(kafkaSettings);
responseBuilder.topic(coreSettings.getTopic());
responseBuilder.clientId("tb-rule-engine-consumer-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("tb-rule-engine-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true);
responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
return responseBuilder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> getToCoreMsgConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> consumerBuilder = TBKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(coreSettings.getTopic());
consumerBuilder.clientId("tb-core-consumer" + nodeIdProvider.getNodeId());
consumerBuilder.groupId("tb-core-node-" + nodeIdProvider.getNodeId());
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
return consumerBuilder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> getTransportApiRequestConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<TransportApiRequestMsg>> responseBuilder = TBKafkaConsumerTemplate.builder();
responseBuilder.settings(kafkaSettings);
responseBuilder.topic(coreSettings.getTopic());
responseBuilder.clientId("consumer-transport-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true);
//TODO 2.5
responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
return responseBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> getTransportApiResponseProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<TransportApiResponseMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("transport-api-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
}

33
common/queue/src/main/java/org/thingsboard/server/provider/KafkaTbCoreQueueProvider.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -32,7 +32,7 @@ import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.kafka.TbNodeIdProvider; import org.thingsboard.server.kafka.TbNodeIdProvider;
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${queue.type:null}'=='kafka'") @ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='tb-core'")
public class KafkaTbCoreQueueProvider implements TbCoreQueueProvider { public class KafkaTbCoreQueueProvider implements TbCoreQueueProvider {
private final TbKafkaSettings kafkaSettings; private final TbKafkaSettings kafkaSettings;
@ -49,7 +49,7 @@ public class KafkaTbCoreQueueProvider implements TbCoreQueueProvider {
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> getTransportMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> getTransportMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TBKafkaProducerTemplate.builder(); TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings); requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-core-" + nodeIdProvider.getNodeId()); requestBuilder.clientId("producer-transport-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic()); requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build(); return requestBuilder.build();
} }
@ -58,7 +58,7 @@ public class KafkaTbCoreQueueProvider implements TbCoreQueueProvider {
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> getRuleEngineMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> getRuleEngineMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> requestBuilder = TBKafkaProducerTemplate.builder(); TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings); requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-core-" + nodeIdProvider.getNodeId()); requestBuilder.clientId("producer-rule-engine-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic()); requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build(); return requestBuilder.build();
} }
@ -74,28 +74,33 @@ public class KafkaTbCoreQueueProvider implements TbCoreQueueProvider {
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> getToCoreMsgConsumer() { public TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> getToCoreMsgConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> responseBuilder = TBKafkaConsumerTemplate.builder(); TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> consumerBuilder = TBKafkaConsumerTemplate.builder();
consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(coreSettings.getTopic());
consumerBuilder.clientId("tb-core-consumer" + nodeIdProvider.getNodeId());
consumerBuilder.groupId("tb-core-node-" + nodeIdProvider.getNodeId());
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders()));
return consumerBuilder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> getTransportApiRequestConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<TransportApiRequestMsg>> responseBuilder = TBKafkaConsumerTemplate.builder();
responseBuilder.settings(kafkaSettings); responseBuilder.settings(kafkaSettings);
responseBuilder.topic(coreSettings.getTopic()); responseBuilder.topic(coreSettings.getTopic());
responseBuilder.clientId("consumer-transport-" + nodeIdProvider.getNodeId()); responseBuilder.clientId("consumer-transport-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId()); responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true); responseBuilder.autoCommit(true);
//TODO: 2.5 //TODO 2.5
// responseBuilder.autoCommitIntervalMs(autoCommitInterval); responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
// responseBuilder.decoder(new TransportApiResponseDecoder());
return responseBuilder.build(); return responseBuilder.build();
} }
@Override
public TbQueueConsumer<TbProtoQueueMsg<TransportApiRequestMsg>> getTransportApiRequestConsumer() {
return null;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> getTransportApiResponseProducer() { public TbQueueProducer<TbProtoQueueMsg<TransportApiResponseMsg>> getTransportApiResponseProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<TransportApiResponseMsg>> requestBuilder = TBKafkaProducerTemplate.builder(); TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<TransportApiResponseMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings); requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-core-" + nodeIdProvider.getNodeId()); requestBuilder.clientId("transport-api-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic()); requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build(); return requestBuilder.build();
} }

86
common/queue/src/main/java/org/thingsboard/server/provider/KafkaTbRuleEngineQueueProvider.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.provider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.TbQueueConsumer;
import org.thingsboard.server.TbQueueCoreSettings;
import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate;
import org.thingsboard.server.kafka.TBKafkaProducerTemplate;
import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.kafka.TbNodeIdProvider;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='tb-rule-engine'")
public class KafkaTbRuleEngineQueueProvider implements TbRuleEngineQueueProvider {
private final TbKafkaSettings kafkaSettings;
private final TbNodeIdProvider nodeIdProvider;
private final TbQueueCoreSettings coreSettings;
public KafkaTbRuleEngineQueueProvider(TbKafkaSettings kafkaSettings, TbNodeIdProvider nodeIdProvider, TbQueueCoreSettings coreSettings) {
this.kafkaSettings = kafkaSettings;
this.nodeIdProvider = nodeIdProvider;
this.coreSettings = coreSettings;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> getTransportMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToTransportMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-transport-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> getRuleEngineMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-rule-engine-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> getTbCoreMsgProducer() {
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCoreMsg>> requestBuilder = TBKafkaProducerTemplate.builder();
requestBuilder.settings(kafkaSettings);
requestBuilder.clientId("producer-core-" + nodeIdProvider.getNodeId());
requestBuilder.defaultTopic(coreSettings.getTopic());
return requestBuilder.build();
}
@Override
public TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> getToRuleEngineMsgConsumer() {
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<TbProtoQueueMsg<ToRuleEngineMsg>> responseBuilder = TBKafkaConsumerTemplate.builder();
responseBuilder.settings(kafkaSettings);
responseBuilder.topic(coreSettings.getTopic());
responseBuilder.clientId("tb-rule-engine-consumer-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("tb-rule-engine-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true);
responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders()));
return responseBuilder.build();
}
}

8
common/queue/src/main/java/org/thingsboard/server/provider/KafkaTransportQueueProvider.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -35,7 +35,7 @@ import org.thingsboard.server.kafka.TbKafkaSettings;
import org.thingsboard.server.kafka.TbNodeIdProvider; import org.thingsboard.server.kafka.TbNodeIdProvider;
@Component @Component
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-transport') && '${queue.type:null}'=='kafka'") @ConditionalOnExpression("'${queue.type:null}'=='kafka' && ('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-transport')")
@Slf4j @Slf4j
public class KafkaTransportQueueProvider implements TransportQueueProvider { public class KafkaTransportQueueProvider implements TransportQueueProvider {
@ -62,9 +62,7 @@ public class KafkaTransportQueueProvider implements TransportQueueProvider {
responseBuilder.clientId("consumer-transport-" + nodeIdProvider.getNodeId()); responseBuilder.clientId("consumer-transport-" + nodeIdProvider.getNodeId());
responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId()); responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId());
responseBuilder.autoCommit(true); responseBuilder.autoCommit(true);
//TODO: 2.5 responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders()));
// responseBuilder.autoCommitIntervalMs(autoCommitInterval);
// responseBuilder.decoder(new TransportApiResponseDecoder());
DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder
<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> templateBuilder = DefaultTbQueueRequestTemplate.builder(); <TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> templateBuilder = DefaultTbQueueRequestTemplate.builder();

61
common/queue/src/main/java/org/thingsboard/server/provider/TbRuleEngineQueueProvider.java

@ -0,0 +1,61 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.provider;
import org.thingsboard.server.TbQueueConsumer;
import org.thingsboard.server.TbQueueProducer;
import org.thingsboard.server.common.TbProtoQueueMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg;
/**
* Responsible for initialization of various Producers and Consumers used by TB Core Node.
* Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable
*/
public interface TbRuleEngineQueueProvider {
/**
* Used to push messages to instances of TB Transport Service
*
* @return
*/
TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> getTransportMsgProducer();
/**
* Used to push messages to instances of TB RuleEngine Service
*
* @return
*/
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> getRuleEngineMsgProducer();
/**
* Used to push messages to other instances of TB Core Service
*
* @return
*/
TbQueueProducer<TbProtoQueueMsg<ToCoreMsg>> getTbCoreMsgProducer();
/**
* Used to consume messages by TB Core Service
*
* @return
*/
TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> getToRuleEngineMsgConsumer();
}

4
common/transport/transport-api/pom.xml

@ -36,6 +36,10 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId> <artifactId>data</artifactId>

38
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -34,6 +34,9 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.discovery.PartitionService;
import org.thingsboard.server.discovery.ServiceType;
import org.thingsboard.server.discovery.TopicPartitionInfo;
import org.thingsboard.server.provider.TransportQueueProvider; import org.thingsboard.server.provider.TransportQueueProvider;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
@ -76,8 +79,8 @@ public class DefaultTransportService implements TransportService {
@Value("${transport.sessions.report_timeout}") @Value("${transport.sessions.report_timeout}")
private long sessionReportTimeout; private long sessionReportTimeout;
@Autowired private final TransportQueueProvider queueProvider;
private TransportQueueProvider queueProvider; private final PartitionService partitionService;
@Value("${kafka.notifications.poll_interval}") @Value("${kafka.notifications.poll_interval}")
private int notificationsPollDuration; private int notificationsPollDuration;
@ -98,6 +101,11 @@ public class DefaultTransportService implements TransportService {
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
private volatile boolean stopped = false; private volatile boolean stopped = false;
public DefaultTransportService(TransportQueueProvider queueProvider, PartitionService partitionService) {
this.queueProvider = queueProvider;
this.partitionService = partitionService;
}
@PostConstruct @PostConstruct
public void init() { public void init() {
if (rateLimitEnabled) { if (rateLimitEnabled) {
@ -420,6 +428,14 @@ public class DefaultTransportService implements TransportService {
return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()); return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB());
} }
protected TenantId getTenantId(TransportProtos.SessionInfoProto sessionInfo) {
return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
}
protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) {
return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
}
public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) {
return TransportProtos.SessionEventMsg.newBuilder() return TransportProtos.SessionEventMsg.newBuilder()
.setSessionType(TransportProtos.SessionType.ASYNC) .setSessionType(TransportProtos.SessionType.ASYNC)
@ -427,15 +443,19 @@ public class DefaultTransportService implements TransportService {
} }
protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) { protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback<Void> callback) {
tbCoreMsgProducer.send(new TbProtoQueueMsg<>(getRoutingKey(sessionInfo), TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo));
ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ? tbCoreMsgProducer.send(tpi,
new TransportTbQueueCallback(callback) : null); new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build()), callback != null ?
new TransportTbQueueCallback(callback) : null);
} }
protected void sendToRuleEngine(TransportProtos.SessionInfoProto sessionInfo, TransportToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback<Void> callback) { protected void sendToRuleEngine(TransportProtos.SessionInfoProto sessionInfo, TransportToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback<Void> callback) {
ruleEngineMsgProducer.send(new TbProtoQueueMsg<>(getRoutingKey(sessionInfo), TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(sessionInfo), getDeviceId(sessionInfo));
ToRuleEngineMsg.newBuilder().setToRuleEngineMsg(toRuleEngineMsg).build()), callback != null ? ruleEngineMsgProducer.send(tpi,
new TransportTbQueueCallback(callback) : null); new TbProtoQueueMsg<>(getRoutingKey(sessionInfo),
ToRuleEngineMsg.newBuilder().setToRuleEngineMsg(toRuleEngineMsg).build()), callback != null ?
new TransportTbQueueCallback(callback) : null);
} }
private class TransportTbQueueCallback implements TbQueueCallback { private class TransportTbQueueCallback implements TbQueueCallback {

Loading…
Cancel
Save