63 changed files with 1055 additions and 2236 deletions
@ -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"; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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(); |
|
||||
}); |
|
||||
} |
|
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
|
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
@ -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(); |
|
||||
|
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
@ -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(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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(); |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
|
|
||||
} |
|
||||
@ -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"); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
|
||||
} |
|
||||
} |
|
||||
@ -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(); |
|
||||
} |
|
||||
} |
|
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue