Separated persistence layer, created submodule for MapDB persistence letting the broker use the in-memory message store

This commit is contained in:
andsel
2017-04-01 16:21:34 +02:00
parent 7ffb8fb43b
commit 5c6889fbd5
39 changed files with 477 additions and 499 deletions
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.IMatchingCondition;
import io.moquette.spi.MessageGUID;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.impl.subscriptions.Topic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static io.moquette.spi.impl.Utils.defaultGet;
/**
* @author andrea
*/
public class MemoryMessagesStore implements IMessagesStore {
private static final Logger LOG = LoggerFactory.getLogger(MemoryMessagesStore.class);
private Map<Topic, MessageGUID> m_retainedStore = new HashMap<>();
private Map<MessageGUID, StoredMessage> m_persistentMessageStore = new HashMap<>();
private Map<String, Map<Integer, MessageGUID>> m_messageToGuids = new HashMap<>();
MemoryMessagesStore() {
}
@Override
public void initStore() {
}
@Override
public void storeRetained(Topic topic, MessageGUID guid) {
m_retainedStore.put(topic, guid);
}
@Override
public Collection<StoredMessage> searchMatching(IMatchingCondition condition) {
LOG.debug("searchMatching scanning all retained messages, presents are {}", m_retainedStore.size());
List<StoredMessage> results = new ArrayList<>();
for (Map.Entry<Topic, MessageGUID> entry : m_retainedStore.entrySet()) {
final MessageGUID guid = entry.getValue();
StoredMessage storedMsg = m_persistentMessageStore.get(guid);
if (condition.match(entry.getKey())) {
results.add(storedMsg);
}
}
return results;
}
@Override
public MessageGUID storePublishForFuture(StoredMessage storedMessage) {
LOG.debug("storePublishForFuture store evt {}", storedMessage);
MessageGUID guid = new MessageGUID(UUID.randomUUID().toString());
storedMessage.setGuid(guid);
m_persistentMessageStore.put(guid, storedMessage);
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> guids = defaultGet(m_messageToGuids, storedMessage.getClientID(), emptyGuids);
guids.put(storedMessage.getMessageID(), guid);
m_messageToGuids.put(storedMessage.getClientID(), guids);
return guid;
}
@Override
public void dropMessagesInSession(String clientID) {
Map<Integer, MessageGUID> messageGUIDMap = m_messageToGuids.get(clientID);
if (messageGUIDMap == null || messageGUIDMap.isEmpty()) {
return;
}
//remove all guids from retained
Collection<MessageGUID> messagesToRemove = new HashSet<>(messageGUIDMap.values());
messagesToRemove.removeAll(m_retainedStore.values());
for (MessageGUID guid : messagesToRemove) {
m_persistentMessageStore.remove(guid);
}
}
@Override
public StoredMessage getMessageByGuid(MessageGUID guid) {
return m_persistentMessageStore.get(guid);
}
@Override
public void cleanRetained(Topic topic) {
m_retainedStore.remove(topic);
}
@Override
public int getPendingPublishMessages(String clientID) {
Map<Integer, MessageGUID> messageToGuids = m_messageToGuids.get(clientID);
if (messageToGuids == null)
return 0;
else
return messageToGuids.size();
}
@Override
public MessageGUID mapToGuid(String clientID, int messageID) {
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> guids = Utils.defaultGet(m_messageToGuids, clientID, emptyGuids);
return guids.get(messageID);
}
}
@@ -0,0 +1,268 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import io.moquette.server.Constants;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.MessageGUID;
import io.moquette.spi.impl.Utils;
import io.moquette.spi.impl.subscriptions.Subscription;
import io.moquette.spi.impl.subscriptions.Topic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
*
* @author andrea
*/
public class MemorySessionStore implements ISessionsStore {
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
private Map<String, Map<Topic, Subscription>> m_persistentSubscriptions = new HashMap<>();
private Map<String, PersistentSession> m_persistentSessions = new HashMap<>();
// maps clientID->[MessageId -> guid]
private Map<String, Map<Integer, MessageGUID>> m_inflightStore = new HashMap<>();
// private Map<String, Set<Integer>> m_inflightIDs = new HashMap<>();
// maps clientID->BlockingQueue
private Map<String, BlockingQueue<StoredMessage>> queues = new HashMap<>();
// maps clientID->[MessageId -> guid]
private Map<String, Map<Integer, MessageGUID>> m_secondPhaseStore = new HashMap<>();
private final IMessagesStore m_messagesStore;
public MemorySessionStore(IMessagesStore messagesStore) {
this.m_messagesStore = messagesStore;
}
@Override
public void removeSubscription(Topic topic, String clientID) {
LOG.debug("removeSubscription topic filter: {} for clientID: {}", topic, clientID);
if (!m_persistentSubscriptions.containsKey(clientID)) {
return;
}
Map<Topic, Subscription> clientSubscriptions = m_persistentSubscriptions.get(clientID);
clientSubscriptions.remove(topic);
}
@Override
public void initStore() {
}
@Override
public void addNewSubscription(Subscription newSubscription) {
final String clientID = newSubscription.getClientId();
if (!m_persistentSubscriptions.containsKey(clientID)) {
m_persistentSubscriptions.put(clientID, new HashMap<Topic, Subscription>());
}
m_persistentSubscriptions.get(clientID).put(newSubscription.getTopicFilter(), newSubscription);
}
@Override
public void wipeSubscriptions(String clientID) {
m_persistentSubscriptions.remove(clientID);
}
@Override
public boolean contains(String clientID) {
return m_persistentSubscriptions.containsKey(clientID);
}
@Override
public ClientSession createNewSession(String clientID, boolean cleanSession) {
LOG.debug("createNewSession for client <{}>", clientID);
if (m_persistentSessions.containsKey(clientID)) {
LOG.error("already exists a session for client <{}>, bad condition", clientID);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
LOG.debug("clientID {} is a newcome, creating it's empty subscriptions set", clientID);
m_persistentSubscriptions.put(clientID, new HashMap<Topic, Subscription>());
m_persistentSessions.put(clientID, new PersistentSession(cleanSession));
return new ClientSession(clientID, m_messagesStore, this, cleanSession);
}
@Override
public ClientSession sessionForClient(String clientID) {
if (!m_persistentSessions.containsKey(clientID)) {
return null;
}
PersistentSession storedSession = m_persistentSessions.get(clientID);
return new ClientSession(clientID, m_messagesStore, this, storedSession.cleanSession);
}
@Override
public Collection<ClientSession> getAllSessions() {
Collection<ClientSession> result = new ArrayList<>();
for (Map.Entry<String, PersistentSession> entry : m_persistentSessions.entrySet()) {
result.add(new ClientSession(entry.getKey(), m_messagesStore, this, entry.getValue().cleanSession));
}
return result;
}
@Override
public void updateCleanStatus(String clientID, boolean cleanSession) {
m_persistentSessions.put(clientID, new PersistentSession(cleanSession));
}
@Override
public List<ClientTopicCouple> listAllSubscriptions() {
List<ClientTopicCouple> allSubscriptions = new ArrayList<>();
for (Map.Entry<String, Map<Topic, Subscription>> entry : m_persistentSubscriptions.entrySet()) {
for (Subscription sub : entry.getValue().values()) {
allSubscriptions.add(sub.asClientTopicCouple());
}
}
return allSubscriptions;
}
@Override
public Subscription getSubscription(ClientTopicCouple couple) {
Map<Topic, Subscription> subscriptions = m_persistentSubscriptions.get(couple.clientID);
if (subscriptions == null || subscriptions.isEmpty()) {
return null;
}
return subscriptions.get(couple.topicFilter);
}
@Override
public List<Subscription> getSubscriptions() {
List<Subscription> subscriptions = new ArrayList<>();
for (Map.Entry<String, Map<Topic, Subscription>> entry : m_persistentSubscriptions.entrySet()) {
subscriptions.addAll(entry.getValue().values());
}
return subscriptions;
}
@Override
public void inFlightAck(String clientID, int messageID) {
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
if (m == null) {
LOG.error("Can't find the inFlight record for client <{}>", clientID);
return;
}
m.remove(messageID);
}
@Override
public void inFlight(String clientID, int messageID, MessageGUID guid) {
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
if (m == null) {
m = new HashMap<>();
}
m.put(messageID, guid);
this.m_inflightStore.put(clientID, m);
}
/**
* Return the next valid packetIdentifier for the given client session.
*/
@Override
public int nextPacketID(String clientID) {
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
if (m == null) {
m = new HashMap<>();
int nextPacketId = 1;
m.put(nextPacketId, null);
return nextPacketId;
}
int maxId = m.keySet().isEmpty() ? 0 :Collections.max(m.keySet());
int nextPacketId = (maxId + 1) % 0xFFFF;
m.put(nextPacketId, null);
return nextPacketId;
}
@Override
public BlockingQueue<StoredMessage> queue(String clientID) {
final ArrayBlockingQueue<StoredMessage> emptyQueue = new ArrayBlockingQueue<>(Constants.MAX_MESSAGE_QUEUE);
BlockingQueue<StoredMessage> messagesQueue = Utils.defaultGet(queues, clientID, emptyQueue);
queues.put(clientID, messagesQueue);
return messagesQueue;
}
@Override
public void dropQueue(String clientID) {
queues.remove(clientID);
}
@Override
public void moveInFlightToSecondPhaseAckWaiting(String clientID, int messageID) {
LOG.info("acknowledging inflight clientID <{}> messageID {}", clientID, messageID);
Map<Integer, MessageGUID> m = this.m_inflightStore.get(clientID);
if (m == null) {
LOG.error("Can't find the inFlight record for client <{}>", clientID);
return;
}
MessageGUID guid = m.remove(messageID);
LOG.info("Moving to second phase store");
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, emptyGuids);
messageIDs.put(messageID, guid);
m_secondPhaseStore.put(clientID, messageIDs);
}
@Override
public MessageGUID secondPhaseAcknowledged(String clientID, int messageID) {
final HashMap<Integer, MessageGUID> emptyGuids = new HashMap<>();
Map<Integer, MessageGUID> messageIDs = Utils.defaultGet(m_secondPhaseStore, clientID, emptyGuids);
MessageGUID guid = messageIDs.remove(messageID);
m_secondPhaseStore.put(clientID, messageIDs);
return guid;
}
@Override
public StoredMessage getInflightMessage(String clientID, int messageID) {
Map<Integer, MessageGUID> inflightMessages = m_inflightStore.get(clientID);
MessageGUID guid = inflightMessages.get(messageID);
return m_messagesStore.getMessageByGuid(guid);
}
@Override
public int getInflightMessagesNo(String clientID) {
Map<Integer, MessageGUID> inflightMessages = m_inflightStore.get(clientID);
if (inflightMessages == null)
return 0;
else
return inflightMessages.size();
}
@Override
public int getPendingPublishMessagesNo(String clientID) {
return m_messagesStore.getPendingPublishMessages(clientID);
}
@Override
public int getSecondPhaseAckPendingMessages(String clientID) {
Map<Integer, MessageGUID> pendingAcks = m_secondPhaseStore.get(clientID);
if (pendingAcks == null)
return 0;
else
return pendingAcks.size();
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import io.moquette.spi.IMessagesStore;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.IStore;
public class MemoryStorageService implements IStore {
private MemorySessionStore m_sessionsStore;
private MemoryMessagesStore m_messagesStore;
public MemoryStorageService() {
m_messagesStore = new MemoryMessagesStore();
m_sessionsStore = new MemorySessionStore(m_messagesStore);
m_messagesStore.initStore();
m_sessionsStore.initStore();
}
@Override
public IMessagesStore messagesStore() {
return m_messagesStore;
}
@Override
public ISessionsStore sessionsStore() {
return m_sessionsStore;
}
@Override
public void close() {
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.persistence;
import java.io.Serializable;
/**
* This is a DTO used to persist minimal status (clean session and activation status) of a
* session.
*/
public class PersistentSession implements Serializable {
private static final long serialVersionUID = 5052054783220481854L;
public final boolean cleanSession;
public PersistentSession(boolean cleanSession) {
this.cleanSession = cleanSession;
}
}