committed by
GitHub
497 changed files with 8022 additions and 20215 deletions
File diff suppressed because it is too large
@ -1,520 +0,0 @@ |
|||
{ |
|||
"title": "Rule Engine Statistics", |
|||
"configuration": { |
|||
"widgets": { |
|||
"81987f19-3eac-e4ce-b790-d96e9b54d9a0": { |
|||
"isSystemType": true, |
|||
"bundleAlias": "charts", |
|||
"typeAlias": "basic_timeseries", |
|||
"type": "timeseries", |
|||
"title": "New widget", |
|||
"sizeX": 12, |
|||
"sizeY": 7, |
|||
"config": { |
|||
"datasources": [ |
|||
{ |
|||
"type": "entity", |
|||
"dataKeys": [ |
|||
{ |
|||
"name": "successfulMsgs", |
|||
"type": "timeseries", |
|||
"label": "${entityName} Successful", |
|||
"color": "#4caf50", |
|||
"settings": { |
|||
"excludeFromStacking": false, |
|||
"hideDataByDefault": false, |
|||
"disableDataHiding": false, |
|||
"removeFromLegend": false, |
|||
"showLines": true, |
|||
"fillLines": false, |
|||
"showPoints": false, |
|||
"showPointShape": "circle", |
|||
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", |
|||
"showPointsLineWidth": 5, |
|||
"showPointsRadius": 3, |
|||
"showSeparateAxis": false, |
|||
"axisPosition": "left", |
|||
"thresholds": [ |
|||
{ |
|||
"thresholdValueSource": "predefinedValue" |
|||
} |
|||
], |
|||
"comparisonSettings": { |
|||
"showValuesForComparison": true |
|||
} |
|||
}, |
|||
"_hash": 0.15490750967648736 |
|||
}, |
|||
{ |
|||
"name": "failedMsgs", |
|||
"type": "timeseries", |
|||
"label": "${entityName} Permanent Failures", |
|||
"color": "#ef5350", |
|||
"settings": { |
|||
"excludeFromStacking": false, |
|||
"hideDataByDefault": false, |
|||
"disableDataHiding": false, |
|||
"removeFromLegend": false, |
|||
"showLines": true, |
|||
"fillLines": false, |
|||
"showPoints": false, |
|||
"showPointShape": "circle", |
|||
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", |
|||
"showPointsLineWidth": 5, |
|||
"showPointsRadius": 3, |
|||
"showSeparateAxis": false, |
|||
"axisPosition": "left", |
|||
"thresholds": [ |
|||
{ |
|||
"thresholdValueSource": "predefinedValue" |
|||
} |
|||
], |
|||
"comparisonSettings": { |
|||
"showValuesForComparison": true |
|||
} |
|||
}, |
|||
"_hash": 0.4186621166514697 |
|||
}, |
|||
{ |
|||
"name": "tmpFailed", |
|||
"type": "timeseries", |
|||
"label": "${entityName} Processing Failures", |
|||
"color": "#ffc107", |
|||
"settings": { |
|||
"excludeFromStacking": false, |
|||
"hideDataByDefault": false, |
|||
"disableDataHiding": false, |
|||
"removeFromLegend": false, |
|||
"showLines": true, |
|||
"fillLines": false, |
|||
"showPoints": false, |
|||
"showPointShape": "circle", |
|||
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", |
|||
"showPointsLineWidth": 5, |
|||
"showPointsRadius": 3, |
|||
"showSeparateAxis": false, |
|||
"axisPosition": "left", |
|||
"thresholds": [ |
|||
{ |
|||
"thresholdValueSource": "predefinedValue" |
|||
} |
|||
], |
|||
"comparisonSettings": { |
|||
"showValuesForComparison": true |
|||
} |
|||
}, |
|||
"_hash": 0.49891007198715376 |
|||
} |
|||
], |
|||
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" |
|||
} |
|||
], |
|||
"timewindow": { |
|||
"realtime": { |
|||
"interval": 1000, |
|||
"timewindowMs": 300000 |
|||
}, |
|||
"aggregation": { |
|||
"type": "NONE", |
|||
"limit": 8640 |
|||
}, |
|||
"hideInterval": false, |
|||
"hideAggregation": false, |
|||
"hideAggInterval": false |
|||
}, |
|||
"showTitle": true, |
|||
"backgroundColor": "#fff", |
|||
"color": "rgba(0, 0, 0, 0.87)", |
|||
"padding": "8px", |
|||
"settings": { |
|||
"shadowSize": 4, |
|||
"fontColor": "#545454", |
|||
"fontSize": 10, |
|||
"xaxis": { |
|||
"showLabels": true, |
|||
"color": "#545454" |
|||
}, |
|||
"yaxis": { |
|||
"showLabels": true, |
|||
"color": "#545454" |
|||
}, |
|||
"grid": { |
|||
"color": "#545454", |
|||
"tickColor": "#DDDDDD", |
|||
"verticalLines": true, |
|||
"horizontalLines": true, |
|||
"outlineWidth": 1 |
|||
}, |
|||
"stack": false, |
|||
"tooltipIndividual": false, |
|||
"timeForComparison": "months", |
|||
"xaxisSecond": { |
|||
"axisPosition": "top", |
|||
"showLabels": true |
|||
} |
|||
}, |
|||
"title": "Queue Stats", |
|||
"dropShadow": true, |
|||
"enableFullscreen": true, |
|||
"titleStyle": { |
|||
"fontSize": "16px", |
|||
"fontWeight": 400 |
|||
}, |
|||
"mobileHeight": null, |
|||
"showTitleIcon": false, |
|||
"titleIcon": null, |
|||
"iconColor": "rgba(0, 0, 0, 0.87)", |
|||
"iconSize": "24px", |
|||
"titleTooltip": "", |
|||
"widgetStyle": {}, |
|||
"useDashboardTimewindow": false, |
|||
"displayTimewindow": true, |
|||
"showLegend": true, |
|||
"actions": {}, |
|||
"legendConfig": { |
|||
"direction": "column", |
|||
"position": "bottom", |
|||
"showMin": true, |
|||
"showMax": true, |
|||
"showAvg": false, |
|||
"showTotal": true |
|||
} |
|||
}, |
|||
"id": "81987f19-3eac-e4ce-b790-d96e9b54d9a0" |
|||
}, |
|||
"5eb79712-5c24-3060-7e4f-6af36b8f842d": { |
|||
"isSystemType": true, |
|||
"bundleAlias": "cards", |
|||
"typeAlias": "timeseries_table", |
|||
"type": "timeseries", |
|||
"title": "New widget", |
|||
"sizeX": 24, |
|||
"sizeY": 5, |
|||
"config": { |
|||
"datasources": [ |
|||
{ |
|||
"type": "entity", |
|||
"dataKeys": [ |
|||
{ |
|||
"name": "ruleEngineException", |
|||
"type": "timeseries", |
|||
"label": "Rule Chain", |
|||
"color": "#2196f3", |
|||
"settings": { |
|||
"useCellStyleFunction": false, |
|||
"useCellContentFunction": true, |
|||
"cellContentFunction": "return JSON.parse(value).ruleChainName;" |
|||
}, |
|||
"_hash": 0.9954481282345906 |
|||
}, |
|||
{ |
|||
"name": "ruleEngineException", |
|||
"type": "timeseries", |
|||
"label": "Rule Node", |
|||
"color": "#4caf50", |
|||
"settings": { |
|||
"useCellStyleFunction": false, |
|||
"useCellContentFunction": true, |
|||
"cellContentFunction": "return JSON.parse(value).ruleNodeName;" |
|||
}, |
|||
"_hash": 0.18580357036589978 |
|||
}, |
|||
{ |
|||
"name": "ruleEngineException", |
|||
"type": "timeseries", |
|||
"label": "Latest Error", |
|||
"color": "#f44336", |
|||
"settings": { |
|||
"useCellStyleFunction": false, |
|||
"useCellContentFunction": true, |
|||
"cellContentFunction": "return JSON.parse(value).message;" |
|||
}, |
|||
"_hash": 0.7255162989552142 |
|||
} |
|||
], |
|||
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" |
|||
} |
|||
], |
|||
"timewindow": { |
|||
"realtime": { |
|||
"interval": 1000, |
|||
"timewindowMs": 86400000 |
|||
}, |
|||
"aggregation": { |
|||
"type": "NONE", |
|||
"limit": 200 |
|||
} |
|||
}, |
|||
"showTitle": true, |
|||
"backgroundColor": "rgb(255, 255, 255)", |
|||
"color": "rgba(0, 0, 0, 0.87)", |
|||
"padding": "8px", |
|||
"settings": { |
|||
"showTimestamp": true, |
|||
"displayPagination": true, |
|||
"defaultPageSize": 10 |
|||
}, |
|||
"title": "Exceptions", |
|||
"dropShadow": true, |
|||
"enableFullscreen": true, |
|||
"titleStyle": { |
|||
"fontSize": "16px", |
|||
"fontWeight": 400 |
|||
}, |
|||
"useDashboardTimewindow": false, |
|||
"showLegend": false, |
|||
"widgetStyle": {}, |
|||
"actions": {}, |
|||
"showTitleIcon": false, |
|||
"titleIcon": null, |
|||
"iconColor": "rgba(0, 0, 0, 0.87)", |
|||
"iconSize": "24px", |
|||
"titleTooltip": "", |
|||
"displayTimewindow": true |
|||
}, |
|||
"id": "5eb79712-5c24-3060-7e4f-6af36b8f842d" |
|||
}, |
|||
"ad3f1417-87a8-750e-fc67-49a2de1466d4": { |
|||
"isSystemType": true, |
|||
"bundleAlias": "charts", |
|||
"typeAlias": "basic_timeseries", |
|||
"type": "timeseries", |
|||
"title": "New widget", |
|||
"sizeX": 12, |
|||
"sizeY": 7, |
|||
"config": { |
|||
"datasources": [ |
|||
{ |
|||
"type": "entity", |
|||
"dataKeys": [ |
|||
{ |
|||
"name": "timeoutMsgs", |
|||
"type": "timeseries", |
|||
"label": "${entityName} Permanent Timeouts", |
|||
"color": "#4caf50", |
|||
"settings": { |
|||
"excludeFromStacking": false, |
|||
"hideDataByDefault": false, |
|||
"disableDataHiding": false, |
|||
"removeFromLegend": false, |
|||
"showLines": true, |
|||
"fillLines": false, |
|||
"showPoints": false, |
|||
"showPointShape": "circle", |
|||
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", |
|||
"showPointsLineWidth": 5, |
|||
"showPointsRadius": 3, |
|||
"showSeparateAxis": false, |
|||
"axisPosition": "left", |
|||
"thresholds": [ |
|||
{ |
|||
"thresholdValueSource": "predefinedValue" |
|||
} |
|||
], |
|||
"comparisonSettings": { |
|||
"showValuesForComparison": true |
|||
} |
|||
}, |
|||
"_hash": 0.565222981550328 |
|||
}, |
|||
{ |
|||
"name": "tmpTimeout", |
|||
"type": "timeseries", |
|||
"label": "${entityName} Processing Timeouts", |
|||
"color": "#9c27b0", |
|||
"settings": { |
|||
"excludeFromStacking": false, |
|||
"hideDataByDefault": false, |
|||
"disableDataHiding": false, |
|||
"removeFromLegend": false, |
|||
"showLines": true, |
|||
"fillLines": false, |
|||
"showPoints": false, |
|||
"showPointShape": "circle", |
|||
"pointShapeFormatter": "var size = radius * Math.sqrt(Math.PI) / 2;\nctx.moveTo(x - size, y - size);\nctx.lineTo(x + size, y + size);\nctx.moveTo(x - size, y + size);\nctx.lineTo(x + size, y - size);", |
|||
"showPointsLineWidth": 5, |
|||
"showPointsRadius": 3, |
|||
"showSeparateAxis": false, |
|||
"axisPosition": "left", |
|||
"thresholds": [ |
|||
{ |
|||
"thresholdValueSource": "predefinedValue" |
|||
} |
|||
], |
|||
"comparisonSettings": { |
|||
"showValuesForComparison": true |
|||
} |
|||
}, |
|||
"_hash": 0.2679547062508352 |
|||
} |
|||
], |
|||
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" |
|||
} |
|||
], |
|||
"timewindow": { |
|||
"realtime": { |
|||
"interval": 1000, |
|||
"timewindowMs": 300000 |
|||
}, |
|||
"aggregation": { |
|||
"type": "NONE", |
|||
"limit": 8640 |
|||
}, |
|||
"hideInterval": false, |
|||
"hideAggregation": false, |
|||
"hideAggInterval": false |
|||
}, |
|||
"showTitle": true, |
|||
"backgroundColor": "#fff", |
|||
"color": "rgba(0, 0, 0, 0.87)", |
|||
"padding": "8px", |
|||
"settings": { |
|||
"shadowSize": 4, |
|||
"fontColor": "#545454", |
|||
"fontSize": 10, |
|||
"xaxis": { |
|||
"showLabels": true, |
|||
"color": "#545454" |
|||
}, |
|||
"yaxis": { |
|||
"showLabels": true, |
|||
"color": "#545454" |
|||
}, |
|||
"grid": { |
|||
"color": "#545454", |
|||
"tickColor": "#DDDDDD", |
|||
"verticalLines": true, |
|||
"horizontalLines": true, |
|||
"outlineWidth": 1 |
|||
}, |
|||
"stack": false, |
|||
"tooltipIndividual": false, |
|||
"timeForComparison": "months", |
|||
"xaxisSecond": { |
|||
"axisPosition": "top", |
|||
"showLabels": true |
|||
} |
|||
}, |
|||
"title": "Processing Failures and Timeouts", |
|||
"dropShadow": true, |
|||
"enableFullscreen": true, |
|||
"titleStyle": { |
|||
"fontSize": "16px", |
|||
"fontWeight": 400 |
|||
}, |
|||
"mobileHeight": null, |
|||
"showTitleIcon": false, |
|||
"titleIcon": null, |
|||
"iconColor": "rgba(0, 0, 0, 0.87)", |
|||
"iconSize": "24px", |
|||
"titleTooltip": "", |
|||
"widgetStyle": {}, |
|||
"useDashboardTimewindow": false, |
|||
"displayTimewindow": true, |
|||
"showLegend": true, |
|||
"actions": {}, |
|||
"legendConfig": { |
|||
"direction": "column", |
|||
"position": "bottom", |
|||
"showMin": true, |
|||
"showMax": true, |
|||
"showAvg": false, |
|||
"showTotal": true |
|||
} |
|||
}, |
|||
"id": "ad3f1417-87a8-750e-fc67-49a2de1466d4" |
|||
} |
|||
}, |
|||
"states": { |
|||
"default": { |
|||
"name": "Rule Engine Statistics", |
|||
"root": true, |
|||
"layouts": { |
|||
"main": { |
|||
"widgets": { |
|||
"81987f19-3eac-e4ce-b790-d96e9b54d9a0": { |
|||
"sizeX": 12, |
|||
"sizeY": 7, |
|||
"mobileHeight": null, |
|||
"row": 0, |
|||
"col": 0 |
|||
}, |
|||
"5eb79712-5c24-3060-7e4f-6af36b8f842d": { |
|||
"sizeX": 24, |
|||
"sizeY": 5, |
|||
"row": 7, |
|||
"col": 0 |
|||
}, |
|||
"ad3f1417-87a8-750e-fc67-49a2de1466d4": { |
|||
"sizeX": 12, |
|||
"sizeY": 7, |
|||
"mobileHeight": null, |
|||
"row": 0, |
|||
"col": 12 |
|||
} |
|||
}, |
|||
"gridSettings": { |
|||
"backgroundColor": "#eeeeee", |
|||
"color": "rgba(0,0,0,0.870588)", |
|||
"columns": 24, |
|||
"margins": [ |
|||
10, |
|||
10 |
|||
], |
|||
"backgroundSizeMode": "100%", |
|||
"autoFillHeight": true, |
|||
"mobileAutoFillHeight": false, |
|||
"mobileRowHeight": 70 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"entityAliases": { |
|||
"140f23dd-e3a0-ed98-6189-03c49d2d8018": { |
|||
"id": "140f23dd-e3a0-ed98-6189-03c49d2d8018", |
|||
"alias": "TbServiceQueues", |
|||
"filter": { |
|||
"type": "assetType", |
|||
"resolveMultiple": true, |
|||
"assetType": "TbServiceQueue", |
|||
"assetNameFilter": "" |
|||
} |
|||
} |
|||
}, |
|||
"timewindow": { |
|||
"displayValue": "", |
|||
"selectedTab": 0, |
|||
"hideInterval": false, |
|||
"hideAggregation": false, |
|||
"hideAggInterval": false, |
|||
"realtime": { |
|||
"interval": 1000, |
|||
"timewindowMs": 60000 |
|||
}, |
|||
"history": { |
|||
"historyType": 0, |
|||
"interval": 1000, |
|||
"timewindowMs": 60000, |
|||
"fixedTimewindow": { |
|||
"startTimeMs": 1586176634823, |
|||
"endTimeMs": 1586263034823 |
|||
} |
|||
}, |
|||
"aggregation": { |
|||
"type": "AVG", |
|||
"limit": 25000 |
|||
} |
|||
}, |
|||
"settings": { |
|||
"stateControllerId": "entity", |
|||
"showTitle": false, |
|||
"showDashboardsSelect": true, |
|||
"showEntitiesSelect": true, |
|||
"showDashboardTimewindow": true, |
|||
"showDashboardExport": true, |
|||
"toolbarAlwaysOpen": true |
|||
} |
|||
}, |
|||
"name": "Rule Engine Statistics" |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
/** |
|||
* 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"; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,230 @@ |
|||
/** |
|||
* 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(); |
|||
}); |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
/** |
|||
* 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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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; |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* 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; |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* 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; |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* 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; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* 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.ruleChain; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.MsgType; |
|||
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; |
|||
import org.thingsboard.server.common.msg.aware.TenantAwareMsg; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
/** |
|||
* Created by ashvayka on 19.03.18. |
|||
*/ |
|||
@Data |
|||
final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg { |
|||
|
|||
private static final long serialVersionUID = 2459605482321657447L; |
|||
private final TenantId tenantId; |
|||
private final RuleChainId ruleChainId; |
|||
|
|||
public RemoteToRuleChainTellNextMsg(RuleNodeToRuleChainTellNextMsg original, TenantId tenantId, RuleChainId ruleChainId) { |
|||
super(original.getOriginator(), original.getRelationTypes(), original.getMsg()); |
|||
this.tenantId = tenantId; |
|||
this.ruleChainId = ruleChainId; |
|||
} |
|||
|
|||
@Override |
|||
public MsgType getMsgType() { |
|||
return MsgType.REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
/** |
|||
* 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.shared; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import akka.actor.ActorRef; |
|||
import akka.actor.Props; |
|||
import akka.actor.UntypedActor; |
|||
import akka.japi.Creator; |
|||
import com.google.common.collect.BiMap; |
|||
import com.google.common.collect.HashBiMap; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|||
import org.thingsboard.server.common.data.SearchTextBased; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UUIDBased; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Created by ashvayka on 15.03.18. |
|||
*/ |
|||
@Slf4j |
|||
public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> { |
|||
|
|||
protected final ActorSystemContext systemContext; |
|||
protected final BiMap<T, ActorRef> actors; |
|||
|
|||
public EntityActorsManager(ActorSystemContext systemContext) { |
|||
this.systemContext = systemContext; |
|||
this.actors = HashBiMap.create(); |
|||
} |
|||
|
|||
protected abstract TenantId getTenantId(); |
|||
|
|||
protected abstract String getDispatcherName(); |
|||
|
|||
protected abstract Creator<A> creator(T entityId); |
|||
|
|||
protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction(); |
|||
|
|||
public void init(ActorContext context) { |
|||
for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) { |
|||
T entityId = (T) entity.getId(); |
|||
log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId()); |
|||
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
|
|||
ActorRef actorRef = getOrCreateActor(context, entityId); |
|||
visit(entity, actorRef); |
|||
log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId()); |
|||
} |
|||
} |
|||
|
|||
public void visit(M entity, ActorRef actorRef) { |
|||
} |
|||
|
|||
public ActorRef getOrCreateActor(ActorContext context, T entityId) { |
|||
return actors.computeIfAbsent(entityId, eId -> |
|||
context.actorOf(Props.create(creator(eId)) |
|||
.withDispatcher(getDispatcherName()), eId.toString())); |
|||
} |
|||
|
|||
public void broadcast(Object msg) { |
|||
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|||
} |
|||
|
|||
public void remove(T id) { |
|||
actors.remove(id); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
/** |
|||
* 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.shared.rulechain; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import akka.japi.Creator; |
|||
import lombok.Getter; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.ruleChain.RuleChainActor; |
|||
import org.thingsboard.server.actors.shared.EntityActorsManager; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
|
|||
/** |
|||
* Created by ashvayka on 15.03.18. |
|||
*/ |
|||
@Slf4j |
|||
public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> { |
|||
|
|||
protected final RuleChainService service; |
|||
@Getter |
|||
protected RuleChain rootChain; |
|||
@Getter |
|||
protected ActorRef rootChainActor; |
|||
|
|||
public RuleChainManager(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
this.service = systemContext.getRuleChainService(); |
|||
} |
|||
|
|||
@Override |
|||
public Creator<RuleChainActor> creator(RuleChainId entityId) { |
|||
return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId); |
|||
} |
|||
|
|||
@Override |
|||
public void visit(RuleChain entity, ActorRef actorRef) { |
|||
if (entity != null && entity.isRoot()) { |
|||
rootChain = entity; |
|||
rootChainActor = actorRef; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* 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.shared.rulechain; |
|||
|
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
|
|||
import java.util.Collections; |
|||
|
|||
public class SystemRuleChainManager extends RuleChainManager { |
|||
|
|||
public SystemRuleChainManager(ActorSystemContext systemContext) { |
|||
super(systemContext); |
|||
} |
|||
|
|||
@Override |
|||
protected FetchFunction<RuleChain> getFetchEntitiesFunction() { |
|||
return link -> new PageData<>(); |
|||
} |
|||
|
|||
@Override |
|||
protected TenantId getTenantId() { |
|||
return ModelConstants.SYSTEM_TENANT; |
|||
} |
|||
|
|||
@Override |
|||
protected String getDispatcherName() { |
|||
return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME; |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
/** |
|||
* 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.shared.rulechain; |
|||
|
|||
import akka.actor.ActorContext; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.actors.service.DefaultActorService; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
|
|||
public class TenantRuleChainManager extends RuleChainManager { |
|||
|
|||
private final TenantId tenantId; |
|||
|
|||
public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) { |
|||
super(systemContext); |
|||
this.tenantId = tenantId; |
|||
} |
|||
|
|||
@Override |
|||
public void init(ActorContext context) { |
|||
super.init(context); |
|||
} |
|||
|
|||
@Override |
|||
protected TenantId getTenantId() { |
|||
return tenantId; |
|||
} |
|||
|
|||
@Override |
|||
protected String getDispatcherName() { |
|||
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME; |
|||
} |
|||
|
|||
@Override |
|||
protected FetchFunction<RuleChain> getFetchEntitiesFunction() { |
|||
return link -> service.findTenantRuleChains(tenantId, link); |
|||
} |
|||
} |
|||
@ -1,54 +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.controller; |
|||
|
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api") |
|||
public class QueueController extends BaseController { |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET) |
|||
@ResponseBody |
|||
public List<String> getTenantQueuesByServiceType(@RequestParam String serviceType) throws ThingsboardException { |
|||
checkParameter("serviceType", serviceType); |
|||
try { |
|||
ServiceType type = ServiceType.valueOf(serviceType); |
|||
switch (type) { |
|||
case TB_RULE_ENGINE: |
|||
return Arrays.asList("Main", "HighPriority", "SequentialByOriginator"); |
|||
default: |
|||
return Collections.emptyList(); |
|||
} |
|||
} catch (Exception e) { |
|||
throw handleException(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* 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; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* 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); |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* 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); |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
/** |
|||
* 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"); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
/** |
|||
* 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.rpc; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import io.grpc.Server; |
|||
import io.grpc.ServerBuilder; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
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.discovery.ServerInstance; |
|||
import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import java.io.IOException; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ArrayBlockingQueue; |
|||
import java.util.concurrent.BlockingQueue; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Service |
|||
@Slf4j |
|||
public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceImplBase implements ClusterRpcService { |
|||
|
|||
@Autowired |
|||
private ServerInstanceService instanceService; |
|||
|
|||
@Autowired |
|||
private DataDecodingEncodingService encodingService; |
|||
|
|||
private RpcMsgListener listener; |
|||
|
|||
private Server server; |
|||
|
|||
private ServerInstance instance; |
|||
|
|||
private ConcurrentMap<UUID, BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>>> pendingSessionMap = |
|||
new ConcurrentHashMap<>(); |
|||
|
|||
public void init(RpcMsgListener listener) { |
|||
this.listener = listener; |
|||
log.info("Initializing RPC service!"); |
|||
instance = instanceService.getSelf(); |
|||
server = ServerBuilder.forPort(instance.getPort()).addService(this).build(); |
|||
log.info("Going to start RPC server using port: {}", instance.getPort()); |
|||
try { |
|||
server.start(); |
|||
} catch (IOException e) { |
|||
log.error("Failed to start RPC server!", e); |
|||
throw new RuntimeException("Failed to start RPC server!"); |
|||
} |
|||
log.info("RPC service initialized!"); |
|||
} |
|||
|
|||
@Override |
|||
public void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream) { |
|||
BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = pendingSessionMap.remove(msgUid); |
|||
if (queue != null) { |
|||
try { |
|||
queue.put(inputStream); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to report created session!"); |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} else { |
|||
log.warn("Failed to lookup pending session!"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public StreamObserver<ClusterAPIProtos.ClusterMessage> handleMsgs( |
|||
StreamObserver<ClusterAPIProtos.ClusterMessage> responseObserver) { |
|||
log.info("Processing new session."); |
|||
return createSession(new RpcSessionCreateRequestMsg(UUID.randomUUID(), null, responseObserver)); |
|||
} |
|||
|
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (server != null) { |
|||
log.info("Going to onStop RPC server"); |
|||
server.shutdownNow(); |
|||
try { |
|||
server.awaitTermination(); |
|||
log.info("RPC server stopped!"); |
|||
} catch (InterruptedException e) { |
|||
log.warn("Failed to onStop RPC server!"); |
|||
Thread.currentThread().interrupt(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void broadcast(RpcBroadcastMsg msg) { |
|||
listener.onBroadcastMsg(msg); |
|||
} |
|||
|
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> createSession(RpcSessionCreateRequestMsg msg) { |
|||
BlockingQueue<StreamObserver<ClusterAPIProtos.ClusterMessage>> queue = new ArrayBlockingQueue<>(1); |
|||
pendingSessionMap.put(msg.getMsgUid(), queue); |
|||
listener.onRpcSessionCreateRequestMsg(msg); |
|||
try { |
|||
StreamObserver<ClusterAPIProtos.ClusterMessage> observer = queue.take(); |
|||
log.info("Processed new session."); |
|||
return observer; |
|||
} catch (Exception e) { |
|||
log.info("Failed to process session.", e); |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ClusterAPIProtos.ClusterMessage message) { |
|||
listener.onSendMsg(message); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, TbActorMsg actorMsg) { |
|||
listener.onSendMsg(encodingService.convertToProtoDataMessage(serverAddress, actorMsg)); |
|||
} |
|||
|
|||
@Override |
|||
public void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data) { |
|||
ClusterAPIProtos.ClusterMessage msg = ClusterAPIProtos.ClusterMessage |
|||
.newBuilder() |
|||
.setServerAddress(ClusterAPIProtos.ServerAddress |
|||
.newBuilder() |
|||
.setHost(serverAddress.getHost()) |
|||
.setPort(serverAddress.getPort()) |
|||
.build()) |
|||
.setMessageType(msgType) |
|||
.setPayload(ByteString.copyFrom(data)).build(); |
|||
listener.onSendMsg(msg); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* 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.rpc; |
|||
|
|||
import io.grpc.stub.StreamObserver; |
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
public interface ClusterRpcService { |
|||
|
|||
void init(RpcMsgListener listener); |
|||
|
|||
void broadcast(RpcBroadcastMsg msg); |
|||
|
|||
void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream); |
|||
|
|||
void tell(ClusterAPIProtos.ClusterMessage message); |
|||
|
|||
void tell(ServerAddress serverAddress, TbActorMsg actorMsg); |
|||
|
|||
void tell(ServerAddress serverAddress, ClusterAPIProtos.MessageType msgType, byte[] data); |
|||
} |
|||
@ -0,0 +1,125 @@ |
|||
/** |
|||
* 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.rpc; |
|||
|
|||
import io.grpc.Channel; |
|||
import io.grpc.ManagedChannel; |
|||
import io.grpc.stub.StreamObserver; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.common.msg.cluster.ServerType; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
import java.io.Closeable; |
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
@Data |
|||
@Slf4j |
|||
public final class GrpcSession implements Closeable { |
|||
private final UUID sessionId; |
|||
private final boolean client; |
|||
private final GrpcSessionListener listener; |
|||
private final ManagedChannel channel; |
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream; |
|||
private StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream; |
|||
|
|||
private boolean connected; |
|||
private ServerAddress remoteServer; |
|||
|
|||
public GrpcSession(GrpcSessionListener listener) { |
|||
this(null, listener, null); |
|||
} |
|||
|
|||
public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener, ManagedChannel channel) { |
|||
this.sessionId = UUID.randomUUID(); |
|||
this.listener = listener; |
|||
if (remoteServer != null) { |
|||
this.client = true; |
|||
this.connected = true; |
|||
this.remoteServer = remoteServer; |
|||
} else { |
|||
this.client = false; |
|||
} |
|||
this.channel = channel; |
|||
} |
|||
|
|||
public void initInputStream() { |
|||
this.inputStream = new StreamObserver<ClusterAPIProtos.ClusterMessage>() { |
|||
@Override |
|||
public void onNext(ClusterAPIProtos.ClusterMessage clusterMessage) { |
|||
if (!connected && clusterMessage.getMessageType() == ClusterAPIProtos.MessageType.CONNECT_RPC_MESSAGE) { |
|||
connected = true; |
|||
ServerAddress rpcAddress = new ServerAddress(clusterMessage.getServerAddress().getHost(), clusterMessage.getServerAddress().getPort(), ServerType.CORE); |
|||
remoteServer = new ServerAddress(rpcAddress.getHost(), rpcAddress.getPort(), ServerType.CORE); |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
if (connected) { |
|||
listener.onReceiveClusterGrpcMsg(GrpcSession.this, clusterMessage); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onError(Throwable t) { |
|||
listener.onError(GrpcSession.this, t); |
|||
} |
|||
|
|||
@Override |
|||
public void onCompleted() { |
|||
outputStream.onCompleted(); |
|||
listener.onDisconnected(GrpcSession.this); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public void initOutputStream() { |
|||
if (client) { |
|||
listener.onConnected(GrpcSession.this); |
|||
} |
|||
} |
|||
|
|||
public void sendMsg(ClusterAPIProtos.ClusterMessage msg) { |
|||
if (connected) { |
|||
try { |
|||
outputStream.onNext(msg); |
|||
} catch (Throwable t) { |
|||
try { |
|||
outputStream.onError(t); |
|||
} catch (Throwable t2) { |
|||
} |
|||
listener.onError(GrpcSession.this, t); |
|||
} |
|||
} else { |
|||
log.warn("[{}] Failed to send message due to closed session!", sessionId); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void close() { |
|||
connected = false; |
|||
try { |
|||
outputStream.onCompleted(); |
|||
} catch (IllegalStateException e) { |
|||
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage()); |
|||
} |
|||
if (channel != null) { |
|||
channel.shutdownNow(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* 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.rpc; |
|||
|
|||
import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; |
|||
import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; |
|||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|||
import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
|||
|
|||
/** |
|||
* @author Andrew Shvayka |
|||
*/ |
|||
|
|||
public interface RpcMsgListener { |
|||
void onReceivedMsg(ServerAddress remoteServer, ClusterAPIProtos.ClusterMessage msg); |
|||
void onSendMsg(ClusterAPIProtos.ClusterMessage msg); |
|||
void onRpcSessionCreateRequestMsg(RpcSessionCreateRequestMsg msg); |
|||
void onBroadcastMsg(RpcBroadcastMsg msg); |
|||
} |
|||
@ -1,40 +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.install.migrate; |
|||
|
|||
import com.datastax.driver.core.Row; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.thingsboard.server.dao.model.ModelConstants.EPOCH_DIFF; |
|||
|
|||
public class CassandraToSqlEventTsColumn extends CassandraToSqlColumn { |
|||
|
|||
CassandraToSqlEventTsColumn() { |
|||
super("id", "ts", CassandraToSqlColumnType.BIGINT, null); |
|||
} |
|||
|
|||
@Override |
|||
public String getColumnValue(Row row) { |
|||
UUID id = row.getUUID(getIndex()); |
|||
long ts = getTs(id); |
|||
return ts + ""; |
|||
} |
|||
|
|||
private long getTs(UUID uuid) { |
|||
return (uuid.timestamp() - EPOCH_DIFF) / 10000; |
|||
} |
|||
} |
|||
@ -1,204 +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.queue; |
|||
|
|||
import com.google.protobuf.ByteString; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; |
|||
import org.thingsboard.server.queue.TbQueueCallback; |
|||
import org.thingsboard.server.queue.TbQueueProducer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
|
|||
import java.util.HashSet; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultTbClusterService implements TbClusterService { |
|||
|
|||
@Value("${cluster.stats.enabled:false}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final AtomicInteger toCoreMsgs = new AtomicInteger(0); |
|||
private final AtomicInteger toCoreNfs = new AtomicInteger(0); |
|||
private final AtomicInteger toRuleEngineMsgs = new AtomicInteger(0); |
|||
private final AtomicInteger toRuleEngineNfs = new AtomicInteger(0); |
|||
private final AtomicInteger toTransportNfs = new AtomicInteger(0); |
|||
|
|||
private final TbQueueProducerProvider producerProvider; |
|||
private final PartitionService partitionService; |
|||
private final DataDecodingEncodingService encodingService; |
|||
|
|||
public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService) { |
|||
this.producerProvider = producerProvider; |
|||
this.partitionService = partitionService; |
|||
this.encodingService = encodingService; |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId); |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(TopicPartitionInfo tpi, UUID msgId, ToCoreMsg msg, TbQueueCallback callback) { |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(msgId, msg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, msg.getTenantId(), msg.getDeviceId()); |
|||
log.trace("PUSHING msg: {} to:{}", msg, tpi); |
|||
byte[] msgBytes = encodingService.encode(msg); |
|||
ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorNotificationMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
producerProvider.getTbCoreMsgProducer().send(tpi, new TbProtoQueueMsg<>(msg.getDeviceId().getId(), toCoreMsg), callback); |
|||
toCoreMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToCore(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
FromDeviceRPCResponseProto.Builder builder = FromDeviceRPCResponseProto.newBuilder() |
|||
.setRequestIdMSB(response.getId().getMostSignificantBits()) |
|||
.setRequestIdLSB(response.getId().getLeastSignificantBits()) |
|||
.setError(response.getError().isPresent() ? response.getError().get().ordinal() : -1); |
|||
response.getResponse().ifPresent(builder::setResponse); |
|||
ToCoreNotificationMsg msg = ToCoreNotificationMsg.newBuilder().setFromDeviceRpcResponse(builder).build(); |
|||
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(response.getId(), msg), callback); |
|||
toCoreNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, ToRuleEngineMsg msg, TbQueueCallback callback) { |
|||
log.trace("PUSHING msg: {} to:{}", msg, tpi); |
|||
producerProvider.getRuleEngineMsgProducer().send(tpi, new TbProtoQueueMsg<>(msgId, msg), callback); |
|||
toRuleEngineMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { |
|||
if (tenantId.isNullUid()) { |
|||
if (entityId.getEntityType().equals(EntityType.TENANT)) { |
|||
tenantId = new TenantId(entityId.getId()); |
|||
} else { |
|||
log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg); |
|||
return; |
|||
} |
|||
} |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId); |
|||
log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); |
|||
ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() |
|||
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) |
|||
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) |
|||
.setTbMsg(TbMsg.toByteString(tbMsg)).build(); |
|||
producerProvider.getRuleEngineMsgProducer().send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), callback); |
|||
toRuleEngineMsgs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToRuleEngine(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
FromDeviceRPCResponseProto.Builder builder = FromDeviceRPCResponseProto.newBuilder() |
|||
.setRequestIdMSB(response.getId().getMostSignificantBits()) |
|||
.setRequestIdLSB(response.getId().getLeastSignificantBits()) |
|||
.setError(response.getError().isPresent() ? response.getError().get().ordinal() : -1); |
|||
response.getResponse().ifPresent(builder::setResponse); |
|||
ToRuleEngineNotificationMsg msg = ToRuleEngineNotificationMsg.newBuilder().setFromDeviceRpcResponse(builder).build(); |
|||
producerProvider.getRuleEngineNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(response.getId(), msg), callback); |
|||
toRuleEngineNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void pushNotificationToTransport(String serviceId, ToTransportMsg response, TbQueueCallback callback) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceId); |
|||
log.trace("PUSHING msg: {} to:{}", response, tpi); |
|||
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), response), callback); |
|||
toTransportNfs.incrementAndGet(); |
|||
} |
|||
|
|||
@Override |
|||
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { |
|||
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); |
|||
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state)); |
|||
} |
|||
|
|||
private void broadcast(ComponentLifecycleMsg msg) { |
|||
byte[] msgBytes = encodingService.encode(msg); |
|||
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); |
|||
Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); |
|||
if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)) { |
|||
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); |
|||
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); |
|||
for (String serviceId : tbCoreServices) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); |
|||
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setComponentLifecycleMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
toCoreNfProducer.send(tpi, new TbProtoQueueMsg<>(msg.getEntityId().getId(), toCoreMsg), null); |
|||
toCoreNfs.incrementAndGet(); |
|||
} |
|||
// No need to push notifications twice
|
|||
tbRuleEngineServices.removeAll(tbCoreServices); |
|||
} |
|||
for (String serviceId : tbRuleEngineServices) { |
|||
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId); |
|||
ToRuleEngineNotificationMsg toRuleEngineMsg = ToRuleEngineNotificationMsg.newBuilder().setComponentLifecycleMsg(ByteString.copyFrom(msgBytes)).build(); |
|||
toRuleEngineProducer.send(tpi, new TbProtoQueueMsg<>(msg.getEntityId().getId(), toRuleEngineMsg), null); |
|||
toRuleEngineNfs.incrementAndGet(); |
|||
} |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${cluster.stats.print_interval_ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
int toCoreMsgCnt = toCoreMsgs.getAndSet(0); |
|||
int toCoreNfsCnt = toCoreNfs.getAndSet(0); |
|||
int toRuleEngineMsgsCnt = toRuleEngineMsgs.getAndSet(0); |
|||
int toRuleEngineNfsCnt = toRuleEngineNfs.getAndSet(0); |
|||
int toTransportNfsCnt = toTransportNfs.getAndSet(0); |
|||
if (toCoreMsgCnt > 0 || toCoreNfsCnt > 0 || toRuleEngineMsgsCnt > 0 || toRuleEngineNfsCnt > 0 || toTransportNfsCnt > 0) { |
|||
log.info("To TbCore: [{}] messages [{}] notifications; To TbRuleEngine: [{}] messages [{}] notifications; To Transport: [{}] notifications", |
|||
toCoreMsgCnt, toCoreNfsCnt, toRuleEngineMsgsCnt, toRuleEngineNfsCnt, toTransportNfsCnt); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,290 +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.queue; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.rule.engine.api.RpcError; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.provider.TbCoreQueueFactory; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
|||
import org.thingsboard.server.service.state.DeviceStateService; |
|||
import org.thingsboard.server.service.subscription.SubscriptionManagerService; |
|||
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; |
|||
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; |
|||
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@Slf4j |
|||
public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService { |
|||
|
|||
@Value("${queue.core.poll-interval}") |
|||
private long pollDuration; |
|||
@Value("${queue.core.pack-processing-timeout}") |
|||
private long packProcessingTimeout; |
|||
@Value("${queue.core.stats.enabled:false}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> mainConsumer; |
|||
private final DeviceStateService stateService; |
|||
private final TbLocalSubscriptionService localSubscriptionService; |
|||
private final SubscriptionManagerService subscriptionManagerService; |
|||
private final TbCoreDeviceRpcService tbCoreDeviceRpcService; |
|||
private final TbCoreConsumerStats stats = new TbCoreConsumerStats(); |
|||
|
|||
public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, |
|||
DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService, |
|||
SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService, |
|||
TbCoreDeviceRpcService tbCoreDeviceRpcService) { |
|||
super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); |
|||
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); |
|||
this.stateService = stateService; |
|||
this.localSubscriptionService = localSubscriptionService; |
|||
this.subscriptionManagerService = subscriptionManagerService; |
|||
this.tbCoreDeviceRpcService = tbCoreDeviceRpcService; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init("tb-core-consumer", "tb-core-notifications-consumer"); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void destroy(){ |
|||
super.destroy(); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (partitionChangeEvent.getServiceType().equals(getServiceType())) { |
|||
log.info("Subscribing to partitions: {}", partitionChangeEvent.getPartitions()); |
|||
this.mainConsumer.subscribe(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void launchMainConsumers() { |
|||
consumersExecutor.submit(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<ToCoreMsg>> msgs = mainConsumer.poll(pollDuration); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = msgs.stream().collect( |
|||
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); |
|||
CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
TbPackProcessingContext<TbProtoQueueMsg<ToCoreMsg>> ctx = new TbPackProcessingContext<>( |
|||
processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); |
|||
pendingMap.forEach((id, msg) -> { |
|||
log.trace("[{}] Creating main callback for message: {}", id, msg.getValue()); |
|||
TbCallback callback = new TbPackCallback<>(id, ctx); |
|||
try { |
|||
ToCoreMsg toCoreMsg = msg.getValue(); |
|||
if (toCoreMsg.hasToSubscriptionMgrMsg()) { |
|||
log.trace("[{}] Forwarding message to subscription manager service {}", id, toCoreMsg.getToSubscriptionMgrMsg()); |
|||
forwardToSubMgrService(toCoreMsg.getToSubscriptionMgrMsg(), callback); |
|||
} else if (toCoreMsg.hasToDeviceActorMsg()) { |
|||
log.trace("[{}] Forwarding message to device actor {}", id, toCoreMsg.getToDeviceActorMsg()); |
|||
forwardToDeviceActor(toCoreMsg.getToDeviceActorMsg(), callback); |
|||
} else if (toCoreMsg.hasDeviceStateServiceMsg()) { |
|||
log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg()); |
|||
forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback); |
|||
} else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process message: {}", id, msg, e); |
|||
callback.onFailure(e); |
|||
} |
|||
}); |
|||
if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) { |
|||
ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue())); |
|||
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue())); |
|||
} |
|||
mainConsumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Core Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
protected ServiceType getServiceType() { |
|||
return ServiceType.TB_CORE; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPollDuration() { |
|||
return pollDuration; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPackProcessingTimeout() { |
|||
return packProcessingTimeout; |
|||
} |
|||
|
|||
@Override |
|||
protected void handleNotification(UUID id, TbProtoQueueMsg<ToCoreNotificationMsg> msg, TbCallback callback) { |
|||
ToCoreNotificationMsg toCoreNotification = msg.getValue(); |
|||
if (toCoreNotification.hasToLocalSubscriptionServiceMsg()) { |
|||
log.trace("[{}] Forwarding message to local subscription service {}", id, toCoreNotification.getToLocalSubscriptionServiceMsg()); |
|||
forwardToLocalSubMgrService(toCoreNotification.getToLocalSubscriptionServiceMsg(), callback); |
|||
} else if (toCoreNotification.hasFromDeviceRpcResponse()) { |
|||
log.trace("[{}] Forwarding message to RPC service {}", id, toCoreNotification.getFromDeviceRpcResponse()); |
|||
forwardToCoreRpcService(toCoreNotification.getFromDeviceRpcResponse(), callback); |
|||
} else if (toCoreNotification.getComponentLifecycleMsg() != null && !toCoreNotification.getComponentLifecycleMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} |
|||
if (statsEnabled) { |
|||
stats.log(toCoreNotification); |
|||
} |
|||
} |
|||
|
|||
private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) { |
|||
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; |
|||
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()) |
|||
, proto.getResponse(), error); |
|||
tbCoreDeviceRpcService.processRpcResponseFromRuleEngine(response); |
|||
callback.onSuccess(); |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${queue.core.stats.print-interval-ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
stats.printStats(); |
|||
} |
|||
} |
|||
|
|||
private void forwardToLocalSubMgrService(LocalSubscriptionServiceMsgProto msg, TbCallback callback) { |
|||
if (msg.hasSubUpdate()) { |
|||
localSubscriptionService.onSubscriptionUpdate(msg.getSubUpdate().getSessionId(), TbSubscriptionUtils.fromProto(msg.getSubUpdate()), callback); |
|||
} else { |
|||
throwNotHandled(msg, callback); |
|||
} |
|||
} |
|||
|
|||
private void forwardToSubMgrService(SubscriptionMgrMsgProto msg, TbCallback callback) { |
|||
if (msg.hasAttributeSub()) { |
|||
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getAttributeSub()), callback); |
|||
} else if (msg.hasTelemetrySub()) { |
|||
subscriptionManagerService.addSubscription(TbSubscriptionUtils.fromProto(msg.getTelemetrySub()), callback); |
|||
} else if (msg.hasSubClose()) { |
|||
TbSubscriptionCloseProto closeProto = msg.getSubClose(); |
|||
subscriptionManagerService.cancelSubscription(closeProto.getSessionId(), closeProto.getSubscriptionId(), callback); |
|||
} else if (msg.hasTsUpdate()) { |
|||
TbTimeSeriesUpdateProto proto = msg.getTsUpdate(); |
|||
subscriptionManagerService.onTimeSeriesUpdate( |
|||
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), |
|||
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), |
|||
TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback); |
|||
} else if (msg.hasAttrUpdate()) { |
|||
TbAttributeUpdateProto proto = msg.getAttrUpdate(); |
|||
subscriptionManagerService.onAttributesUpdate( |
|||
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), |
|||
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), |
|||
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); |
|||
} else { |
|||
throwNotHandled(msg, callback); |
|||
} |
|||
if (statsEnabled) { |
|||
stats.log(msg); |
|||
} |
|||
} |
|||
|
|||
private void forwardToStateService(DeviceStateServiceMsgProto deviceStateServiceMsg, TbCallback callback) { |
|||
if (statsEnabled) { |
|||
stats.log(deviceStateServiceMsg); |
|||
} |
|||
stateService.onQueueMsg(deviceStateServiceMsg, callback); |
|||
} |
|||
|
|||
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TbCallback callback) { |
|||
if (statsEnabled) { |
|||
stats.log(toDeviceActorMsg); |
|||
} |
|||
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback), ActorRef.noSender()); |
|||
} |
|||
|
|||
private void throwNotHandled(Object msg, TbCallback callback) { |
|||
log.warn("Message not handled: {}", msg); |
|||
callback.onFailure(new RuntimeException("Message not handled!")); |
|||
} |
|||
|
|||
@Override |
|||
protected void stopMainConsumers() { |
|||
if (mainConsumer != null) { |
|||
mainConsumer.unsubscribe(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -1,262 +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.queue; |
|||
|
|||
import akka.actor.ActorRef; |
|||
import com.google.protobuf.ProtocolStringList; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.scheduling.annotation.Scheduled; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbActorMsg; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.common.msg.queue.ServiceQueue; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; |
|||
import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; |
|||
import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; |
|||
import org.thingsboard.server.queue.util.TbRuleEngineComponent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategy; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory; |
|||
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Collections; |
|||
import java.util.HashSet; |
|||
import java.util.List; |
|||
import java.util.Optional; |
|||
import java.util.Set; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Service |
|||
@TbRuleEngineComponent |
|||
@Slf4j |
|||
public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService { |
|||
|
|||
@Value("${queue.rule-engine.poll-interval}") |
|||
private long pollDuration; |
|||
@Value("${queue.rule-engine.pack-processing-timeout}") |
|||
private long packProcessingTimeout; |
|||
@Value("${queue.rule-engine.stats.enabled:true}") |
|||
private boolean statsEnabled; |
|||
|
|||
private final TbRuleEngineSubmitStrategyFactory submitStrategyFactory; |
|||
private final TbRuleEngineProcessingStrategyFactory processingStrategyFactory; |
|||
private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory; |
|||
private final TbQueueRuleEngineSettings ruleEngineSettings; |
|||
private final RuleEngineStatisticsService statisticsService; |
|||
private final ConcurrentMap<String, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> consumers = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<String, TbRuleEngineQueueConfiguration> consumerConfigurations = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<String, TbRuleEngineConsumerStats> consumerStats = new ConcurrentHashMap<>(); |
|||
private ExecutorService submitExecutor; |
|||
|
|||
public DefaultTbRuleEngineConsumerService(TbRuleEngineProcessingStrategyFactory processingStrategyFactory, |
|||
TbRuleEngineSubmitStrategyFactory submitStrategyFactory, |
|||
TbQueueRuleEngineSettings ruleEngineSettings, |
|||
TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService, |
|||
ActorSystemContext actorContext, DataDecodingEncodingService encodingService) { |
|||
super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer()); |
|||
this.statisticsService = statisticsService; |
|||
this.ruleEngineSettings = ruleEngineSettings; |
|||
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; |
|||
this.submitStrategyFactory = submitStrategyFactory; |
|||
this.processingStrategyFactory = processingStrategyFactory; |
|||
} |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
super.init("tb-rule-engine-consumer", "tb-rule-engine-notifications-consumer"); |
|||
for (TbRuleEngineQueueConfiguration configuration : ruleEngineSettings.getQueues()) { |
|||
consumerConfigurations.putIfAbsent(configuration.getName(), configuration); |
|||
consumers.computeIfAbsent(configuration.getName(), queueName -> tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration)); |
|||
consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName())); |
|||
} |
|||
submitExecutor = Executors.newSingleThreadExecutor(); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
super.destroy(); |
|||
if (submitExecutor != null) { |
|||
submitExecutor.shutdownNow(); |
|||
} |
|||
ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config)); |
|||
} |
|||
|
|||
@Override |
|||
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
|||
if (partitionChangeEvent.getServiceType().equals(getServiceType())) { |
|||
ServiceQueue serviceQueue = partitionChangeEvent.getServiceQueueKey().getServiceQueue(); |
|||
log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), partitionChangeEvent.getPartitions()); |
|||
consumers.get(serviceQueue.getQueue()).subscribe(partitionChangeEvent.getPartitions()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void launchMainConsumers() { |
|||
consumers.forEach((queue, consumer) -> launchConsumer(consumer, consumerConfigurations.get(queue), consumerStats.get(queue))); |
|||
} |
|||
|
|||
@Override |
|||
protected void stopMainConsumers() { |
|||
consumers.values().forEach(TbQueueConsumer::unsubscribe); |
|||
} |
|||
|
|||
private void launchConsumer(TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer, TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats) { |
|||
consumersExecutor.execute(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<ToRuleEngineMsg>> msgs = consumer.poll(pollDuration); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
TbRuleEngineSubmitStrategy submitStrategy = submitStrategyFactory.newInstance(configuration.getName(), configuration.getSubmitStrategy()); |
|||
TbRuleEngineProcessingStrategy ackStrategy = processingStrategyFactory.newInstance(configuration.getName(), configuration.getProcessingStrategy()); |
|||
|
|||
submitStrategy.init(msgs); |
|||
|
|||
while (!stopped) { |
|||
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy); |
|||
submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> { |
|||
log.trace("[{}] Creating callback for message: {}", id, msg.getValue()); |
|||
ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); |
|||
TenantId tenantId = new TenantId(new UUID(toRuleEngineMsg.getTenantIdMSB(), toRuleEngineMsg.getTenantIdLSB())); |
|||
TbMsgCallback callback = new TbMsgPackCallback(id, tenantId, ctx); |
|||
try { |
|||
if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) { |
|||
forwardToRuleEngineActor(tenantId, toRuleEngineMsg, callback); |
|||
} else { |
|||
callback.onSuccess(); |
|||
} |
|||
} catch (Exception e) { |
|||
callback.onFailure(new RuleEngineException(e.getMessage())); |
|||
} |
|||
})); |
|||
|
|||
boolean timeout = false; |
|||
if (!ctx.await(configuration.getPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { |
|||
timeout = true; |
|||
} |
|||
|
|||
TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(timeout, ctx); |
|||
TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result); |
|||
if (statsEnabled) { |
|||
stats.log(result, decision.isCommit()); |
|||
} |
|||
if (decision.isCommit()) { |
|||
submitStrategy.stop(); |
|||
break; |
|||
} else { |
|||
submitStrategy.update(decision.getReprocessMap()); |
|||
} |
|||
} |
|||
consumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
log.warn("Failed to process 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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Rule Engine Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
protected ServiceType getServiceType() { |
|||
return ServiceType.TB_RULE_ENGINE; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPollDuration() { |
|||
return pollDuration; |
|||
} |
|||
|
|||
@Override |
|||
protected long getNotificationPackProcessingTimeout() { |
|||
return packProcessingTimeout; |
|||
} |
|||
|
|||
@Override |
|||
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception { |
|||
ToRuleEngineNotificationMsg nfMsg = msg.getValue(); |
|||
if (nfMsg.getComponentLifecycleMsg() != null && !nfMsg.getComponentLifecycleMsg().isEmpty()) { |
|||
Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray()); |
|||
if (actorMsg.isPresent()) { |
|||
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); |
|||
actorContext.tell(actorMsg.get(), ActorRef.noSender()); |
|||
} |
|||
callback.onSuccess(); |
|||
} else { |
|||
callback.onSuccess(); |
|||
} |
|||
} |
|||
|
|||
private void forwardToRuleEngineActor(TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) { |
|||
TbMsg tbMsg = TbMsg.fromBytes(toRuleEngineMsg.getTbMsg().toByteArray(), callback); |
|||
QueueToRuleEngineMsg msg; |
|||
ProtocolStringList relationTypesList = toRuleEngineMsg.getRelationTypesList(); |
|||
Set<String> relationTypes = null; |
|||
if (relationTypesList != null) { |
|||
if (relationTypesList.size() == 1) { |
|||
relationTypes = Collections.singleton(relationTypesList.get(0)); |
|||
} else { |
|||
relationTypes = new HashSet<>(relationTypesList); |
|||
} |
|||
} |
|||
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage()); |
|||
actorContext.tell(msg, ActorRef.noSender()); |
|||
} |
|||
|
|||
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}") |
|||
public void printStats() { |
|||
if (statsEnabled) { |
|||
long ts = System.currentTimeMillis(); |
|||
consumerStats.forEach((queue, stats) -> { |
|||
stats.printStats(); |
|||
statisticsService.reportQueueStats(ts, stats); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -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.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.Tenant; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.dao.tenant.TenantService; |
|||
import org.thingsboard.server.queue.discovery.TenantRoutingInfo; |
|||
import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; |
|||
|
|||
@Slf4j |
|||
@Service |
|||
@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core' || '${service.type:null}'=='tb-rule-engine'") |
|||
public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService { |
|||
|
|||
private final TenantService tenantService; |
|||
|
|||
public DefaultTenantRoutingInfoService(TenantService tenantService) { |
|||
this.tenantService = tenantService; |
|||
} |
|||
|
|||
@Override |
|||
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { |
|||
Tenant tenant = tenantService.findTenantById(tenantId); |
|||
if (tenant != null) { |
|||
return new TenantRoutingInfo(tenantId, tenant.isIsolatedTbCore(), tenant.isIsolatedTbRuleEngine()); |
|||
} else { |
|||
throw new RuntimeException("Tenant not found!"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,52 +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.queue; |
|||
|
|||
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; |
|||
import org.thingsboard.server.queue.TbQueueCallback; |
|||
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface TbClusterService { |
|||
|
|||
void pushMsgToCore(TopicPartitionInfo tpi, UUID msgKey, ToCoreMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToCore(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); |
|||
|
|||
void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, TransportProtos.ToRuleEngineMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToRuleEngine(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); |
|||
|
|||
void pushNotificationToTransport(String targetServiceId, ToTransportMsg response, TbQueueCallback callback); |
|||
|
|||
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); |
|||
|
|||
} |
|||
@ -1,23 +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.queue; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
|
|||
public interface TbCoreConsumerService extends ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
} |
|||
@ -1,48 +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.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.common.msg.queue.TbMsgCallback; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Slf4j |
|||
public class TbMsgPackCallback implements TbMsgCallback { |
|||
private final UUID id; |
|||
private final TenantId tenantId; |
|||
private final TbMsgPackProcessingContext ctx; |
|||
|
|||
public TbMsgPackCallback(UUID id, TenantId tenantId, TbMsgPackProcessingContext ctx) { |
|||
this.id = id; |
|||
this.tenantId = tenantId; |
|||
this.ctx = ctx; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess() { |
|||
log.trace("[{}] ON SUCCESS", id); |
|||
ctx.onSuccess(id); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(RuleEngineException e) { |
|||
log.trace("[{}] ON FAILURE", id, e); |
|||
ctx.onFailure(tenantId, id, e); |
|||
} |
|||
} |
|||
@ -1,84 +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.queue; |
|||
|
|||
import lombok.Getter; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
public class TbMsgPackProcessingContext { |
|||
|
|||
private final TbRuleEngineSubmitStrategy submitStrategy; |
|||
|
|||
private final AtomicInteger pendingCount; |
|||
private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> pendingMap; |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> successMap = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> failedMap = new ConcurrentHashMap<>(); |
|||
@Getter |
|||
private final ConcurrentMap<TenantId, RuleEngineException> exceptionsMap = new ConcurrentHashMap<>(); |
|||
|
|||
public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) { |
|||
this.submitStrategy = submitStrategy; |
|||
this.pendingMap = submitStrategy.getPendingMap(); |
|||
this.pendingCount = new AtomicInteger(pendingMap.size()); |
|||
} |
|||
|
|||
public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { |
|||
return processingTimeoutLatch.await(packProcessingTimeout, milliseconds); |
|||
} |
|||
|
|||
public void onSuccess(UUID id) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg; |
|||
boolean empty = false; |
|||
msg = pendingMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
successMap.put(id, msg); |
|||
submitStrategy.onSuccess(id); |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
|
|||
public void onFailure(TenantId tenantId, UUID id, RuleEngineException e) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg; |
|||
boolean empty = false; |
|||
msg = pendingMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
failedMap.put(id, msg); |
|||
exceptionsMap.putIfAbsent(tenantId, e); |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,90 +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.queue; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
public class TbPackProcessingContext<T> { |
|||
|
|||
private final AtomicInteger pendingCount; |
|||
private final CountDownLatch processingTimeoutLatch; |
|||
private final ConcurrentMap<UUID, T> ackMap; |
|||
private final ConcurrentMap<UUID, T> failedMap; |
|||
|
|||
public TbPackProcessingContext(CountDownLatch processingTimeoutLatch, |
|||
ConcurrentMap<UUID, T> ackMap, |
|||
ConcurrentMap<UUID, T> failedMap) { |
|||
this.processingTimeoutLatch = processingTimeoutLatch; |
|||
this.pendingCount = new AtomicInteger(ackMap.size()); |
|||
this.ackMap = ackMap; |
|||
this.failedMap = failedMap; |
|||
} |
|||
|
|||
public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { |
|||
return processingTimeoutLatch.await(packProcessingTimeout, milliseconds); |
|||
} |
|||
|
|||
public void onSuccess(UUID id) { |
|||
boolean empty = false; |
|||
T msg = ackMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} else { |
|||
if (log.isTraceEnabled()) { |
|||
log.trace("Items left: {}", ackMap.size()); |
|||
for (T t : ackMap.values()) { |
|||
log.trace("left item: {}", t); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void onFailure(UUID id, Throwable t) { |
|||
boolean empty = false; |
|||
T msg = ackMap.remove(id); |
|||
if (msg != null) { |
|||
empty = pendingCount.decrementAndGet() == 0; |
|||
failedMap.put(id, msg); |
|||
if (log.isTraceEnabled()) { |
|||
log.trace("Items left: {}", ackMap.size()); |
|||
for (T v : ackMap.values()) { |
|||
log.trace("left item: {}", v); |
|||
} |
|||
} |
|||
} |
|||
if (empty) { |
|||
processingTimeoutLatch.countDown(); |
|||
} |
|||
} |
|||
|
|||
public ConcurrentMap<UUID, T> getAckMap() { |
|||
return ackMap; |
|||
} |
|||
|
|||
public ConcurrentMap<UUID, T> getFailedMap() { |
|||
return failedMap; |
|||
} |
|||
} |
|||
@ -1,23 +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.queue; |
|||
|
|||
import org.springframework.context.ApplicationListener; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
|
|||
public interface TbRuleEngineConsumerService extends ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
} |
|||
@ -1,132 +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.queue; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.queue.RuleEngineException; |
|||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbRuleEngineConsumerStats { |
|||
|
|||
public static final String TOTAL_MSGS = "totalMsgs"; |
|||
public static final String SUCCESSFUL_MSGS = "successfulMsgs"; |
|||
public static final String TMP_TIMEOUT = "tmpTimeout"; |
|||
public static final String TMP_FAILED = "tmpFailed"; |
|||
public static final String TIMEOUT_MSGS = "timeoutMsgs"; |
|||
public static final String FAILED_MSGS = "failedMsgs"; |
|||
public static final String SUCCESSFUL_ITERATIONS = "successfulIterations"; |
|||
public static final String FAILED_ITERATIONS = "failedIterations"; |
|||
|
|||
private final AtomicInteger totalMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger successMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger successIterationsCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedIterationsCounter = new AtomicInteger(0); |
|||
|
|||
private final Map<String, AtomicInteger> counters = new HashMap<>(); |
|||
private final ConcurrentMap<UUID, TbTenantRuleEngineStats> tenantStats = new ConcurrentHashMap<>(); |
|||
private final ConcurrentMap<TenantId, RuleEngineException> tenantExceptions = new ConcurrentHashMap<>(); |
|||
|
|||
private final String queueName; |
|||
|
|||
public TbRuleEngineConsumerStats(String queueName) { |
|||
this.queueName = queueName; |
|||
counters.put(TOTAL_MSGS, totalMsgCounter); |
|||
counters.put(SUCCESSFUL_MSGS, successMsgCounter); |
|||
counters.put(TIMEOUT_MSGS, timeoutMsgCounter); |
|||
counters.put(FAILED_MSGS, failedMsgCounter); |
|||
|
|||
counters.put(TMP_TIMEOUT, tmpTimeoutMsgCounter); |
|||
counters.put(TMP_FAILED, tmpFailedMsgCounter); |
|||
counters.put(SUCCESSFUL_ITERATIONS, successIterationsCounter); |
|||
counters.put(FAILED_ITERATIONS, failedIterationsCounter); |
|||
} |
|||
|
|||
public void log(TbRuleEngineProcessingResult msg, boolean finalIterationForPack) { |
|||
int success = msg.getSuccessMap().size(); |
|||
int pending = msg.getPendingMap().size(); |
|||
int failed = msg.getFailedMap().size(); |
|||
totalMsgCounter.addAndGet(success + pending + failed); |
|||
successMsgCounter.addAndGet(success); |
|||
msg.getSuccessMap().values().forEach(m -> getTenantStats(m).logSuccess()); |
|||
if (finalIterationForPack) { |
|||
if (pending > 0 || failed > 0) { |
|||
timeoutMsgCounter.addAndGet(pending); |
|||
failedMsgCounter.addAndGet(failed); |
|||
if (pending > 0) { |
|||
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTimeout()); |
|||
} |
|||
if (failed > 0) { |
|||
msg.getFailedMap().values().forEach(m -> getTenantStats(m).logFailed()); |
|||
} |
|||
failedIterationsCounter.incrementAndGet(); |
|||
} else { |
|||
successIterationsCounter.incrementAndGet(); |
|||
} |
|||
} else { |
|||
failedIterationsCounter.incrementAndGet(); |
|||
tmpTimeoutMsgCounter.addAndGet(pending); |
|||
tmpFailedMsgCounter.addAndGet(failed); |
|||
if (pending > 0) { |
|||
msg.getPendingMap().values().forEach(m -> getTenantStats(m).logTmpTimeout()); |
|||
} |
|||
if (failed > 0) { |
|||
msg.getFailedMap().values().forEach(m -> getTenantStats(m).logTmpFailed()); |
|||
} |
|||
} |
|||
msg.getExceptionsMap().forEach(tenantExceptions::putIfAbsent); |
|||
} |
|||
|
|||
private TbTenantRuleEngineStats getTenantStats(TbProtoQueueMsg<ToRuleEngineMsg> m) { |
|||
ToRuleEngineMsg reMsg = m.getValue(); |
|||
return tenantStats.computeIfAbsent(new UUID(reMsg.getTenantIdMSB(), reMsg.getTenantIdLSB()), TbTenantRuleEngineStats::new); |
|||
} |
|||
|
|||
public void printStats() { |
|||
int total = totalMsgCounter.get(); |
|||
if (total > 0) { |
|||
StringBuilder stats = new StringBuilder(); |
|||
counters.forEach((label, value) -> { |
|||
stats.append(label).append(" = [").append(value.get()).append("] "); |
|||
}); |
|||
log.info("[{}] Stats: {}", queueName, stats); |
|||
} |
|||
} |
|||
|
|||
public void reset() { |
|||
counters.values().forEach(counter -> counter.set(0)); |
|||
tenantStats.clear(); |
|||
tenantExceptions.clear(); |
|||
} |
|||
} |
|||
@ -1,92 +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.queue; |
|||
|
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public class TbTenantRuleEngineStats { |
|||
|
|||
private final UUID tenantId; |
|||
|
|||
private final AtomicInteger totalMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger successMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpTimeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger tmpFailedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final AtomicInteger timeoutMsgCounter = new AtomicInteger(0); |
|||
private final AtomicInteger failedMsgCounter = new AtomicInteger(0); |
|||
|
|||
private final Map<String, AtomicInteger> counters = new HashMap<>(); |
|||
|
|||
public TbTenantRuleEngineStats(UUID tenantId) { |
|||
this.tenantId = tenantId; |
|||
counters.put(TbRuleEngineConsumerStats.TOTAL_MSGS, totalMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS, successMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.TIMEOUT_MSGS, timeoutMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.FAILED_MSGS, failedMsgCounter); |
|||
|
|||
counters.put(TbRuleEngineConsumerStats.TMP_TIMEOUT, tmpTimeoutMsgCounter); |
|||
counters.put(TbRuleEngineConsumerStats.TMP_FAILED, tmpFailedMsgCounter); |
|||
} |
|||
|
|||
public void logSuccess() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
successMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logFailed() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
failedMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTimeout() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
timeoutMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTmpFailed() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
tmpFailedMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void logTmpTimeout() { |
|||
totalMsgCounter.incrementAndGet(); |
|||
tmpTimeoutMsgCounter.incrementAndGet(); |
|||
} |
|||
|
|||
public void printStats() { |
|||
int total = totalMsgCounter.get(); |
|||
if (total > 0) { |
|||
StringBuilder stats = new StringBuilder(); |
|||
counters.forEach((label, value) -> { |
|||
stats.append(label).append(" = [").append(value.get()).append("]"); |
|||
}); |
|||
log.info("[{}] Stats: {}", tenantId, stats); |
|||
} |
|||
} |
|||
|
|||
public void reset() { |
|||
counters.values().forEach(counter -> counter.set(0)); |
|||
} |
|||
} |
|||
@ -1,149 +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.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
|||
import org.springframework.context.ApplicationListener; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
|||
import org.thingsboard.server.actors.ActorSystemContext; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TbCallback; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.TbQueueConsumer; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; |
|||
import org.thingsboard.server.service.encoding.DataDecodingEncodingService; |
|||
import org.thingsboard.server.service.queue.TbPackCallback; |
|||
import org.thingsboard.server.service.queue.TbPackProcessingContext; |
|||
|
|||
import javax.annotation.PreDestroy; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> implements ApplicationListener<PartitionChangeEvent> { |
|||
|
|||
protected volatile ExecutorService consumersExecutor; |
|||
protected volatile ExecutorService notificationsConsumerExecutor; |
|||
protected volatile boolean stopped = false; |
|||
|
|||
protected final ActorSystemContext actorContext; |
|||
protected final DataDecodingEncodingService encodingService; |
|||
|
|||
protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer; |
|||
|
|||
public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) { |
|||
this.actorContext = actorContext; |
|||
this.encodingService = encodingService; |
|||
this.nfConsumer = nfConsumer; |
|||
} |
|||
|
|||
public void init(String mainConsumerThreadName, String nfConsumerThreadName) { |
|||
this.consumersExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName(mainConsumerThreadName)); |
|||
this.notificationsConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(nfConsumerThreadName)); |
|||
} |
|||
|
|||
@EventListener(ApplicationReadyEvent.class) |
|||
public void onApplicationEvent(ApplicationReadyEvent event) { |
|||
log.info("Subscribing to notifications: {}", nfConsumer.getTopic()); |
|||
this.nfConsumer.subscribe(); |
|||
launchNotificationsConsumer(); |
|||
launchMainConsumers(); |
|||
} |
|||
|
|||
protected abstract ServiceType getServiceType(); |
|||
|
|||
protected abstract void launchMainConsumers(); |
|||
|
|||
protected abstract void stopMainConsumers(); |
|||
|
|||
protected abstract long getNotificationPollDuration(); |
|||
|
|||
protected abstract long getNotificationPackProcessingTimeout(); |
|||
|
|||
protected void launchNotificationsConsumer() { |
|||
notificationsConsumerExecutor.submit(() -> { |
|||
while (!stopped) { |
|||
try { |
|||
List<TbProtoQueueMsg<N>> msgs = nfConsumer.poll(getNotificationPollDuration()); |
|||
if (msgs.isEmpty()) { |
|||
continue; |
|||
} |
|||
ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = msgs.stream().collect( |
|||
Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); |
|||
CountDownLatch processingTimeoutLatch = new CountDownLatch(1); |
|||
TbPackProcessingContext<TbProtoQueueMsg<N>> ctx = new TbPackProcessingContext<>( |
|||
processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); |
|||
pendingMap.forEach((id, msg) -> { |
|||
log.trace("[{}] Creating notification callback for message: {}", id, msg.getValue()); |
|||
TbCallback callback = new TbPackCallback<>(id, ctx); |
|||
try { |
|||
handleNotification(id, msg, callback); |
|||
} catch (Throwable e) { |
|||
log.warn("[{}] Failed to process notification: {}", id, msg, e); |
|||
callback.onFailure(e); |
|||
} |
|||
}); |
|||
if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { |
|||
ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process notification: {}", id, msg.getValue())); |
|||
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process notification: {}", id, msg.getValue())); |
|||
} |
|||
nfConsumer.commit(); |
|||
} catch (Exception e) { |
|||
if (!stopped) { |
|||
log.warn("Failed to obtain notifications from queue.", e); |
|||
try { |
|||
Thread.sleep(getNotificationPollDuration()); |
|||
} catch (InterruptedException e2) { |
|||
log.trace("Failed to wait until the server has capacity to handle new notifications", e2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
log.info("TB Notifications Consumer stopped."); |
|||
}); |
|||
} |
|||
|
|||
protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception; |
|||
|
|||
@PreDestroy |
|||
public void destroy() { |
|||
stopped = true; |
|||
|
|||
stopMainConsumers(); |
|||
|
|||
if (nfConsumer != null) { |
|||
nfConsumer.unsubscribe(); |
|||
} |
|||
|
|||
if (consumersExecutor != null) { |
|||
consumersExecutor.shutdownNow(); |
|||
} |
|||
if (notificationsConsumerExecutor != null) { |
|||
notificationsConsumerExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,71 +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.queue.processing; |
|||
|
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.stream.Collectors; |
|||
|
|||
public abstract class AbstractTbRuleEngineSubmitStrategy implements TbRuleEngineSubmitStrategy { |
|||
|
|||
protected final String queueName; |
|||
protected List<IdMsgPair> orderedMsgList; |
|||
private volatile boolean stopped; |
|||
|
|||
public AbstractTbRuleEngineSubmitStrategy(String queueName) { |
|||
this.queueName = queueName; |
|||
} |
|||
|
|||
protected abstract void doOnSuccess(UUID id); |
|||
|
|||
@Override |
|||
public void init(List<TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgs) { |
|||
orderedMsgList = msgs.stream().map(msg -> new IdMsgPair(UUID.randomUUID(), msg)).collect(Collectors.toList()); |
|||
} |
|||
|
|||
@Override |
|||
public ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> getPendingMap() { |
|||
return orderedMsgList.stream().collect(Collectors.toConcurrentMap(pair -> pair.uuid, pair -> pair.msg)); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
List<IdMsgPair> newOrderedMsgList = new ArrayList<>(reprocessMap.size()); |
|||
for (IdMsgPair pair : orderedMsgList) { |
|||
if (reprocessMap.containsKey(pair.uuid)) { |
|||
newOrderedMsgList.add(pair); |
|||
} |
|||
} |
|||
orderedMsgList = newOrderedMsgList; |
|||
} |
|||
|
|||
@Override |
|||
public void onSuccess(UUID id) { |
|||
if (!stopped) { |
|||
doOnSuccess(id); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void stop() { |
|||
stopped = true; |
|||
} |
|||
} |
|||
@ -1,86 +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.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
import java.util.function.BiConsumer; |
|||
|
|||
@Slf4j |
|||
public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
private final int batchSize; |
|||
private final AtomicInteger packIdx = new AtomicInteger(0); |
|||
private final Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> pendingPack = new LinkedHashMap<>(); |
|||
private volatile BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer; |
|||
|
|||
public BatchTbRuleEngineSubmitStrategy(String queueName, int batchSize) { |
|||
super(queueName); |
|||
this.batchSize = batchSize; |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
this.msgConsumer = msgConsumer; |
|||
submitNext(); |
|||
} |
|||
|
|||
@Override |
|||
public void update(ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> reprocessMap) { |
|||
super.update(reprocessMap); |
|||
packIdx.set(0); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
boolean endOfPendingPack; |
|||
synchronized (pendingPack) { |
|||
TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg> msg = pendingPack.remove(id); |
|||
endOfPendingPack = msg != null && pendingPack.isEmpty(); |
|||
} |
|||
if (endOfPendingPack) { |
|||
packIdx.incrementAndGet(); |
|||
submitNext(); |
|||
} |
|||
} |
|||
|
|||
private void submitNext() { |
|||
int listSize = orderedMsgList.size(); |
|||
int startIdx = Math.min(packIdx.get() * batchSize, listSize); |
|||
int endIdx = Math.min(startIdx + batchSize, listSize); |
|||
synchronized (pendingPack) { |
|||
pendingPack.clear(); |
|||
for (int i = startIdx; i < endIdx; i++) { |
|||
IdMsgPair pair = orderedMsgList.get(i); |
|||
pendingPack.put(pair.uuid, pair.msg); |
|||
} |
|||
} |
|||
int submitSize = pendingPack.size(); |
|||
if (log.isInfoEnabled() && submitSize > 0) { |
|||
log.info("[{}] submitting [{}] messages to rule engine", queueName, submitSize); |
|||
} |
|||
pendingPack.forEach(msgConsumer); |
|||
} |
|||
|
|||
} |
|||
@ -1,50 +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.queue.processing; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentMap; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.function.BiConsumer; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Slf4j |
|||
public class BurstTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { |
|||
|
|||
public BurstTbRuleEngineSubmitStrategy(String queueName) { |
|||
super(queueName); |
|||
} |
|||
|
|||
@Override |
|||
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { |
|||
if (log.isInfoEnabled()) { |
|||
log.info("[{}] submitting [{}] messages to rule engine", queueName, orderedMsgList.size()); |
|||
} |
|||
orderedMsgList.forEach(pair -> msgConsumer.accept(pair.uuid, pair.msg)); |
|||
} |
|||
|
|||
@Override |
|||
protected void doOnSuccess(UUID id) { |
|||
|
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue