13 changed files with 1 additions and 584 deletions
@ -1,111 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.transport.quota.tenant.TenantQuotaService; |
|||
import org.thingsboard.server.dao.queue.MsgQueue; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.ScheduledExecutorService; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class DefaultMsgQueueService implements MsgQueueService { |
|||
|
|||
@Value("${actors.rule.queue.max_size}") |
|||
private long queueMaxSize; |
|||
|
|||
@Value("${actors.rule.queue.cleanup_period}") |
|||
private long queueCleanUpPeriod; |
|||
|
|||
@Autowired |
|||
private MsgQueue msgQueue; |
|||
|
|||
@Autowired(required = false) |
|||
private TenantQuotaService quotaService; |
|||
|
|||
private ScheduledExecutorService cleanupExecutor; |
|||
|
|||
private Map<TenantId, AtomicLong> pendingCountPerTenant = new ConcurrentHashMap<>(); |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
if (queueCleanUpPeriod > 0) { |
|||
cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); |
|||
cleanupExecutor.scheduleAtFixedRate(() -> cleanup(), |
|||
queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS); |
|||
} |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (cleanupExecutor != null) { |
|||
cleanupExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|||
if(quotaService != null && quotaService.isQuotaExceeded(tenantId.getId().toString())) { |
|||
log.warn("Tenant TbMsg Quota exceeded for [{}:{}] . Reject", tenantId.getId()); |
|||
return Futures.immediateFailedFuture(new RuntimeException("Tenant TbMsg Quota exceeded")); |
|||
} |
|||
|
|||
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); |
|||
if (pendingMsgCount.incrementAndGet() < queueMaxSize) { |
|||
return msgQueue.put(tenantId, msg, nodeId, clusterPartition); |
|||
} else { |
|||
pendingMsgCount.decrementAndGet(); |
|||
return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|||
ListenableFuture<Void> result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition); |
|||
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); |
|||
pendingMsgCount.decrementAndGet(); |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
public Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { |
|||
return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition); |
|||
} |
|||
|
|||
private void cleanup() { |
|||
pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> { |
|||
pendingMsgCount.set(0); |
|||
msgQueue.cleanUp(tenantId); |
|||
}); |
|||
} |
|||
|
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface MsgQueueService { |
|||
|
|||
ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|||
|
|||
ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|||
|
|||
Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); |
|||
|
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.dao.queue; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public interface MsgQueue { |
|||
|
|||
ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|||
|
|||
ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|||
|
|||
Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); |
|||
|
|||
ListenableFuture<Void> cleanUp(TenantId tenantId); |
|||
|
|||
} |
|||
@ -1,152 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.dao.queue; |
|||
|
|||
import com.datastax.driver.core.Cluster; |
|||
import com.datastax.driver.core.HostDistance; |
|||
import com.datastax.driver.core.PoolingOptions; |
|||
import com.datastax.driver.core.Session; |
|||
import com.datastax.driver.core.utils.UUIDs; |
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import com.google.common.util.concurrent.Futures; |
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.CommandLineRunner; |
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.RuleNodeId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
|
|||
import javax.annotation.Nullable; |
|||
import java.net.InetSocketAddress; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.CountDownLatch; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicLong; |
|||
|
|||
//@SpringBootApplication
|
|||
//@EnableAutoConfiguration
|
|||
//@ComponentScan({"org.thingsboard.rule.engine"})
|
|||
//@PropertySource("classpath:processing-pipeline.properties")
|
|||
@Slf4j |
|||
public class QueueBenchmark implements CommandLineRunner { |
|||
|
|||
public static void main(String[] args) { |
|||
try { |
|||
SpringApplication.run(QueueBenchmark.class, args); |
|||
} catch (Throwable th) { |
|||
th.printStackTrace(); |
|||
System.exit(0); |
|||
} |
|||
} |
|||
|
|||
@Autowired |
|||
private MsgQueue msgQueue; |
|||
|
|||
@Override |
|||
public void run(String... strings) throws Exception { |
|||
System.out.println("It works + " + msgQueue); |
|||
|
|||
|
|||
long start = System.currentTimeMillis(); |
|||
int msgCount = 10000000; |
|||
AtomicLong count = new AtomicLong(0); |
|||
ExecutorService service = Executors.newFixedThreadPool(100); |
|||
|
|||
CountDownLatch latch = new CountDownLatch(msgCount); |
|||
for (int i = 0; i < msgCount; i++) { |
|||
service.submit(() -> { |
|||
boolean isFinished = false; |
|||
while (!isFinished) { |
|||
try { |
|||
TbMsg msg = randomMsg(); |
|||
UUID nodeId = UUIDs.timeBased(); |
|||
ListenableFuture<Void> put = msgQueue.put(new TenantId(EntityId.NULL_UUID), msg, nodeId, 100L); |
|||
// ListenableFuture<Void> put = msgQueue.ack(msg, nodeId, 100L);
|
|||
Futures.addCallback(put, new FutureCallback<Void>() { |
|||
@Override |
|||
public void onSuccess(@Nullable Void result) { |
|||
latch.countDown(); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
// t.printStackTrace();
|
|||
System.out.println("onFailure, because:" + t.getMessage()); |
|||
latch.countDown(); |
|||
} |
|||
}); |
|||
isFinished = true; |
|||
} catch (Throwable th) { |
|||
// th.printStackTrace();
|
|||
System.out.println("Repeat query, because:" + th.getMessage()); |
|||
// latch.countDown();
|
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
long prev = 0L; |
|||
while (latch.getCount() != 0) { |
|||
TimeUnit.SECONDS.sleep(1); |
|||
long curr = latch.getCount(); |
|||
long rps = prev - curr; |
|||
prev = curr; |
|||
System.out.println("rps = " + rps); |
|||
} |
|||
|
|||
long end = System.currentTimeMillis(); |
|||
System.out.println("final rps = " + (msgCount / (end - start) * 1000)); |
|||
|
|||
System.out.println("Finished"); |
|||
|
|||
} |
|||
|
|||
private TbMsg randomMsg() { |
|||
TbMsgMetaData metaData = new TbMsgMetaData(); |
|||
metaData.putValue("key", "value"); |
|||
String dataStr = "someContent"; |
|||
return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr, new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); |
|||
} |
|||
|
|||
@Bean |
|||
public Session session() { |
|||
Cluster thingsboard = Cluster.builder() |
|||
.addContactPointsWithPorts(new InetSocketAddress("127.0.0.1", 9042)) |
|||
.withClusterName("thingsboard") |
|||
// .withSocketOptions(socketOpts.getOpts())
|
|||
.withPoolingOptions(new PoolingOptions() |
|||
.setMaxRequestsPerConnection(HostDistance.LOCAL, 32768) |
|||
.setMaxRequestsPerConnection(HostDistance.REMOTE, 32768)).build(); |
|||
|
|||
Session session = thingsboard.connect("thingsboard"); |
|||
return session; |
|||
} |
|||
|
|||
@Bean |
|||
public int defaultTtl() { |
|||
return 6000; |
|||
} |
|||
|
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.dao.queue.memory; |
|||
|
|||
import lombok.Data; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
/** |
|||
* Created by ashvayka on 30.04.18. |
|||
*/ |
|||
@Data |
|||
public final class InMemoryMsgKey { |
|||
final UUID nodeId; |
|||
final long clusterPartition; |
|||
} |
|||
@ -1,123 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2018 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.dao.queue.memory; |
|||
|
|||
import com.google.common.util.concurrent.ListenableFuture; |
|||
import com.google.common.util.concurrent.ListeningExecutorService; |
|||
import com.google.common.util.concurrent.MoreExecutors; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.stereotype.Component; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.dao.queue.MsgQueue; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import javax.annotation.PreDestroy; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import java.util.concurrent.ExecutionException; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
/** |
|||
* Created by ashvayka on 27.04.18. |
|||
*/ |
|||
@Component |
|||
@ConditionalOnProperty(prefix = "actors.rule.queue", value = "type", havingValue = "memory", matchIfMissing = true) |
|||
@Slf4j |
|||
public class InMemoryMsgQueue implements MsgQueue { |
|||
|
|||
private ListeningExecutorService queueExecutor; |
|||
private Map<TenantId, Map<InMemoryMsgKey, Map<UUID, TbMsg>>> data = new HashMap<>(); |
|||
|
|||
@PostConstruct |
|||
public void init() { |
|||
// Should be always single threaded due to absence of locks.
|
|||
queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); |
|||
} |
|||
|
|||
@PreDestroy |
|||
public void stop() { |
|||
if (queueExecutor != null) { |
|||
queueExecutor.shutdownNow(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|||
return queueExecutor.submit(() -> { |
|||
data.computeIfAbsent(tenantId, key -> new HashMap<>()). |
|||
computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|||
return queueExecutor.submit(() -> { |
|||
Map<InMemoryMsgKey, Map<UUID, TbMsg>> tenantMap = data.get(tenantId); |
|||
if (tenantMap != null) { |
|||
InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); |
|||
Map<UUID, TbMsg> map = tenantMap.get(key); |
|||
if (map != null) { |
|||
map.remove(msg.getId()); |
|||
if (map.isEmpty()) { |
|||
tenantMap.remove(key); |
|||
} |
|||
} |
|||
if (tenantMap.isEmpty()) { |
|||
data.remove(tenantId); |
|||
} |
|||
} |
|||
return null; |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { |
|||
ListenableFuture<List<TbMsg>> list = queueExecutor.submit(() -> { |
|||
Map<InMemoryMsgKey, Map<UUID, TbMsg>> tenantMap = data.get(tenantId); |
|||
if (tenantMap != null) { |
|||
InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); |
|||
Map<UUID, TbMsg> map = tenantMap.get(key); |
|||
if (map != null) { |
|||
return new ArrayList<>(map.values()); |
|||
} else { |
|||
return Collections.emptyList(); |
|||
} |
|||
} else { |
|||
return Collections.emptyList(); |
|||
} |
|||
}); |
|||
try { |
|||
return list.get(); |
|||
} catch (InterruptedException | ExecutionException e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public ListenableFuture<Void> cleanUp(TenantId tenantId) { |
|||
return queueExecutor.submit(() -> { |
|||
data.remove(tenantId); |
|||
return null; |
|||
}); |
|||
} |
|||
} |
|||
@ -1,7 +1,2 @@ |
|||
database.entities.type=cassandra |
|||
database.ts.type=cassandra |
|||
|
|||
cassandra.queue.partitioning=HOURS |
|||
cassandra.queue.ack.ttl=3600 |
|||
cassandra.queue.msg.ttl=3600 |
|||
cassandra.queue.partitions.ttl=3600 |
|||
Loading…
Reference in new issue