4 changed files with 199 additions and 176 deletions
@ -0,0 +1,148 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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.partition; |
||||
|
|
||||
|
import com.google.common.util.concurrent.ListeningScheduledExecutorService; |
||||
|
import com.google.common.util.concurrent.MoreExecutors; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.common.util.ThingsBoardThreadFactory; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.msg.queue.ServiceType; |
||||
|
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
||||
|
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; |
||||
|
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; |
||||
|
|
||||
|
import java.util.HashSet; |
||||
|
import java.util.Queue; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractPartitionBasedService<T extends EntityId> extends TbApplicationEventListener<PartitionChangeEvent> { |
||||
|
|
||||
|
protected final ConcurrentMap<TopicPartitionInfo, Set<T>> partitionedEntities = new ConcurrentHashMap<>(); |
||||
|
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>(); |
||||
|
|
||||
|
protected ListeningScheduledExecutorService scheduledExecutor; |
||||
|
|
||||
|
abstract protected String getSchedulerExecutorName(); |
||||
|
|
||||
|
abstract protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions); |
||||
|
|
||||
|
abstract protected void cleanupEntityOnPartitionRemoval(T entityId); |
||||
|
|
||||
|
public Set<T> getPartitionedEntities(TopicPartitionInfo tpi) { |
||||
|
return partitionedEntities.get(tpi); |
||||
|
} |
||||
|
|
||||
|
protected void init() { |
||||
|
// Should be always single threaded due to absence of locks.
|
||||
|
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state-scheduled"))); |
||||
|
} |
||||
|
|
||||
|
protected ServiceType getServiceType() { |
||||
|
return ServiceType.TB_CORE; |
||||
|
} |
||||
|
|
||||
|
protected void stop() { |
||||
|
if (scheduledExecutor != null) { |
||||
|
scheduledExecutor.shutdownNow(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* DiscoveryService will call this event from the single thread (one-by-one). |
||||
|
* Events order is guaranteed by DiscoveryService. |
||||
|
* The only concurrency is expected from the [main] thread on Application started. |
||||
|
* Async implementation. Locks is not allowed by design. |
||||
|
* Any locks or delays in this module will affect DiscoveryService and entire system |
||||
|
*/ |
||||
|
@Override |
||||
|
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
||||
|
if (getServiceType().equals(partitionChangeEvent.getServiceType())) { |
||||
|
log.debug("onTbApplicationEvent, processing event: {}", partitionChangeEvent); |
||||
|
subscribeQueue.add(partitionChangeEvent.getPartitions()); |
||||
|
scheduledExecutor.submit(this::pollInitStateFromDB); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void pollInitStateFromDB() { |
||||
|
final Set<TopicPartitionInfo> partitions = getLatestPartitions(); |
||||
|
if (partitions == null) { |
||||
|
log.debug("Nothing to do. Partitions are empty."); |
||||
|
return; |
||||
|
} |
||||
|
initStateFromDB(partitions); |
||||
|
} |
||||
|
|
||||
|
private void initStateFromDB(Set<TopicPartitionInfo> partitions) { |
||||
|
try { |
||||
|
log.info("CURRENT PARTITIONS: {}", partitionedEntities.keySet()); |
||||
|
log.info("NEW PARTITIONS: {}", partitions); |
||||
|
|
||||
|
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions); |
||||
|
addedPartitions.removeAll(partitionedEntities.keySet()); |
||||
|
|
||||
|
log.info("ADDED PARTITIONS: {}", addedPartitions); |
||||
|
|
||||
|
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedEntities.keySet()); |
||||
|
removedPartitions.removeAll(partitions); |
||||
|
|
||||
|
log.info("REMOVED PARTITIONS: {}", removedPartitions); |
||||
|
|
||||
|
// We no longer manage current partition of entities;
|
||||
|
removedPartitions.forEach(partition -> { |
||||
|
Set<T> entities = partitionedEntities.remove(partition); |
||||
|
entities.forEach(this::cleanupEntityOnPartitionRemoval); |
||||
|
}); |
||||
|
|
||||
|
onRepartitionEvent(); |
||||
|
|
||||
|
addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); |
||||
|
|
||||
|
if (!addedPartitions.isEmpty()) { |
||||
|
onAddedPartitions(addedPartitions); |
||||
|
} |
||||
|
|
||||
|
scheduledExecutor.submit(() -> { |
||||
|
log.info("Managing following partitions:"); |
||||
|
partitionedEntities.forEach((tpi, entities) -> { |
||||
|
log.info("[{}]: {} entities", tpi.getFullTopicName(), entities.size()); |
||||
|
}); |
||||
|
}); |
||||
|
} catch (Throwable t) { |
||||
|
log.warn("Failed to init entities state from DB", t); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void onRepartitionEvent() { |
||||
|
} |
||||
|
|
||||
|
private Set<TopicPartitionInfo> getLatestPartitions() { |
||||
|
log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size()); |
||||
|
Set<TopicPartitionInfo> partitions = null; |
||||
|
while (!subscribeQueue.isEmpty()) { |
||||
|
partitions = subscribeQueue.poll(); |
||||
|
log.debug("polled from the queue partitions {}", partitions); |
||||
|
} |
||||
|
log.debug("getLatestPartitionsFromQueue, partitions {}", partitions); |
||||
|
return partitions; |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue