Transport Profile cache

This commit is contained in:
Andrii Shvaika 2020-09-04 15:35:50 +03:00
parent e67946e47f
commit 0245ff24a8
3 changed files with 130 additions and 55 deletions

View File

@ -0,0 +1,35 @@
/**
* 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.common.transport;
import com.google.protobuf.ByteString;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import java.util.Optional;
public interface TransportProfileCache {
DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody);
DeviceProfile get(DeviceProfileId id);
void put(DeviceProfile profile);
DeviceProfile put(ByteString profileBody);
}

View File

@ -0,0 +1,77 @@
/**
* 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.common.transport.service;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.transport.TransportProfileCache;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Slf4j
@Component
@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
public class DefaultTransportProfileCache implements TransportProfileCache {
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>();
private final DataDecodingEncodingService dataDecodingEncodingService;
public DefaultTransportProfileCache(DataDecodingEncodingService dataDecodingEncodingService) {
this.dataDecodingEncodingService = dataDecodingEncodingService;
}
@Override
public DeviceProfile getOrCreate(DeviceProfileId id, ByteString profileBody) {
DeviceProfile profile = deviceProfiles.get(id);
if (profile == null) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray());
if (deviceProfile.isPresent()) {
profile = deviceProfile.get();
deviceProfiles.put(id, profile);
}
}
return profile;
}
@Override
public DeviceProfile get(DeviceProfileId id) {
return deviceProfiles.get(id);
}
@Override
public void put(DeviceProfile profile) {
deviceProfiles.put(profile.getId(), profile);
}
@Override
public DeviceProfile put(ByteString profileBody) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray());
if (deviceProfile.isPresent()) {
put(deviceProfile.get());
return deviceProfile.get();
} else {
return null;
}
}
}

View File

@ -43,6 +43,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportProfileCache;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
@ -121,8 +122,7 @@ public class DefaultTransportService implements TransportService {
private final PartitionService partitionService; private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
private final DataDecodingEncodingService dataDecodingEncodingService; private final TransportProfileCache transportProfileCache;
protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate; protected TbQueueRequestTemplate<TbProtoQueueMsg<TransportApiRequestMsg>, TbProtoQueueMsg<TransportApiResponseMsg>> transportApiRequestTemplate;
protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer; protected TbQueueProducer<TbProtoQueueMsg<ToRuleEngineMsg>> ruleEngineMsgProducer;
@ -141,7 +141,6 @@ public class DefaultTransportService implements TransportService {
//TODO 3.2: @ybondarenko Implement cleanup of this maps. //TODO 3.2: @ybondarenko Implement cleanup of this maps.
private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceId, TbRateLimits> perDeviceLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<DeviceId, TbRateLimits> perDeviceLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>();
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer"));
private volatile boolean stopped = false; private volatile boolean stopped = false;
@ -151,13 +150,13 @@ public class DefaultTransportService implements TransportService {
TbQueueProducerProvider producerProvider, TbQueueProducerProvider producerProvider,
PartitionService partitionService, PartitionService partitionService,
StatsFactory statsFactory, StatsFactory statsFactory,
DataDecodingEncodingService dataDecodingEncodingService) { TransportProfileCache transportProfileCache) {
this.serviceInfoProvider = serviceInfoProvider; this.serviceInfoProvider = serviceInfoProvider;
this.queueProvider = queueProvider; this.queueProvider = queueProvider;
this.producerProvider = producerProvider; this.producerProvider = producerProvider;
this.partitionService = partitionService; this.partitionService = partitionService;
this.statsFactory = statsFactory; this.statsFactory = statsFactory;
this.dataDecodingEncodingService = dataDecodingEncodingService; this.transportProfileCache = transportProfileCache;
} }
@PostConstruct @PostConstruct
@ -276,14 +275,7 @@ public class DefaultTransportService implements TransportService {
result.deviceInfo(tdi); result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody(); ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) { if (profileBody != null && !profileBody.isEmpty()) {
DeviceProfile profile = deviceProfiles.get(tdi.getDeviceProfileId()); DeviceProfile profile = transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody);
if (profile == null) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray());
if (deviceProfile.isPresent()) {
profile = deviceProfile.get();
deviceProfiles.put(tdi.getDeviceProfileId(), profile);
}
}
if (transportType != DeviceTransportType.DEFAULT if (transportType != DeviceTransportType.DEFAULT
&& profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) {
log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType); log.debug("[{}] Device profile [{}] has different transport type: {}, expected: {}", tdi.getDeviceId(), tdi.getDeviceProfileId(), profile.getTransportType(), transportType);
@ -309,15 +301,7 @@ public class DefaultTransportService implements TransportService {
result.deviceInfo(tdi); result.deviceInfo(tdi);
ByteString profileBody = msg.getProfileBody(); ByteString profileBody = msg.getProfileBody();
if (profileBody != null && !profileBody.isEmpty()) { if (profileBody != null && !profileBody.isEmpty()) {
DeviceProfile profile = deviceProfiles.get(tdi.getDeviceProfileId()); result.deviceProfile(transportProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody));
if (profile == null) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray());
if (deviceProfile.isPresent()) {
profile = deviceProfile.get();
deviceProfiles.put(tdi.getDeviceProfileId(), profile);
}
}
result.deviceProfile(profile);
} }
} }
return result.build(); return result.build();
@ -629,8 +613,10 @@ public class DefaultTransportService implements TransportService {
} }
} else { } else {
if (toSessionMsg.hasDeviceProfileUpdateMsg()) { if (toSessionMsg.hasDeviceProfileUpdateMsg()) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(toSessionMsg.getDeviceProfileUpdateMsg().getData().toByteArray()); DeviceProfile deviceProfile = transportProfileCache.put(toSessionMsg.getDeviceProfileUpdateMsg().getData());
deviceProfile.ifPresent(this::onProfileUpdate); if (deviceProfile != null) {
onProfileUpdate(deviceProfile);
}
} else { } else {
//TODO: should we notify the device actor about missed session? //TODO: should we notify the device actor about missed session?
log.debug("[{}] Missing session.", sessionId); log.debug("[{}] Missing session.", sessionId);
@ -640,7 +626,7 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback) { public void getDeviceProfile(DeviceProfileId deviceProfileId, TransportServiceCallback<DeviceProfile> callback) {
DeviceProfile deviceProfile = deviceProfiles.get(deviceProfileId); DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId);
if (deviceProfile != null) { if (deviceProfile != null) {
callback.onSuccess(deviceProfile); callback.onSuccess(deviceProfile);
} else { } else {
@ -653,14 +639,13 @@ public class DefaultTransportService implements TransportService {
TransportApiRequestMsg.newBuilder().setGetDeviceProfileRequestMsg(msg).build()); TransportApiRequestMsg.newBuilder().setGetDeviceProfileRequestMsg(msg).build());
AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg),
response -> { response -> {
byte[] devProfileBody = response.getValue().getGetDeviceProfileResponseMsg().getData().toByteArray(); ByteString devProfileBody = response.getValue().getGetDeviceProfileResponseMsg().getData();
if (devProfileBody != null && devProfileBody.length > 0) { if (devProfileBody != null && !devProfileBody.isEmpty()) {
Optional<DeviceProfile> deviceProfileOpt = dataDecodingEncodingService.decode(devProfileBody); DeviceProfile profile = transportProfileCache.put(devProfileBody);
if (deviceProfileOpt.isPresent()) { if (profile != null) {
deviceProfiles.put(deviceProfileOpt.get().getId(), deviceProfile); callback.onSuccess(profile);
callback.onSuccess(deviceProfileOpt.get());
} else { } else {
log.warn("Failed to decode device profile: {}", Arrays.toString(devProfileBody)); log.warn("Failed to decode device profile: {}", devProfileBody);
callback.onError(new IllegalArgumentException("Failed to decode device profile!")); callback.onError(new IllegalArgumentException("Failed to decode device profile!"));
} }
} else { } else {
@ -673,7 +658,6 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void onProfileUpdate(DeviceProfile deviceProfile) { public void onProfileUpdate(DeviceProfile deviceProfile) {
deviceProfiles.put(deviceProfile.getId(), deviceProfile);
long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits();
long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits();
sessions.forEach((id, md) -> { sessions.forEach((id, md) -> {
@ -736,7 +720,7 @@ public class DefaultTransportService implements TransportService {
private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB()));
DeviceProfile deviceProfile = deviceProfiles.get(deviceProfileId); DeviceProfile deviceProfile = transportProfileCache.get(deviceProfileId);
RuleChainId ruleChainId; RuleChainId ruleChainId;
if (deviceProfile == null) { if (deviceProfile == null) {
log.warn("[{}] Device profile is null!", deviceProfileId); log.warn("[{}] Device profile is null!", deviceProfileId);
@ -747,27 +731,6 @@ public class DefaultTransportService implements TransportService {
return ruleChainId; return ruleChainId;
} }
private <T extends com.google.protobuf.GeneratedMessageV3> ListenableFuture<TbProtoQueueMsg<T>> extractProfile(ListenableFuture<TbProtoQueueMsg<T>> send,
Function<T, Boolean> hasDeviceInfo,
Function<T, TransportProtos.DeviceInfoProto> deviceInfoF,
Function<T, ByteString> profileBodyF) {
return Futures.transform(send, response -> {
T value = response.getValue();
if (hasDeviceInfo.apply(value)) {
TransportProtos.DeviceInfoProto deviceInfo = deviceInfoF.apply(value);
ByteString profileBody = profileBodyF.apply(value);
if (profileBody != null && !profileBody.isEmpty()) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceInfo.getDeviceProfileIdMSB(), deviceInfo.getDeviceProfileIdLSB()));
if (!deviceProfiles.containsKey(deviceProfileId)) {
Optional<DeviceProfile> deviceProfile = dataDecodingEncodingService.decode(profileBody.toByteArray());
deviceProfile.ifPresent(profile -> deviceProfiles.put(deviceProfileId, profile));
}
}
}
return response;
}, transportCallbackExecutor);
}
private class TransportTbQueueCallback implements TbQueueCallback { private class TransportTbQueueCallback implements TbQueueCallback {
private final TransportServiceCallback<Void> callback; private final TransportServiceCallback<Void> callback;