Commit 7f1f29877449ecedfd448c3d0f628acc1c39a8a9
Committed by
Andrew Shvayka
1 parent
a852e11a
cassandra buffered rate executor: separate beans for read and write operations
Showing
9 changed files
with
219 additions
and
87 deletions
@@ -59,7 +59,8 @@ import org.thingsboard.server.dao.edge.EdgeEventService; | @@ -59,7 +59,8 @@ import org.thingsboard.server.dao.edge.EdgeEventService; | ||
59 | import org.thingsboard.server.dao.edge.EdgeService; | 59 | import org.thingsboard.server.dao.edge.EdgeService; |
60 | import org.thingsboard.server.dao.entityview.EntityViewService; | 60 | import org.thingsboard.server.dao.entityview.EntityViewService; |
61 | import org.thingsboard.server.dao.event.EventService; | 61 | import org.thingsboard.server.dao.event.EventService; |
62 | -import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | 62 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; |
63 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; | ||
63 | import org.thingsboard.server.dao.ota.OtaPackageService; | 64 | import org.thingsboard.server.dao.ota.OtaPackageService; |
64 | import org.thingsboard.server.dao.relation.RelationService; | 65 | import org.thingsboard.server.dao.relation.RelationService; |
65 | import org.thingsboard.server.dao.resource.ResourceService; | 66 | import org.thingsboard.server.dao.resource.ResourceService; |
@@ -85,7 +86,6 @@ import org.thingsboard.server.cluster.TbClusterService; | @@ -85,7 +86,6 @@ import org.thingsboard.server.cluster.TbClusterService; | ||
85 | import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; | 86 | import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
86 | import org.thingsboard.server.service.rpc.TbRpcService; | 87 | import org.thingsboard.server.service.rpc.TbRpcService; |
87 | import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; | 88 | import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; |
88 | -import org.thingsboard.server.service.script.JsExecutorService; | ||
89 | import org.thingsboard.server.service.script.JsInvokeService; | 89 | import org.thingsboard.server.service.script.JsInvokeService; |
90 | import org.thingsboard.server.service.session.DeviceSessionCacheService; | 90 | import org.thingsboard.server.service.session.DeviceSessionCacheService; |
91 | import org.thingsboard.server.service.sms.SmsExecutorService; | 91 | import org.thingsboard.server.service.sms.SmsExecutorService; |
@@ -421,7 +421,11 @@ public class ActorSystemContext { | @@ -421,7 +421,11 @@ public class ActorSystemContext { | ||
421 | 421 | ||
422 | @Autowired(required = false) | 422 | @Autowired(required = false) |
423 | @Getter | 423 | @Getter |
424 | - private CassandraBufferedRateExecutor cassandraBufferedRateExecutor; | 424 | + private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; |
425 | + | ||
426 | + @Autowired(required = false) | ||
427 | + @Getter | ||
428 | + private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; | ||
425 | 429 | ||
426 | @Autowired(required = false) | 430 | @Autowired(required = false) |
427 | @Getter | 431 | @Getter |
@@ -557,8 +557,13 @@ class DefaultTbContext implements TbContext { | @@ -557,8 +557,13 @@ class DefaultTbContext implements TbContext { | ||
557 | } | 557 | } |
558 | 558 | ||
559 | @Override | 559 | @Override |
560 | - public TbResultSetFuture submitCassandraTask(CassandraStatementTask task) { | ||
561 | - return mainCtx.getCassandraBufferedRateExecutor().submit(task); | 560 | + public TbResultSetFuture submitCassandraReadTask(CassandraStatementTask task) { |
561 | + return mainCtx.getCassandraBufferedRateReadExecutor().submit(task); | ||
562 | + } | ||
563 | + | ||
564 | + @Override | ||
565 | + public TbResultSetFuture submitCassandraWriteTask(CassandraStatementTask task) { | ||
566 | + return mainCtx.getCassandraBufferedRateWriteExecutor().submit(task); | ||
562 | } | 567 | } |
563 | 568 | ||
564 | @Override | 569 | @Override |
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Qualifier; | @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Qualifier; | ||
26 | import org.thingsboard.server.common.data.id.TenantId; | 26 | import org.thingsboard.server.common.data.id.TenantId; |
27 | import org.thingsboard.server.dao.cassandra.CassandraCluster; | 27 | import org.thingsboard.server.dao.cassandra.CassandraCluster; |
28 | import org.thingsboard.server.dao.cassandra.guava.GuavaSession; | 28 | import org.thingsboard.server.dao.cassandra.guava.GuavaSession; |
29 | +import org.thingsboard.server.dao.util.BufferedRateExecutor; | ||
29 | 30 | ||
30 | import java.util.concurrent.ConcurrentHashMap; | 31 | import java.util.concurrent.ConcurrentHashMap; |
31 | import java.util.concurrent.ConcurrentMap; | 32 | import java.util.concurrent.ConcurrentMap; |
@@ -40,7 +41,10 @@ public abstract class CassandraAbstractDao { | @@ -40,7 +41,10 @@ public abstract class CassandraAbstractDao { | ||
40 | private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); | 41 | private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); |
41 | 42 | ||
42 | @Autowired | 43 | @Autowired |
43 | - private CassandraBufferedRateExecutor rateLimiter; | 44 | + private CassandraBufferedRateReadExecutor rateReadLimiter; |
45 | + | ||
46 | + @Autowired | ||
47 | + private CassandraBufferedRateWriteExecutor rateWriteLimiter; | ||
44 | 48 | ||
45 | private GuavaSession session; | 49 | private GuavaSession session; |
46 | 50 | ||
@@ -61,36 +65,38 @@ public abstract class CassandraAbstractDao { | @@ -61,36 +65,38 @@ public abstract class CassandraAbstractDao { | ||
61 | } | 65 | } |
62 | 66 | ||
63 | protected AsyncResultSet executeRead(TenantId tenantId, Statement statement) { | 67 | protected AsyncResultSet executeRead(TenantId tenantId, Statement statement) { |
64 | - return execute(tenantId, statement, defaultReadLevel); | 68 | + return execute(tenantId, statement, defaultReadLevel, rateReadLimiter); |
65 | } | 69 | } |
66 | 70 | ||
67 | protected AsyncResultSet executeWrite(TenantId tenantId, Statement statement) { | 71 | protected AsyncResultSet executeWrite(TenantId tenantId, Statement statement) { |
68 | - return execute(tenantId, statement, defaultWriteLevel); | 72 | + return execute(tenantId, statement, defaultWriteLevel, rateWriteLimiter); |
69 | } | 73 | } |
70 | 74 | ||
71 | protected TbResultSetFuture executeAsyncRead(TenantId tenantId, Statement statement) { | 75 | protected TbResultSetFuture executeAsyncRead(TenantId tenantId, Statement statement) { |
72 | - return executeAsync(tenantId, statement, defaultReadLevel); | 76 | + return executeAsync(tenantId, statement, defaultReadLevel, rateReadLimiter); |
73 | } | 77 | } |
74 | 78 | ||
75 | protected TbResultSetFuture executeAsyncWrite(TenantId tenantId, Statement statement) { | 79 | protected TbResultSetFuture executeAsyncWrite(TenantId tenantId, Statement statement) { |
76 | - return executeAsync(tenantId, statement, defaultWriteLevel); | 80 | + return executeAsync(tenantId, statement, defaultWriteLevel, rateWriteLimiter); |
77 | } | 81 | } |
78 | 82 | ||
79 | - private AsyncResultSet execute(TenantId tenantId, Statement statement, ConsistencyLevel level) { | 83 | + private AsyncResultSet execute(TenantId tenantId, Statement statement, ConsistencyLevel level, |
84 | + BufferedRateExecutor<CassandraStatementTask, TbResultSetFuture> rateExecutor) { | ||
80 | if (log.isDebugEnabled()) { | 85 | if (log.isDebugEnabled()) { |
81 | log.debug("Execute cassandra statement {}", statementToString(statement)); | 86 | log.debug("Execute cassandra statement {}", statementToString(statement)); |
82 | } | 87 | } |
83 | - return executeAsync(tenantId, statement, level).getUninterruptibly(); | 88 | + return executeAsync(tenantId, statement, level, rateExecutor).getUninterruptibly(); |
84 | } | 89 | } |
85 | 90 | ||
86 | - private TbResultSetFuture executeAsync(TenantId tenantId, Statement statement, ConsistencyLevel level) { | 91 | + private TbResultSetFuture executeAsync(TenantId tenantId, Statement statement, ConsistencyLevel level, |
92 | + BufferedRateExecutor<CassandraStatementTask, TbResultSetFuture> rateExecutor) { | ||
87 | if (log.isDebugEnabled()) { | 93 | if (log.isDebugEnabled()) { |
88 | log.debug("Execute cassandra async statement {}", statementToString(statement)); | 94 | log.debug("Execute cassandra async statement {}", statementToString(statement)); |
89 | } | 95 | } |
90 | if (statement.getConsistencyLevel() == null) { | 96 | if (statement.getConsistencyLevel() == null) { |
91 | statement.setConsistencyLevel(level); | 97 | statement.setConsistencyLevel(level); |
92 | } | 98 | } |
93 | - return rateLimiter.submit(new CassandraStatementTask(tenantId, getSession(), statement)); | 99 | + return rateExecutor.submit(new CassandraStatementTask(tenantId, getSession(), statement)); |
94 | } | 100 | } |
95 | 101 | ||
96 | private static String statementToString(Statement statement) { | 102 | private static String statementToString(Statement statement) { |
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java
renamed from
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java
@@ -22,9 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -22,9 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
22 | import org.springframework.beans.factory.annotation.Value; | 22 | import org.springframework.beans.factory.annotation.Value; |
23 | import org.springframework.scheduling.annotation.Scheduled; | 23 | import org.springframework.scheduling.annotation.Scheduled; |
24 | import org.springframework.stereotype.Component; | 24 | import org.springframework.stereotype.Component; |
25 | -import org.thingsboard.server.common.data.id.TenantId; | ||
26 | -import org.thingsboard.server.common.stats.DefaultCounter; | ||
27 | -import org.thingsboard.server.common.stats.StatsCounter; | ||
28 | import org.thingsboard.server.common.stats.StatsFactory; | 25 | import org.thingsboard.server.common.stats.StatsFactory; |
29 | import org.thingsboard.server.dao.entity.EntityService; | 26 | import org.thingsboard.server.dao.entity.EntityService; |
30 | import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; | 27 | import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; |
@@ -32,8 +29,6 @@ import org.thingsboard.server.dao.util.AsyncTaskContext; | @@ -32,8 +29,6 @@ import org.thingsboard.server.dao.util.AsyncTaskContext; | ||
32 | import org.thingsboard.server.dao.util.NoSqlAnyDao; | 29 | import org.thingsboard.server.dao.util.NoSqlAnyDao; |
33 | 30 | ||
34 | import javax.annotation.PreDestroy; | 31 | import javax.annotation.PreDestroy; |
35 | -import java.util.HashMap; | ||
36 | -import java.util.Map; | ||
37 | 32 | ||
38 | /** | 33 | /** |
39 | * Created by ashvayka on 24.10.18. | 34 | * Created by ashvayka on 24.10.18. |
@@ -41,15 +36,11 @@ import java.util.Map; | @@ -41,15 +36,11 @@ import java.util.Map; | ||
41 | @Component | 36 | @Component |
42 | @Slf4j | 37 | @Slf4j |
43 | @NoSqlAnyDao | 38 | @NoSqlAnyDao |
44 | -public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { | 39 | +public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { |
45 | 40 | ||
46 | - @Autowired | ||
47 | - private EntityService entityService; | ||
48 | - private Map<TenantId, String> tenantNamesCache = new HashMap<>(); | 41 | + static final String BUFFER_NAME = "Read"; |
49 | 42 | ||
50 | - private boolean printTenantNames; | ||
51 | - | ||
52 | - public CassandraBufferedRateExecutor( | 43 | + public CassandraBufferedRateReadExecutor( |
53 | @Value("${cassandra.query.buffer_size}") int queueLimit, | 44 | @Value("${cassandra.query.buffer_size}") int queueLimit, |
54 | @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit, | 45 | @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit, |
55 | @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime, | 46 | @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime, |
@@ -60,57 +51,16 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | @@ -60,57 +51,16 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | ||
60 | @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, | 51 | @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, |
61 | @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, | 52 | @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, |
62 | @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, | 53 | @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, |
63 | - @Autowired StatsFactory statsFactory) { | ||
64 | - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory); | ||
65 | - this.printTenantNames = printTenantNames; | 54 | + @Autowired StatsFactory statsFactory, |
55 | + @Autowired EntityService entityService) { | ||
56 | + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, | ||
57 | + entityService, printTenantNames); | ||
66 | } | 58 | } |
67 | 59 | ||
68 | @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") | 60 | @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") |
61 | + @Override | ||
69 | public void printStats() { | 62 | public void printStats() { |
70 | - int queueSize = getQueueSize(); | ||
71 | - int rateLimitedTenantsCount = (int) stats.getRateLimitedTenants().values().stream() | ||
72 | - .filter(defaultCounter -> defaultCounter.get() > 0) | ||
73 | - .count(); | ||
74 | - | ||
75 | - if (queueSize > 0 | ||
76 | - || rateLimitedTenantsCount > 0 | ||
77 | - || concurrencyLevel.get() > 0 | ||
78 | - || stats.getStatsCounters().stream().anyMatch(counter -> counter.get() > 0) | ||
79 | - ) { | ||
80 | - StringBuilder statsBuilder = new StringBuilder(); | ||
81 | - | ||
82 | - statsBuilder.append("queueSize").append(" = [").append(queueSize).append("] "); | ||
83 | - stats.getStatsCounters().forEach(counter -> { | ||
84 | - statsBuilder.append(counter.getName()).append(" = [").append(counter.get()).append("] "); | ||
85 | - }); | ||
86 | - statsBuilder.append("totalRateLimitedTenants").append(" = [").append(rateLimitedTenantsCount).append("] "); | ||
87 | - statsBuilder.append(CONCURRENCY_LEVEL).append(" = [").append(concurrencyLevel.get()).append("] "); | ||
88 | - | ||
89 | - stats.getStatsCounters().forEach(StatsCounter::clear); | ||
90 | - log.info("Permits {}", statsBuilder); | ||
91 | - } | ||
92 | - | ||
93 | - stats.getRateLimitedTenants().entrySet().stream() | ||
94 | - .filter(entry -> entry.getValue().get() > 0) | ||
95 | - .forEach(entry -> { | ||
96 | - TenantId tenantId = entry.getKey(); | ||
97 | - DefaultCounter counter = entry.getValue(); | ||
98 | - int rateLimitedRequests = counter.get(); | ||
99 | - counter.clear(); | ||
100 | - if (printTenantNames) { | ||
101 | - String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> { | ||
102 | - try { | ||
103 | - return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get(); | ||
104 | - } catch (Exception e) { | ||
105 | - log.error("[{}] Failed to get tenant name", tenantId, e); | ||
106 | - return "N/A"; | ||
107 | - } | ||
108 | - }); | ||
109 | - log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests); | ||
110 | - } else { | ||
111 | - log.info("[{}] Rate limited requests: {}", tenantId, rateLimitedRequests); | ||
112 | - } | ||
113 | - }); | 63 | + super.printStats(); |
114 | } | 64 | } |
115 | 65 | ||
116 | @PreDestroy | 66 | @PreDestroy |
@@ -119,6 +69,11 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | @@ -119,6 +69,11 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | ||
119 | } | 69 | } |
120 | 70 | ||
121 | @Override | 71 | @Override |
72 | + public String getBufferName() { | ||
73 | + return BUFFER_NAME; | ||
74 | + } | ||
75 | + | ||
76 | + @Override | ||
122 | protected SettableFuture<TbResultSet> create() { | 77 | protected SettableFuture<TbResultSet> create() { |
123 | return SettableFuture.create(); | 78 | return SettableFuture.create(); |
124 | } | 79 | } |
@@ -133,7 +88,7 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | @@ -133,7 +88,7 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< | ||
133 | CassandraStatementTask task = taskCtx.getTask(); | 88 | CassandraStatementTask task = taskCtx.getTask(); |
134 | return task.executeAsync( | 89 | return task.executeAsync( |
135 | statement -> | 90 | statement -> |
136 | - this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) | 91 | + this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) |
137 | ); | 92 | ); |
138 | } | 93 | } |
139 | 94 |
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.nosql; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import com.google.common.util.concurrent.SettableFuture; | ||
20 | +import lombok.extern.slf4j.Slf4j; | ||
21 | +import org.springframework.beans.factory.annotation.Autowired; | ||
22 | +import org.springframework.beans.factory.annotation.Value; | ||
23 | +import org.springframework.scheduling.annotation.Scheduled; | ||
24 | +import org.springframework.stereotype.Component; | ||
25 | +import org.thingsboard.server.common.stats.StatsFactory; | ||
26 | +import org.thingsboard.server.dao.entity.EntityService; | ||
27 | +import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; | ||
28 | +import org.thingsboard.server.dao.util.AsyncTaskContext; | ||
29 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | ||
30 | + | ||
31 | +import javax.annotation.PreDestroy; | ||
32 | + | ||
33 | +/** | ||
34 | + * Created by ashvayka on 24.10.18. | ||
35 | + */ | ||
36 | +@Component | ||
37 | +@Slf4j | ||
38 | +@NoSqlAnyDao | ||
39 | +public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { | ||
40 | + | ||
41 | + static final String BUFFER_NAME = "Write"; | ||
42 | + | ||
43 | + public CassandraBufferedRateWriteExecutor( | ||
44 | + @Value("${cassandra.query.buffer_size}") int queueLimit, | ||
45 | + @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit, | ||
46 | + @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime, | ||
47 | + @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads, | ||
48 | + @Value("${cassandra.query.callback_threads:4}") int callbackThreads, | ||
49 | + @Value("${cassandra.query.poll_ms:50}") long pollMs, | ||
50 | + @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, | ||
51 | + @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, | ||
52 | + @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, | ||
53 | + @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, | ||
54 | + @Autowired StatsFactory statsFactory, | ||
55 | + @Autowired EntityService entityService) { | ||
56 | + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, | ||
57 | + entityService, printTenantNames); | ||
58 | + } | ||
59 | + | ||
60 | + @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") | ||
61 | + @Override | ||
62 | + public void printStats() { | ||
63 | + super.printStats(); | ||
64 | + } | ||
65 | + | ||
66 | + @PreDestroy | ||
67 | + public void stop() { | ||
68 | + super.stop(); | ||
69 | + } | ||
70 | + | ||
71 | + @Override | ||
72 | + public String getBufferName() { | ||
73 | + return BUFFER_NAME; | ||
74 | + } | ||
75 | + | ||
76 | + @Override | ||
77 | + protected SettableFuture<TbResultSet> create() { | ||
78 | + return SettableFuture.create(); | ||
79 | + } | ||
80 | + | ||
81 | + @Override | ||
82 | + protected TbResultSetFuture wrap(CassandraStatementTask task, SettableFuture<TbResultSet> future) { | ||
83 | + return new TbResultSetFuture(future); | ||
84 | + } | ||
85 | + | ||
86 | + @Override | ||
87 | + protected ListenableFuture<TbResultSet> execute(AsyncTaskContext<CassandraStatementTask, TbResultSet> taskCtx) { | ||
88 | + CassandraStatementTask task = taskCtx.getTask(); | ||
89 | + return task.executeAsync( | ||
90 | + statement -> | ||
91 | + this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) | ||
92 | + ); | ||
93 | + } | ||
94 | + | ||
95 | +} |
@@ -31,12 +31,17 @@ import lombok.extern.slf4j.Slf4j; | @@ -31,12 +31,17 @@ import lombok.extern.slf4j.Slf4j; | ||
31 | import org.thingsboard.common.util.ThingsBoardExecutors; | 31 | import org.thingsboard.common.util.ThingsBoardExecutors; |
32 | import org.thingsboard.common.util.ThingsBoardThreadFactory; | 32 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
33 | import org.thingsboard.server.common.data.id.TenantId; | 33 | import org.thingsboard.server.common.data.id.TenantId; |
34 | +import org.thingsboard.server.common.msg.tools.TbRateLimits; | ||
35 | +import org.thingsboard.server.common.stats.DefaultCounter; | ||
36 | +import org.thingsboard.server.common.stats.StatsCounter; | ||
34 | import org.thingsboard.server.common.stats.StatsFactory; | 37 | import org.thingsboard.server.common.stats.StatsFactory; |
35 | import org.thingsboard.server.common.stats.StatsType; | 38 | import org.thingsboard.server.common.stats.StatsType; |
36 | -import org.thingsboard.server.common.msg.tools.TbRateLimits; | 39 | +import org.thingsboard.server.dao.entity.EntityService; |
37 | import org.thingsboard.server.dao.nosql.CassandraStatementTask; | 40 | import org.thingsboard.server.dao.nosql.CassandraStatementTask; |
38 | 41 | ||
39 | import javax.annotation.Nullable; | 42 | import javax.annotation.Nullable; |
43 | +import java.util.HashMap; | ||
44 | +import java.util.Map; | ||
40 | import java.util.UUID; | 45 | import java.util.UUID; |
41 | import java.util.concurrent.BlockingQueue; | 46 | import java.util.concurrent.BlockingQueue; |
42 | import java.util.concurrent.ConcurrentHashMap; | 47 | import java.util.concurrent.ConcurrentHashMap; |
@@ -75,22 +80,32 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | @@ -75,22 +80,32 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | ||
75 | protected final AtomicInteger concurrencyLevel; | 80 | protected final AtomicInteger concurrencyLevel; |
76 | protected final BufferedRateExecutorStats stats; | 81 | protected final BufferedRateExecutorStats stats; |
77 | 82 | ||
83 | + | ||
84 | + private final EntityService entityService; | ||
85 | + private final Map<TenantId, String> tenantNamesCache = new HashMap<>(); | ||
86 | + | ||
87 | + private final boolean printTenantNames; | ||
88 | + | ||
78 | public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs, | 89 | public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs, |
79 | - boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory) { | 90 | + boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory, |
91 | + EntityService entityService, boolean printTenantNames) { | ||
80 | this.maxWaitTime = maxWaitTime; | 92 | this.maxWaitTime = maxWaitTime; |
81 | this.pollMs = pollMs; | 93 | this.pollMs = pollMs; |
82 | this.concurrencyLimit = concurrencyLimit; | 94 | this.concurrencyLimit = concurrencyLimit; |
83 | this.printQueriesFreq = printQueriesFreq; | 95 | this.printQueriesFreq = printQueriesFreq; |
84 | this.queue = new LinkedBlockingDeque<>(queueLimit); | 96 | this.queue = new LinkedBlockingDeque<>(queueLimit); |
85 | - this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-dispatcher")); | ||
86 | - this.callbackExecutor = ThingsBoardExecutors.newWorkStealingPool(callbackThreads, getClass()); | ||
87 | - this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout")); | 97 | + this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-" + getBufferName() + "-dispatcher")); |
98 | + this.callbackExecutor = ThingsBoardExecutors.newWorkStealingPool(callbackThreads, "nosql-" + getBufferName() + "-callback"); | ||
99 | + this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-" + getBufferName() + "-timeout")); | ||
88 | this.perTenantLimitsEnabled = perTenantLimitsEnabled; | 100 | this.perTenantLimitsEnabled = perTenantLimitsEnabled; |
89 | this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; | 101 | this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; |
90 | this.stats = new BufferedRateExecutorStats(statsFactory); | 102 | this.stats = new BufferedRateExecutorStats(statsFactory); |
91 | - String concurrencyLevelKey = StatsType.RATE_EXECUTOR.getName() + "." + CONCURRENCY_LEVEL; | 103 | + String concurrencyLevelKey = StatsType.RATE_EXECUTOR.getName() + "." + CONCURRENCY_LEVEL + getBufferName(); //metric name may change with buffer name suffix |
92 | this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0)); | 104 | this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0)); |
93 | 105 | ||
106 | + this.entityService = entityService; | ||
107 | + this.printTenantNames = printTenantNames; | ||
108 | + | ||
94 | for (int i = 0; i < dispatcherThreads; i++) { | 109 | for (int i = 0; i < dispatcherThreads; i++) { |
95 | dispatcherExecutor.submit(this::dispatch); | 110 | dispatcherExecutor.submit(this::dispatch); |
96 | } | 111 | } |
@@ -144,6 +159,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | @@ -144,6 +159,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | ||
144 | 159 | ||
145 | protected abstract ListenableFuture<V> execute(AsyncTaskContext<T, V> taskCtx); | 160 | protected abstract ListenableFuture<V> execute(AsyncTaskContext<T, V> taskCtx); |
146 | 161 | ||
162 | + public abstract String getBufferName(); | ||
163 | + | ||
147 | private void dispatch() { | 164 | private void dispatch() { |
148 | log.info("Buffered rate executor thread started"); | 165 | log.info("Buffered rate executor thread started"); |
149 | while (!Thread.interrupted()) { | 166 | while (!Thread.interrupted()) { |
@@ -264,4 +281,51 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | @@ -264,4 +281,51 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend | ||
264 | protected int getQueueSize() { | 281 | protected int getQueueSize() { |
265 | return queue.size(); | 282 | return queue.size(); |
266 | } | 283 | } |
284 | + | ||
285 | + public void printStats() { | ||
286 | + int queueSize = getQueueSize(); | ||
287 | + int rateLimitedTenantsCount = (int) stats.getRateLimitedTenants().values().stream() | ||
288 | + .filter(defaultCounter -> defaultCounter.get() > 0) | ||
289 | + .count(); | ||
290 | + | ||
291 | + if (queueSize > 0 | ||
292 | + || rateLimitedTenantsCount > 0 | ||
293 | + || concurrencyLevel.get() > 0 | ||
294 | + || stats.getStatsCounters().stream().anyMatch(counter -> counter.get() > 0) | ||
295 | + ) { | ||
296 | + StringBuilder statsBuilder = new StringBuilder(); | ||
297 | + | ||
298 | + statsBuilder.append("queueSize").append(" = [").append(queueSize).append("] "); | ||
299 | + stats.getStatsCounters().forEach(counter -> { | ||
300 | + statsBuilder.append(counter.getName()).append(" = [").append(counter.get()).append("] "); | ||
301 | + }); | ||
302 | + statsBuilder.append("totalRateLimitedTenants").append(" = [").append(rateLimitedTenantsCount).append("] "); | ||
303 | + statsBuilder.append(CONCURRENCY_LEVEL).append(" = [").append(concurrencyLevel.get()).append("] "); | ||
304 | + | ||
305 | + stats.getStatsCounters().forEach(StatsCounter::clear); | ||
306 | + log.info("Permits {}", statsBuilder); | ||
307 | + } | ||
308 | + | ||
309 | + stats.getRateLimitedTenants().entrySet().stream() | ||
310 | + .filter(entry -> entry.getValue().get() > 0) | ||
311 | + .forEach(entry -> { | ||
312 | + TenantId tenantId = entry.getKey(); | ||
313 | + DefaultCounter counter = entry.getValue(); | ||
314 | + int rateLimitedRequests = counter.get(); | ||
315 | + counter.clear(); | ||
316 | + if (printTenantNames) { | ||
317 | + String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> { | ||
318 | + try { | ||
319 | + return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get(); | ||
320 | + } catch (Exception e) { | ||
321 | + log.error("[{}] Failed to get tenant name", tenantId, e); | ||
322 | + return "N/A"; | ||
323 | + } | ||
324 | + }); | ||
325 | + log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests); | ||
326 | + } else { | ||
327 | + log.info("[{}] Rate limited requests: {}", tenantId, rateLimitedRequests); | ||
328 | + } | ||
329 | + }); | ||
330 | + } | ||
267 | } | 331 | } |
@@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; | @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; | ||
20 | import com.datastax.oss.driver.api.core.cql.PreparedStatement; | 20 | import com.datastax.oss.driver.api.core.cql.PreparedStatement; |
21 | import com.datastax.oss.driver.api.core.cql.Statement; | 21 | import com.datastax.oss.driver.api.core.cql.Statement; |
22 | import com.google.common.util.concurrent.Futures; | 22 | import com.google.common.util.concurrent.Futures; |
23 | +import com.google.common.util.concurrent.SettableFuture; | ||
23 | import org.junit.Before; | 24 | import org.junit.Before; |
24 | import org.junit.Test; | 25 | import org.junit.Test; |
25 | import org.junit.runner.RunWith; | 26 | import org.junit.runner.RunWith; |
@@ -38,6 +39,7 @@ import java.util.UUID; | @@ -38,6 +39,7 @@ import java.util.UUID; | ||
38 | import static org.mockito.ArgumentMatchers.any; | 39 | import static org.mockito.ArgumentMatchers.any; |
39 | import static org.mockito.ArgumentMatchers.anyInt; | 40 | import static org.mockito.ArgumentMatchers.anyInt; |
40 | import static org.mockito.ArgumentMatchers.anyString; | 41 | import static org.mockito.ArgumentMatchers.anyString; |
42 | +import static org.mockito.BDDMockito.willReturn; | ||
41 | import static org.mockito.Mockito.doReturn; | 43 | import static org.mockito.Mockito.doReturn; |
42 | import static org.mockito.Mockito.times; | 44 | import static org.mockito.Mockito.times; |
43 | import static org.mockito.Mockito.verify; | 45 | import static org.mockito.Mockito.verify; |
@@ -59,9 +61,6 @@ public class CassandraPartitionsCacheTest { | @@ -59,9 +61,6 @@ public class CassandraPartitionsCacheTest { | ||
59 | private Environment environment; | 61 | private Environment environment; |
60 | 62 | ||
61 | @Mock | 63 | @Mock |
62 | - private CassandraBufferedRateExecutor rateLimiter; | ||
63 | - | ||
64 | - @Mock | ||
65 | private CassandraCluster cluster; | 64 | private CassandraCluster cluster; |
66 | 65 | ||
67 | @Mock | 66 | @Mock |
@@ -74,7 +73,6 @@ public class CassandraPartitionsCacheTest { | @@ -74,7 +73,6 @@ public class CassandraPartitionsCacheTest { | ||
74 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "systemTtl", 0); | 73 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "systemTtl", 0); |
75 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "setNullValuesEnabled", false); | 74 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "setNullValuesEnabled", false); |
76 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "environment", environment); | 75 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "environment", environment); |
77 | - ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "rateLimiter", rateLimiter); | ||
78 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "cluster", cluster); | 76 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "cluster", cluster); |
79 | 77 | ||
80 | when(cluster.getDefaultReadConsistencyLevel()).thenReturn(ConsistencyLevel.ONE); | 78 | when(cluster.getDefaultReadConsistencyLevel()).thenReturn(ConsistencyLevel.ONE); |
@@ -88,7 +86,9 @@ public class CassandraPartitionsCacheTest { | @@ -88,7 +86,9 @@ public class CassandraPartitionsCacheTest { | ||
88 | when(boundStatement.setUuid(anyInt(), any(UUID.class))).thenReturn(boundStatement); | 86 | when(boundStatement.setUuid(anyInt(), any(UUID.class))).thenReturn(boundStatement); |
89 | when(boundStatement.setLong(anyInt(), any(Long.class))).thenReturn(boundStatement); | 87 | when(boundStatement.setLong(anyInt(), any(Long.class))).thenReturn(boundStatement); |
90 | 88 | ||
91 | - doReturn(Futures.immediateFuture(0)).when(cassandraBaseTimeseriesDao).getFuture(any(TbResultSetFuture.class), any()); | 89 | + willReturn(new TbResultSetFuture(SettableFuture.create())).given(cassandraBaseTimeseriesDao).executeAsyncWrite(any(), any()); |
90 | + | ||
91 | + doReturn(Futures.immediateFuture(0)).when(cassandraBaseTimeseriesDao).getFuture(any(), any()); | ||
92 | } | 92 | } |
93 | 93 | ||
94 | @Test | 94 | @Test |
@@ -107,4 +107,5 @@ public class CassandraPartitionsCacheTest { | @@ -107,4 +107,5 @@ public class CassandraPartitionsCacheTest { | ||
107 | } | 107 | } |
108 | verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class)); | 108 | verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class)); |
109 | } | 109 | } |
110 | + | ||
110 | } | 111 | } |
@@ -245,7 +245,9 @@ public interface TbContext { | @@ -245,7 +245,9 @@ public interface TbContext { | ||
245 | 245 | ||
246 | CassandraCluster getCassandraCluster(); | 246 | CassandraCluster getCassandraCluster(); |
247 | 247 | ||
248 | - TbResultSetFuture submitCassandraTask(CassandraStatementTask task); | 248 | + TbResultSetFuture submitCassandraReadTask(CassandraStatementTask task); |
249 | + | ||
250 | + TbResultSetFuture submitCassandraWriteTask(CassandraStatementTask task); | ||
249 | 251 | ||
250 | PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink); | 252 | PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink); |
251 | 253 |
@@ -213,7 +213,7 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { | @@ -213,7 +213,7 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { | ||
213 | if (statement.getConsistencyLevel() == null) { | 213 | if (statement.getConsistencyLevel() == null) { |
214 | statement.setConsistencyLevel(level); | 214 | statement.setConsistencyLevel(level); |
215 | } | 215 | } |
216 | - return ctx.submitCassandraTask(new CassandraStatementTask(ctx.getTenantId(), getSession(), statement)); | 216 | + return ctx.submitCassandraWriteTask(new CassandraStatementTask(ctx.getTenantId(), getSession(), statement)); |
217 | } | 217 | } |
218 | 218 | ||
219 | private static String statementToString(Statement statement) { | 219 | private static String statementToString(Statement statement) { |