I'm begininng to use Mqtt and I have a hard time with handling an unreliable network.
I'm using a Paho Java Client (in groovy) to publish messages to a distant Mosquitto Broker.
Is there a way, when the broker is unreachable, to have the Paho client persist the message and automatically re-connect to the broker and publish the locally stored messages ? Do I have to handle everything myself, using for example a local broker ?
Here is my client building code
String persistenceDir = config['persistence-dir'] ?: System.getProperty('java.io.tmpdir')
def persistence = new MqttDefaultFilePersistence(persistenceDir)
client = new MqttAsyncClient(uri, clientId, persistence)
client.setCallback(this)
options = new MqttConnectOptions()
if (config.password) {
options.setPassword(config.password as char[])
options.setUserName(config.user)
}
options.setCleanSession(false)
client.connect(options)
And my publish code
def message = new MqttMessage(Json.encode(outgoingMessage).getBytes())
try {
client?.connect(options)
def topic = client.getTopic('processMsg')
message.setQos(1)
def token = topic.publish(message)
if (client) {
client.disconnect()
}
Thanks
The Paho client will only persist in-flight messages when it is connected to the broker.
Typically, when connectivity issues start to arrive you'll see message timeouts popping up
Timed out waiting for a response from the server (32000)
At that point the message will still be persisted.
However, when the connection is lost, and you start seeing this
Client is not connected (32104)
You should assume that the message has not been persisted by Paho.
You can debug this in org.eclipse.paho.client.mqttv3.internal.ClientComms :
/**
* Sends a message to the broker if in connected state, but only waits for the message to be
* stored, before returning.
*/
public void sendNoWait(MqttWireMessage message, MqttToken token) throws MqttException {
final String methodName = "sendNoWait";
if (isConnected() ||
(!isConnected() && message instanceof MqttConnect) ||
(isDisconnecting() && message instanceof MqttDisconnect)) {
this.internalSend(message, token);
} else {
//#TRACE 208=failed: not connected
log.fine(className, methodName, "208");
throw ExceptionHelper.createMqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
}
}
The internalSend will persist the message, but only if it is connected to the broker.
Also take into account that there is a maximum number of inflight messages that Paho can process. If it exceeds that it will also decide to not persist the message.
You could just setup a local broker and bridge that with the remote broker. That way you can queue up all your messages locally and when the remote broker comes back online all can be delivered.
Yes... After you get an exception that the message can't be delivered, it has to be either persisted or the message needs to be regenerated.
If you plan to use a local broker you can look at Really Small Message Broker (https://www.ibm.com/developerworks/community/groups/service/html/communityview?communityUuid=d5bedadd-e46f-4c97-af89-22d65ffee070)
Related
I'm working with HiveMQ Client and I wanted to know if there was a way to get the quality of service (QoS) that a client is subscribing with (in terms of a specific topic or in general)? I would be looking for a method I could invoke on a client like so:
Mqtt5BlockingClient subscriber = Mqtt5Client.builder()
.identifier(UUID.randomUUID().toString()) // the unique identifier of the MQTT client
.serverHost("localhost")
.serverPort(1883)
.buildBlocking();
subscriber.getQoS("topic") // returns the QoS of the subscriber is subscribing to the given topic
I would just want this information so I could print in to the console.
I think you have to read more about MQTT concepts.
The Quality of Service (QoS) level is an agreement between a sender and receiver of a message regarding the guarantees of delivering a message.
Therefore, the QoS is used in the publish() and subscribe() methods not the connect().
This is the scenario:
1. Connect: You have to connect your client to any broker with username/password. Every mqtt library has a connect() method. In this step, you have not specified qos yet.
After successful connection (every mqtt library has a callback for the connect method) and you can publish or subscribe to any desired (or allowed) topics.
Example:
Eclipse Paho library:
IMqttToken token = clientPhone.connect();
HiveMQ Library:
client.connect();
//or
client.connectWith().keepAlive(10).send();
//or
Mqtt5Connect connectMessage = Mqtt5Connect.builder().keepAlive(10).build();
client.connect(connectMessage);
2. Publish:
When you want to publish() a message, you have to specify a qos, so that the broker will respond to the client according with this qos:
Qos=0:
Client ---- Publish method ----> broker
Qos=1:
Client ---- Publish method ----> broker
Client <---- PubAck callback ---- broker
Qos=2:
Client ---- Publish method ----> broker
Client <---- PubRec callback ---- broker
Client ---- PubRel method ----> broker
Client <---- PubComp callback ---- broker
Example:
Eclipse Paho library:
IMqttDeliveryToken tokenPub = clientPhone.publish(topicPub, message);
HiveMQ Library:
client.publishWith()
.topic("test/topic")
.qos(MqttQos.AT_LEAST_ONCE)
.payload("payload".getBytes())
.send();
//or:
Mqtt5Publish publishMessage = Mqtt5Publish.builder()
.topic("test/topic")
.qos(MqttQos.AT_LEAST_ONCE)
.payload("payload".getBytes())
.build();
client.publish(publishMessage);
3. Subscribe:
A SUBSCRIBE message can contain an arbitrary number of subscriptions for a client. Each subscription is a pair of a topic and QoS level. The topic in the subscribe message can also contain wildcards, which makes it possible to subscribe to certain topic patterns. If there are overlapping subscriptions for one client, the highest QoS level for that topic wins and will be used by the broker for delivering the message.
Example:
Eclipse Paho library:
IMqttToken subToken = MqttAndroidClientInstance.subscribe(topics, qos);
HiveMQ Library:
client.subscribeWith().topicFilter("test/topic").qos(MqttQos.EXACTLY_ONCE).send();
//or:
Mqtt5Subscribe subscribeMessage = Mqtt5Subscribe.builder()
.topicFilter("test/topic")
.qos(MqttQos.EXACTLY_ONCE)
.build();
client.subscribe(subscribeMessage);
Edit(1):
A mqtt client have to use the following parameters, if wants to receive the already subscribed topics after reconnecting:
A- connect with cleanSession false.
B- Subscribe with QOS>0.
I have a NATS streaming cluster with 3 nodes set up. It seems that NATS messages published by my java application during server downtime is lost (i.e. not republished again when my servers are back up and running).
A more detailed description:
NATS cluster online. Publisher and Subscriber applications come online. Publisher begins to publish a message every second. Subscriber receives messages.
NATS servers are shut off. Publisher continues to publish messages (let's call these messages 'offline messages'). Subscriber stops receiving anything
NATS servers come back online. Subscriber begins to receive messages again, but 'offline messages' are never received.
Both my publisher and subscriber applications are configured to attempt reconnection to NATS server and does not timeout. I do not get any exceptions throughout.
NATS connection:
Options options = new Options.Builder().servers(serverList).maxReconnects(-1).build();
Connection nc = Nats.connect(options);
StreamingConnectionFactory cf = new StreamingConnectionFactory(natsProperties.getClusterId(), natsProperties.getClientId());
cf.setNatsConnection(nc);
streamingConnection = cf.createConnection();
Publisher:
// subject and message String variables are passed in
streamingConnection.publish(subject, message.getBytes());
Subscriber:
streamingConnection.subscribe(subject, new MessageHandler() {
public void onMessage(Message m) {
System.out.prinf("Received msg: %s\n", m.getData())
}
}, new SubscriptionOptions.Builder().durableName(durableName).build());
From the docs, the Java NATS client seems to have a reconnect buffer built in. I tried increasing the buffer by a factor of 10 but to no avail (also, my messages consist only of 2-digit numbers). How do I get it to resend these 'offline messages'?
I have the same problem, the only solution that I see that another method of subscription is occupied, save the sequence of messages but this I do not think is the best
// Receive messages starting at a specific sequence number
sc.subscribe("foo", new MessageHandler() {
public void onMessage(Message m) {
logger.info("Sequence message " + m.getSequence());
System.out.printf("Received a message: %s\n", m.getData());
}
}, new SubscriptionOptions.Builder().startAtSequence(22).build());
So I have a one client-server based ecosystem where I am using RabbitMQ as a persistent Middleware.
Now the flow of a single message goes like this.
Step-1: Client A sends a message to the server with the destination
being set to Client B in the metadata of that message.
Step-2: Server upon receiving a message pushes the message to the
RabbitMQ and sends Client B a notification that he has some messages
to fetch.
Step-3: Client B upon getting notified calls the fetch message API to
get messages from the server.
Step-4: On the server, after getting called from the Client B pulls
messages from the RabbitMQ using the pull-based approach
(channel.basicGet(queueName, false)) and hands over the list of
messages.
Now in the above flow, there are few things that I have some doubt with.
First of all, if my client receives two notifications and calls the pull message API twice, there might be a concurrency problem.
Suppose I am not sending the message Acknowledgement while getting the message but I am sending afterwards, then can It be possible that the same message being sent to two pull API? If so is there any way to prevent this from happening?
Sample Code to Get Message From the MQ:
long currentMessageCount = channel.messageCount(QUEUE_NAME);
while (currentMessageCount-- > 0) {
GetResponse getResponse = channel.basicGet(QUEUE_NAME, false);
if (getResponse == null) {
break;
}
AMQP.BasicProperties props = getResponse.getProps();
Envelope envelope = getResponse.getEnvelope();
int messageCount = getResponse.getMessageCount();
byte[] body = getResponse.getBody();
/*
Do some logic
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
TIA
basicGet is rarely the correct solution. In Step 2, the client should be consuming from RabbitMQ. No notification that a message is ready is necessary. RabbitMQ will send the message to Client B as soon as it's in the queue. Step-3 and Step-4 become unnecessary.
NOTE: the RabbitMQ team monitors the rabbitmq-users mailing list and only sometimes answers questions on StackOverflow.
I have a Spring application that consumes messages on a specific port (say 9001), restructures them and then forwards to a Rabbit MQ server. The code segment is:
private void send(String routingKey, String message) throws Exception {
String exchange = applicationConfiguration.getAMQPExchange();
String exchangeType = applicationConfiguration.getAMQPExchangeType();
Connection connection = myConnection.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, exchangeType);
channel.basicPublish(exchange, routingKey, null, message.getBytes());
log.debug(" [CORE: AMQP] Sent message with key {} : {}",routingKey, message);
}
If the Rabbit MQ server fails (crashes, runs out of RAM, turned off etc) the code above blocks, preventing the upstream service from receiving messages (a bad thing). I am looking for a way of preventing this behaviour whilst not losing mesages so that at some time in the future they can be resent.
I am not sure how best to address this. One option may be to queue the messages to a disk file and then use a separate thread to read and forward to the Rabbit MQ server?
If I understand correctly, the issue you are describing is a known JDK socket behaviour when the connection is lost mid-write. See this mailing list thread: http://markmail.org/thread/3vw6qshxsmu7fv6n.
Note that if RabbitMQ is shut down, the TCP connection should be closed in a way that's quickly observable by the client. However, it is true that stale TCP connections can take
a while to be detected, that's why RabbitMQ's core protocol has heartbeats. Set heartbeat
interval to a low value (say, 6-8) and the client itself will notice unresponsive peer
in that amount of time.
You need to use Publisher confirms [1] but also account for the fact that the app itself
can go down right before sending a message. As you rightly point out, having a disk-based
WAL (write-ahead log) is a common solution for this problem. Note that it is both quite
tricky to get right and still leaves some time window where your app process shutting down can result in an unpublished and unlogged message.
No promises on the time frame but the idea of adding WAL to the Java client has been discussed.
http://www.rabbitmq.com/confirms.html
I have some doubts regarding QoS=2 settings.
Mqtt publisher-subscriber am using Qos=2. Up to my knowledge by setting Qos=2 avoid duplication of message delivery among subscribers. In publisher i have set the Qos=2. I have two subscribers listening the same TOPIC. My code is running correctly but both subscribers getting the same message.
By setting Qos=2 Only one subscriber can get the message right?
How to solve this issue?
public class PubSync {
public static void main(String[] args) {
try {
MqttClient client = new MqttClient(TCPAddress,MqttClient.generateClientId());
MqttTopic topic = client.getTopic(MYTOPIC);
MqttMessage message = new MqttMessage(msg.getBytes());
message.setQos(2);
client.connect();
MqttDeliveryToken token = topic.publish(message);
token.waitForCompletion();
client.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
}
}
QOS 2 means the that each subscriber will only receive 1 copy of any given message.
This differs from QOS 1 where it is possible that a subscriber may receive multiple copies of the same message as the broker ensures that message is delivered.
The QOS levels do not change in any way how many subscribers will see a message.
Depending on the MQTT messaging provider you are using, you should be able to share a subscription to a topic across multiple subscribers so that only one subscriber receives each message. In this case the messaging provider handles distributing the workload evently across all the subscribers.
This is known as shared subscriptions and you can read more about how it works in IBM's MessageSight product here: http://pic.dhe.ibm.com/infocenter/ism/v1r0m0/topic/com.ibm.ism.doc/Overview/ov30010.html