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