Kafka, Spring Kafka and redelivering old messages - spring-boot

I use Kafka and Spring Boot with Spring Kafka. After abnormal application termination and then restart, my application started receiving the old, already processed messages from Kafka queue.
What may be the reason for that and how to find and resolve the issue?
my Kafka properties:
spring.kafka.bootstrap-servers=${kafka.host}:${kafka.port}
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.group-id=postfenix
spring.kafka.consumer.enable-auto-commit=false
My Spring Kafka factory and listener:
#Bean
public ConcurrentKafkaListenerContainerFactory<String, Post> postKafkaListenerContainerFactory(KafkaProperties kafkaProperties) {
ConcurrentKafkaListenerContainerFactory<String, Post> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.getContainerProperties().setAckMode(AckMode.MANUAL);
factory.setConsumerFactory(postConsumerFactory(kafkaProperties));
return factory;
}
#KafkaListener(topics = "${kafka.topic.post.send}", containerFactory = "postKafkaListenerContainerFactory")
public void sendPost(ConsumerRecord<String, Post> consumerRecord, Acknowledgment ack) {
Post post = consumerRecord.value();
// do some logic here
ack.acknowledge();
}

When using Kafka, the clients need to commit offsets themselves. This is in contrast to other message brokers, such as AMQP brokers, where the broker keeps track of messages a client did already receive.
In your case, you do not commit offsets automatically and therefore Kafka expects you to commit them manually (because of this setting: spring.kafka.consumer.enable-auto-commit=false). If you do not commit offsets manually in your program, the behaviour you describe is pretty much the expected one. Kafka simply does not know what messages your program did process successfully. Each time you restart your program, Kafka will see that your program did not commit any offsets yet and will apply the strategy you provide in spring.kafka.consumer.auto-offset-reset=earliest, which means the first message in the queue.
If this is all new to you, I suggest reading up this documentation on Kafka and this Spring documentation, because Kafka is quite different than other message brokers.

Related

Redis messages are consumed by Spring boot application after Redis restart

First started Redis Server then started application.As soon as messages appears in the Redis stream those are getting consumed properly. In mean while if restarts the Redis Server as application is already running it is not consuming new messages from the Redis Server.
Could someone please assist on this.
Is there any more configuration needs to be done, to continue to process message after redis server restart.
I'm using StreamMessageListenerContainer with consumergroup.
#Bean
public Subscription listener(RedisStreamConsumer streamListener, RedisConnectionFactory redisConnectionFactory) throws InterruptedException {
StreamMessageListenerContainer<String, MapRecord<String, Object, String>> listenerContainer =
StreamMessageListenerContainer.create(redisTemplate().getConnectionFactory(),
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.hashKeySerializer(new StringRedisSerializer()).hashValueSerializer(new StringRedisSerializer())
.pollTimeout(Duration.ofMillis(100))
.build());
Subscription subscription = listenerContainer.receive(Consumer.from(groupName, consumerName),
StreamOffset.create(consumerstreamName, ReadOffset.lastConsumed()), streamListener);
subscription.await(Duration.ofSeconds(2));
listenerContainer.start();
return subscription;
}

Message sent using JMS producer is not received in MQTT receiver in the same SpringBoot app

I'm just starting with ActiveMQ Artemis and have Artemis 2.17.0 installed on my machine. Created SpringBoot test app where both JMS and MQTT publishers and receivers exist. Created also small RestController so I can send messages using both JMS and MQTT producers. Receivers are quite simple and just create a log message to console. Now when I create a message using MQTT producer, both JMS and MQTT receivers get and log message to console. But when I send a message using JMS producer, the message is being received only in JMS receiver, no MQTT message in console. Tried several times. Implementation is ok I think as MQTT producer example is working fine. Is there any limitation for routing messages among protocols in Artemis in this way? Or what kind of problem it can be?
Code info about JMS implementation: https://dmarko.tcl-digitrade.com/post/2021/activemq-artemis-spring-boot/
Code info about MQTT implementation: https://dmarko.tcl-digitrade.com/post/2021/activemq-artemis-mqtt/
Apache ActiveMQ Artemis has a flexible addressing model that supports both Point-to-Point and Publish-Subscribe patterns.
By default, Spring Boot creates a JmsTemplate configured to transmit Point-to-Point while MQTT uses a Publish-Subscribe pattern, so the JMS and MQTT receivers are using different messaging patterns and this is causing your issue.
To configure a JmsTemplate for the Publish-Subscribe pattern set spring.jms.pub-sub-domain=true through Boot’s application.properties or set the JmsTemplate pubSubDomain to true, ie:
jmsTemplate.setPubSubDomain(true);
To configure a JmsListener for the Publish-Subscribe pattern set spring.jms.pub-sub-domain=true through Boot’s application.properties or set the JmsListenerContainerFactory pubSubDomain to true, ie:
#Bean
public JmsListenerContainerFactory<?> topicConnectionFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
#JmsListener(destination = "${prices.mqtt.east}", containerFactory = "topicConnectionFactory")
public void receiveFromTopic(String message) {
...
}

Redeliver message to MQ when using #JmsListener

I'm using #EnableJms and #JmsListener annotation to register a queue listener in my application, basing on this tutorial. I'm connecting to IBM MQ, getting connection factory using jndi. I have read about acknowledge modes etc. but still it's a new thing to me. And my problem is that the message is not being returned to a queue (the listener is never called again).
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory
= new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setSessionTransacted(true);
factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE); //I have tried also CLIENT_ACKNOWLEDGE
return factory;
}
#JmsListener(containerFactory = "jmsListenerContainerFactory", destination = "myQueue")
#SendTo("secondQueue")
public String testListener(String message){
if(true) throw new NullPointerException();
else return message;
}
Any help would be much appreciated.
I would have also a second question. From what I understand if I would like to implement any operation on a database, only way to rollback a commit (if something went wrong after this) would be to create a transaction manager? If not, I would need to detect a duplicated message.
First set the acknowledgement mode to Session.CLIENT_ACKNOWLEDGE
and when receiving the messages, if it's processed correctly then just call message.acknowledge() method or else don't call.
It will automatically stay in the queue and you don't need to resend it.
You need to use
import javax.jms.Message
I created simple Spring Boot app and Docker container of IBM MQ to test your case.
I found good instructions in this tutorial: https://developer.ibm.com/tutorials/mq-jms-application-development-with-spring-boot/
And in your case this environment behaves as expected: endless cycle of receive message -> NullPointerException -> return message -> ...
Than I found feature of IBM MQ called "Backout Queues & Thresholds", you'll found the explanation in this blog post: https://community.ibm.com/community/user/imwuc/browse/blogs/blogviewer?BlogKey=28814801-083d-4c80-be5f-90aaaf81cdfb
Briefly, it is possible to restrict number of times message returned to queue after exception, and after this limit send message to another queue.
May be in your case this feature used on your destination queue.

Losing JMS Messages with Spring JMS and ActiveMQ when application server is suddenly stopped

I have a Spring JMS application that has a JMS Listener that connects to an Active MQ queue on application startup. This JMS listener is a part of the an application that takes a message, enriches it with content, and then delivers it to a topic on the same ActiveMQ broker.
The sessionTransacted is set to True. I'm not performing any database transactions, so I do not have #Transactional set anywhere. From what I've read, the sessionTransacted property sets a local transaction around the JMS Listener's receive method and therefore it will not pull the message off the queue until the transaction is complete. I've tested this using a local ActiveMQ instance, and on my local tomcat container, and it worked as expected.
However, when I deploy to our PERF environment and retry the same test, I notice that the message that was currently in-flight when the server was shutdown, is pulled from queue prior to completing the receive method.
What I would like to know is if there is anything obvious that I should be looking for? Are there certain JMS headers that would cause this behaviour to occur? Please let me know if there is anymore information that I can provide.
I'm using Spring 4.1.2.RELEASE with Apache ActiveMQ 5.8.0, on a Tomcat 7 container running Java 8.
UPDATE - Adding my Java JMS Configurations. Please note that I substituted what I had in my PERF properties file into the relevant areas for clarity.
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() throws Throwable {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setMaxMessagesPerTask(-1);
factory.setConcurrency(1);
factory.setSessionTransacted(Boolean.TRUE);
return factory;
}
#Bean
public CachingConnectionFactory connectionFactory(){
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setInitialRedeliveryDelay(1000);
redeliveryPolicy.setRedeliveryDelay(1000);
redeliveryPolicy.setMaximumRedeliveries(6);
redeliveryPolicy.setUseExponentialBackOff(Boolean.TRUE);
redeliveryPolicy.setBackOffMultiplier(5);
ActiveMQConnectionFactory activeMQ = new ActiveMQConnectionFactory(environment.getProperty("queue.username"), environment.getProperty("queue.password"), environment.getProperty("jms.broker.endpoint"));
activeMQ.setRedeliveryPolicy(redeliveryPolicy);
activeMQ.setPrefetchPolicy(prefetchPolicy());
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(activeMQ);
cachingConnectionFactory.setCacheConsumers(Boolean.FALSE);
cachingConnectionFactory.setSessionCacheSize(1);
return cachingConnectionFactory;
}
#Bean
public JmsMessagingTemplate jmsMessagingTemplate(){
ActiveMQTopic activeMQ = new ActiveMQTopic(environment.getProperty("queue.out"));
JmsMessagingTemplate jmsMessagingTemplate = new JmsMessagingTemplate(connectionFactory());
jmsMessagingTemplate.setDefaultDestination(activeMQ);
return jmsMessagingTemplate;
}
protected ActiveMQPrefetchPolicy prefetchPolicy(){
ActiveMQPrefetchPolicy prefetchPolicy = new ActiveMQPrefetchPolicy();
int prefetchValue = 0;
prefetchPolicy.setQueuePrefetch(prefetchValue);
return prefetchPolicy;
}
Thanks,
Juan
It turns out that there were different versions of our application deployed on our PERF environment. Once the application was updated, then it worked as expected.

How to stop consumers

I made a simple Jms project with 2 java files names are MessageSender.java,MessageConsumer.java.one for sending messages to Activemq:Queue and another for consuming messages from Activemq:Queue.Deployed this project in Apache Tomcat.following code was consumer code.
ActiveMQConnectionFactory connectionFactory=new ActiveMQConnectionFactory("admin","admin","tcp://localhost:61617?jms.prefetchPolicy.queuePrefetch=1");
Connection connection=connectionFactory.createConnection();
final Session session=connection.createSession(true, Session.CLIENT_ACKNOWLEDGE);
Queue queue=session.createQueue("ThermalMap");
javax.jms.MessageConsumer consumer=session.createConsumer(queue);
//anonymous class
MessageListener listener = new MessageListener() {
#Override
public void onMessage(Message msg) {
// My business code
}
};
Later If I want to change consumer code,I don't want to stop Tomcatbecause If I stop Tomcat entire jms project should not work. So clients can't able to sent messages to Activemq:Queue.So I don't want to follow this way.
I am thinking, If I stop consumers through Activemq console page.I don't need to stop Tomcat So clients can able to send messages normally.For this I check AMQ console page,I didn't seen any consumers.
Is it correct way to do this.
If it is correct way, How can I do this.
can anyone suggest me.
Thanks.
Call the .close() method on your MessageConsumer.

Resources