committed by
GitHub
4 changed files with 195 additions and 88 deletions
@ -0,0 +1,75 @@ |
|||||
|
/** |
||||
|
* 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.queue.memory; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.queue.TbQueueMsg; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.BlockingQueue; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.LinkedBlockingQueue; |
||||
|
|
||||
|
@Component |
||||
|
@Slf4j |
||||
|
public final class DefaultInMemoryStorage implements InMemoryStorage { |
||||
|
private final ConcurrentHashMap<String, BlockingQueue<TbQueueMsg>> storage = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
@Override |
||||
|
public void printStats() { |
||||
|
if (log.isDebugEnabled()) { |
||||
|
storage.forEach((topic, queue) -> { |
||||
|
if (queue.size() > 0) { |
||||
|
log.debug("[{}] Queue Size [{}]", topic, queue.size()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public int getLagTotal() { |
||||
|
return storage.values().stream().map(BlockingQueue::size).reduce(0, Integer::sum); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean put(String topic, TbQueueMsg msg) { |
||||
|
return storage.computeIfAbsent(topic, (t) -> new LinkedBlockingQueue<>()).add(msg); |
||||
|
} |
||||
|
|
||||
|
@SuppressWarnings("unchecked") |
||||
|
@Override |
||||
|
public <T extends TbQueueMsg> List<T> get(String topic) throws InterruptedException { |
||||
|
final BlockingQueue<TbQueueMsg> queue = storage.get(topic); |
||||
|
if (queue != null) { |
||||
|
final TbQueueMsg firstMsg = queue.poll(); |
||||
|
if (firstMsg != null) { |
||||
|
final int queueSize = queue.size(); |
||||
|
if (queueSize > 0) { |
||||
|
final List<TbQueueMsg> entities = new ArrayList<>(Math.min(queueSize, 999) + 1); |
||||
|
entities.add(firstMsg); |
||||
|
queue.drainTo(entities, 999); |
||||
|
return (List<T>) entities; |
||||
|
} |
||||
|
return Collections.singletonList((T) firstMsg); |
||||
|
} |
||||
|
} |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,115 @@ |
|||||
|
/** |
||||
|
* 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.queue.memory; |
||||
|
|
||||
|
import com.google.gson.Gson; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.queue.TbQueueMsg; |
||||
|
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.mockito.Mockito.mock; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class DefaultInMemoryStorageTest { |
||||
|
static final int MAX_POLL_SIZE = 1000; |
||||
|
final Gson gson = new Gson(); |
||||
|
final String topic = "tb_core_notification.tb-node-0"; |
||||
|
|
||||
|
InMemoryStorage storage = new DefaultInMemoryStorage(); |
||||
|
|
||||
|
@Test |
||||
|
public void givenStorage_whenGetLagTotal_thenReturnInteger() throws InterruptedException { |
||||
|
assertThat(storage.getLagTotal()).isEqualTo(0); |
||||
|
storage.put("main", mock(TbQueueMsg.class)); |
||||
|
assertThat(storage.getLagTotal()).isEqualTo(1); |
||||
|
storage.put("main", mock(TbQueueMsg.class)); |
||||
|
assertThat(storage.getLagTotal()).isEqualTo(2); |
||||
|
storage.put("hp", mock(TbQueueMsg.class)); |
||||
|
assertThat(storage.getLagTotal()).isEqualTo(3); |
||||
|
storage.get("main"); |
||||
|
assertThat(storage.getLagTotal()).isEqualTo(1); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithMoreThenBatchSize_whenPoll_thenReturnFullListAndSecondList() throws InterruptedException { |
||||
|
List<TbQueueMsg> msgs = new ArrayList<>(MAX_POLL_SIZE + 1); |
||||
|
for (int i = 0; i < MAX_POLL_SIZE + 1; i++) { |
||||
|
DefaultTbQueueMsg msg = gson.fromJson("{\"key\": \"" + UUID.randomUUID() + "\"}", DefaultTbQueueMsg.class); |
||||
|
msgs.add(msg); |
||||
|
storage.put(topic, msg); |
||||
|
} |
||||
|
|
||||
|
assertThat(storage.getLagTotal()).as("total lag is 1001").isEqualTo(MAX_POLL_SIZE + 1); |
||||
|
assertThat(storage.get(topic)).as("poll exactly 1000 msgs").isEqualTo(msgs.subList(0, MAX_POLL_SIZE)); |
||||
|
assertThat(storage.get(topic)).as("poll last 1 message").isEqualTo(msgs.subList(MAX_POLL_SIZE, MAX_POLL_SIZE + 1)); |
||||
|
assertThat(storage.getLagTotal()).as("total lag is zero").isEqualTo(0); |
||||
|
} |
||||
|
|
||||
|
private void testPollOnce(final int msgCount) throws InterruptedException { |
||||
|
List<TbQueueMsg> msgs = new ArrayList<>(msgCount); |
||||
|
for (int i = 0; i < msgCount; i++) { |
||||
|
DefaultTbQueueMsg msg = gson.fromJson("{\"key\": \"" + UUID.randomUUID() + "\"}", DefaultTbQueueMsg.class); |
||||
|
msgs.add(msg); |
||||
|
storage.put(topic, msg); |
||||
|
} |
||||
|
|
||||
|
assertThat(storage.getLagTotal()).as("total lag before poll").isEqualTo(msgCount); |
||||
|
assertThat(storage.get(topic)).as("polled exactly msgs").isEqualTo(msgs.subList(0, msgCount)); |
||||
|
assertThat(storage.getLagTotal()).as("final lag is zero").isEqualTo(0); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithExactBatchSize_whenPoll_thenReturnExactBatchSizeList() throws InterruptedException { |
||||
|
testPollOnce(MAX_POLL_SIZE); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithExactBatchSizeMinusOne_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { |
||||
|
testPollOnce(MAX_POLL_SIZE - 1); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithExactBatchSizeMinusTen_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { |
||||
|
testPollOnce(MAX_POLL_SIZE - 10); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueEmpty_whenPoll_thenReturnEmptyList() throws InterruptedException { |
||||
|
testPollOnce(0); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithSingleMessage_whenPoll_thenReturnSingletonList() throws InterruptedException { |
||||
|
testPollOnce(1); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithTwoMessages_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { |
||||
|
testPollOnce(2); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void givenQueueWithTenMessages_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { |
||||
|
testPollOnce(10); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,42 +0,0 @@ |
|||||
/** |
|
||||
* 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.queue.memory; |
|
||||
|
|
||||
import org.junit.After; |
|
||||
import org.junit.Before; |
|
||||
import org.junit.Test; |
|
||||
import org.thingsboard.server.queue.TbQueueMsg; |
|
||||
|
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
|
||||
import static org.mockito.Mockito.mock; |
|
||||
|
|
||||
public class InMemoryStorageTest { |
|
||||
|
|
||||
InMemoryStorage storage = new InMemoryStorage(); |
|
||||
|
|
||||
@Test |
|
||||
public void givenStorage_whenGetLagTotal_thenReturnInteger() throws InterruptedException { |
|
||||
assertThat(storage.getLagTotal()).isEqualTo(0); |
|
||||
storage.put("main", mock(TbQueueMsg.class)); |
|
||||
assertThat(storage.getLagTotal()).isEqualTo(1); |
|
||||
storage.put("main", mock(TbQueueMsg.class)); |
|
||||
assertThat(storage.getLagTotal()).isEqualTo(2); |
|
||||
storage.put("hp", mock(TbQueueMsg.class)); |
|
||||
assertThat(storage.getLagTotal()).isEqualTo(3); |
|
||||
storage.get("main"); |
|
||||
assertThat(storage.getLagTotal()).isEqualTo(1); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue