I'm using the Google Pub/Sub Java SDK to subscribe to a topic. What I want to do is the following:
Start listening to a topic for X seconds (let's assume 25 seconds)
If a message is received then stop listening and process the message (this can take a few minutes)
After processing the message continue listening for a topic again for 25 seconds
If no message is received within 25 seconds then stop definitively listening
I can't seem to find anything in the documentation and only. Maybe it's just not possible?
Here's how I start the subscriber:
// Create a subscriber bound to the asynchronous message receiver
subscriber = Subscriber.newBuilder(projectSubscriptionName, new PubSubRoeMessageReceiver()).build();
// Start subscriber
subscriber.startAsync().awaitRunning();
// Allow the subscriber to run indefinitely unless an unrecoverable error occurs.
subscriber.awaitTerminated();
And this is what my message receiver looks like:
public class PubSubRoeMessageReceiver implements MessageReceiver {
#Override
public void receiveMessage(PubsubMessage pubsubMessage, AckReplyConsumer ackReplyConsumer) {
// Acknowledge message
System.out.println("Acknowledge message");
ackReplyConsumer.ack();
// TODO: stop the subscriber
// TODO: run task X
// TODO: start the subscriber
}
}
Any ideas?
Using Cloud Pub/Sub in this way is an anti-pattern and would cause issues. If you immediately ack the message after you receive it, but before you process it, what do you do if the subscriber crashes for some reason? Pub/Sub won't redeliver the message and therefore may never process it potentially.
Therefore, you probably want to wait to ack until after the message is processed. But then, you wouldn't be able to shut down the subscriber because the fact that the message is outstanding would be lost and therefore, the ack deadline would expire and the message would get redelivered.
If you want to ensure the client only receives one message at a time, you could use the FlowControlSettings on the client. If you set MaxOutstandingElementCount to 1, then only one message will be delivered to receiveMessage at a time:
subscriber = Subscriber.newBuilder(projectSubscriptionName, new PubSubRoeMessageReceiver())
.setFlowControlSettings(FlowControlSettings.newBuilder()
.setMaxOutstandingRequestBytes(10L * 1024L * 1024L) // 10MB messages allowed.
.setMaxOutstandingElementCount(1L) // Only 1 outstanding message at a time.
.build())
.build();
Keep in mind that if you have a large backlog of small messages at the time you start up the subscriber and you intend to start up multiple subscribers, you may run into inefficient load balancing as explained in the documentation.
I am working on an approach where i am required to send a message back to SQS.
I don't want it to go as a new message as that will reset the approximateRecieveCount parameter which is required by the code.
Please note that
I cannot send a NACK to the queue as i am reading it as a batch of 10 messages, I want to manually post it back in certain cases for individual message and not as a batch.
The code I am trying to use
I tried setting the JMSMessageId but it is not possible as according to the documentation -
After you send messages, Amazon SQS sets the following headers and properties for each message:
JMSMessageID
JMS_SQS_SequenceNumber (only for FIFO queues)
The code i am using right now is
defaultJmsTemplate.send(destinationName, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
Message message = session.createTextMessage(errorMessage);
message.setJMSCorrelationID(transactionId);
if (destinationName.endsWith(".fifo")) {
message.setStringProperty("JMSXGroupID", property.getMessageGroup());
message.setStringProperty("JMS_SQS_DeduplicationId", java.util.UUID.randomUUID().toString());
}
return message;
}
});
}
Is there anything that i can set/use to make sure the message is not treated as a new message and the approximate receive count is maintained?
Yes. This can be done. As you are using JMS for SQS while setting up your consumer you can define an UNORDERED_ACKNOWLEDGE mode in your consumer session. By doing so if you do not acknowledge a particular message it will be redelivered after its visibility timeout expires and the approximateRecieveCount will be incremented. This will not impact your other messages in the same batch. One downside of this is if you are using the fifo queue and the all your messages have same group id then you next message will only be processed after this unacknowledged message ends up in dead letter queue. This will only happen after your message is retried for the Maximum Receives that you have set up in fifo queue configuration. Note : The key here is to not acknowledge a particular message.
Looking for possibility to get camel jms ack delivery notification.
So - I want to notify producer when message is delivered on server (not when consumer is received).
Possible solution:
CompletableFuture<Exchange> future = template.asyncSend(uri, exchange, synchronisation);
In this case synchronisation onComplete method triggered when consumer received.
The same with future get.
I want to detect moment when message is delivered on jms server and if it is delivered.
Thank you
TLDR; In the context of a topic exchange and queues created on the fly by the consumers, how to have a message redelivered / the producer notified when no consumer consumes the message?
I have the following components:
a main service, producing files. Each file has a certain category (e.g. pictures.profile, pictures.gallery)
a set of workers, consuming files and producing a textual output from them (e.g. the size of the file)
I currently have a single RabbitMQ topic exchange.
The producer sends messages to the exchange with routing_key = file_category.
Each consumer creates a queue and binds the exchange to this queue for a set of routing keys (e.g. pictures.* and videos.trending).
When a consumer has processed a file, it pushes the result in a processing_results queue.
Now - this works properly, but it still has a major issue. Currently, if the publisher sends a message with a routing key that no consumer is bound to, the message will be lost. This is because even if the queue created by the consumers is durable, it is destroyed as soon as the consumer disconnects since it is unique to this consumer.
Consumer code (python):
channel.exchange_declare(exchange=exchange_name, type='topic', durable=True)
result = channel.queue_declare(exclusive = True, durable=True)
queue_name = result.method.queue
topics = [ "pictures.*", "videos.trending" ]
for topic in topics:
channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key=topic)
channel.basic_consume(my_handler, queue=queue_name)
channel.start_consuming()
Loosing a message in this condition is not acceptable in my use case.
Attempted solution
However, "loosing" a message becomes acceptable if the producer is notified that no consumer received the message (in this case it can just resend it later). I figured out the mandatory field could help, since the specification of AMQP states:
This flag tells the server how to react if the message cannot be routed to a queue. If this flag is set, the server will return an unroutable message with a Return method.
This is indeed working - in the producer, I am able to register a ReturnListener :
rabbitMq.confirmSelect();
rabbitMq.addReturnListener( (int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) -> {
log.info("A message was returned by the broker");
});
rabbitMq.basicPublish(exchangeName, "pictures.profile", true /* mandatory */, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBytes);
This will as expected print A message was returned by the broker if a message is sent with a routing key no consumer is bound to.
Now, I also want to know when the message was correctly received by a consumer. So I tried registering a ConfirmListener as well:
rabbitMq.addConfirmListener(new ConfirmListener() {
void handleAck(long deliveryTag, boolean multiple) throws IOException {
log.info("ACK message {}, multiple = ", deliveryTag, multiple);
}
void handleNack(long deliveryTag, boolean multiple) throws IOException {
log.info("NACK message {}, multiple = ", deliveryTag, multiple);
}
});
The issue here is that the ACK is sent by the broker, not by the consumer itself. So when the producer sends a message with a routing key K:
If a consumer is bound to this routing key, the broker just sends an ACK
Otherwise, the broker sends a basic.return followed by a ACK
Cf the docs:
For unroutable messages, the broker will issue a confirm once the exchange verifies a message won't route to any queue (returns an empty list of queues). If the message is also published as mandatory, the basic.return is sent to the client before basic.ack. The same is true for negative acknowledgements (basic.nack).
So while my problem is theoretically solvable using this, it would make the logic of knowing if a message was correctly consumed very complicated (especially in the context of multi threading, persistence in a database, etc.):
send a message
on receive ACK:
if no basic.return was received for this message
the message was correctly consumed
else
the message wasn't correctly consumed
on receive basic.return
the message wasn't correctly consumed
Possible other solutions
Have a queue for each file category, i.e. the queues pictures_profile, pictures_gallery, etc. Not good since it removes a lot of flexibility for the consumers
Have a "response timeout" logic in the producer. The producer sends a message. It expects an "answer" for this message in the processing_results queue. A solution would be to resend the message if it hasn't been answered to after X seconds. I don't like it though, it would create some additional tricky logic in the producer.
Produce the messages with a TTL of 0, and have the producer listen on a dead-letter exchange. This is the official suggested solution to replace the 'immediate' flag removed in RabbitMQ 3.0 (see paragraph Removal of "immediate" flag). According to the docs of the dead letter exchanges, a dead letter exchange can only be configured per-queue. So it wouldn't work here
[edit] A last solution I see is to have every consumer create a durable queue that isn't destroyed when he disconnects, and have it listen on it. Example: consumer1 creates queue-consumer-1 that is bound to the message of myExchange having a routing key abcd. The issue I foresee is that it implies to find an unique identifier for every consumer application instance (e.g. hostname of the machine it runs on).
I would love to have some inputs on that - thanks!
Related to:
RabbitMQ: persistent message with Topic exchange (not applicable here since queues are created "on the fly")
Make sure the broker holds messages until at least one consumer gets it
RabbitMQ Topic Exchange with persisted queue
[edit] Solution
I ended up implementing something that uses a basic.return, as mentioned earlier. It is actually not so tricky to implement, you just have to make sure that your method producing the messages and the method handling the basic returns are synchronized (or have a shared lock if not in the same class), otherwise you can end up with interleaved execution flows that will mess up your business logic.
I believe that an alternate exchange would be the best fit for your use case for the part regarding the identification of not routed messages.
Whenever an exchange with a configured AE cannot route a message to any queue, it publishes the message to the specified AE instead.
Basically upon creation of the "main" exchange, you configure an alternate exchange for it.
For the referenced alternate exchange, I tend to go with a fanout, then create a queue (notroutedq) binded to it.
This means any message that is not published to at least one of the queues bound to your "main" exchange will end up in the notroutedq
Now regarding your statement:
because even if the queue created by the consumers is durable, it is destroyed as soon as the consumer disconnects since it is unique to this consumer.
Seems that you have configured your queues with auto-delete set to true.
If so, in case of disconnect, as you stated, the queue is destroyed and the messages still present on the queue are lost, case not covered by the alternate exchange configuration.
It's not clear from your use case description whether you'd expect in some cases for a message to end up in more than one queue, seemed more a case of one queue per type of processing expected (while keeping the grouping flexible). If indeed the queue split is related to type of processing, I do not see the benefit of setting the queue with auto-delete, expect maybe not having to do any cleanup maintenance when you want to change the bindings.
Assuming you can go with durable queues, then a dead letter exchange (would again go with fanout) with a binding to a dlq would cover the missing cases.
not routed covered by alternate exchange
correct processing already handled by your processing_result queue
problematic processing or too long to be processed covered by the dead letter exchange, in which case the additional headers added upon dead lettering the message can even help to identify the type of actions to take
I'm using Spring JMS and Web Sphere Default Messaging Provider for my Messaging Needs.
I have created a Topic and connection factory on WAS.
I am able to send message to Topic using JMSTemplate from my publisher.
But Whenever I call JmsTemplate.receive() in my Subscriber , it goes into waiting state and I am not able to receive message in my Subscriber.
However I am able to receive message in Subscriber using JmsTemplate if a use a Listener as Subscriber or if a start a new thread whenever I am publishing message using my Publisher.
Looking for help !
Messages are not stored in the topic. If you create a Publisher, send messages ans then start a subscriber no messages will be subscribed. At the time of publishing if there are any subscribers subscribed to that topic then only those subscribers will receive the message. If no subscribers exist then messages will be dropped.
It goes into waiting state that means it is waiting for messages from the JMS server and there are no currently available.
Whatever mode of subscribing you are using - asynchronous(MessageListener) or Synchronous(receive) you need to create subscriber before.
You can use
subscriber.receive(long timeout);
if you do not want to wait indefinitely.