Spring rabbitmq retry message delivery not working - spring

I am setting PubSub using RabbitMQ, so whenever there is some failure in message processing a negative acknowledgement is sent in Listener, ideally retry should be done after some preconfigured time interval but retries are done continuously for each second till time of message-ttl value. For retry added created Advice chain in SimpleMessageListenerContainer. Queues and consumers are created dynamically depending on subscription. Below is code for rabbitmq service
#Service
public class RabbitMQQueueServiceImpl {
private final String JAVA_OBJECT_SERIALIZER_HEADER_NAME = "Content-Type";
private final String JAVA_OBJECT_SERIALIZER_HEADER_VALUE = "application/x-java-serialized-object";
private final String TIME_TO_LIVE_HEADER_NAME = "x-message-ttl";
#Value("${rabbitmq.message.timetolive}")
private Long messageTimeToLive;
#Value("${rabbitmq.host}")
private String host;
#Value("${rabbitmq.username}")
private String userName;
#Value("${rabbitmq.password}")
private String password;
#Value("${rabbitmq.port}")
private Integer port;
private RabbitTemplate rabbitTemplate;
#PostConstruct
public void init() {
createRabitTemplate();
}
public void subscribe(WebHookSubscription webHookSubscription,String queueName) throws Exception {
createProducer(webHookSubscription,queueName);
createConsumer(webHookSubscription,queueName);
}
public void publish(PublishEvent publishEvent,String queueName) {
//Append eventId for queueName and exchange name as accountId_accountTypeId
String exchange = queueName;
queueName = queueName+"_"+publishEvent.getEventId();
BroadcastMessageBean broadcastMessageBean = new BroadcastMessageBean();
/*
* Set properties for message - message persistence, JAVA object serializer
*/
MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
messageProperties.setHeader(JAVA_OBJECT_SERIALIZER_HEADER_NAME, JAVA_OBJECT_SERIALIZER_HEADER_VALUE);
BeanUtils.copyProperties(publishEvent, broadcastMessageBean);
Gson gson = new Gson();
Message messageBean = new Message(gson.toJson(broadcastMessageBean).getBytes(), messageProperties);
rabbitTemplate.send(exchange, publishEvent.getEventId().toString(), messageBean);
}
public void createQueue(String queueName) {
RabbitTemplate rabbitTemplate = getRabbitTemplate(queueName);
RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitTemplate.getConnectionFactory());
Queue queue = new Queue(queueName, true,false,false);
rabbitAdmin.declareQueue(queue);
}
private void createProducer(WebHookSubscription webHookSubscription, String queueName) {
queueName = queueName+"_"+webHookSubscription.getEventId();
RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitTemplate.getConnectionFactory());
String exchangeName = webHookSubscription.getAccountId()+"_"+webHookSubscription.getAccountTypeId();
String routingKey = webHookSubscription.getEventId().toString();
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put(TIME_TO_LIVE_HEADER_NAME, messageTimeToLive);
Queue queue = new Queue(queueName, true,false,false, arguments );
rabbitAdmin.declareQueue(queue);
Exchange exchange = new TopicExchange(exchangeName, true, false) ;
rabbitAdmin.declareExchange(exchange );
Binding binding = new Binding(queueName, DestinationType.QUEUE, exchangeName, routingKey, arguments);
rabbitAdmin.declareBinding(binding );
}
private void createConsumer(WebHookSubscription webHookSubscription,String queueName) throws Exception {
Map<String, String> headers = new HashMap<String, String>();
//Create exchange name same as queue name and append eventId to queueName for each event subscription for account
queueName = queueName+"_"+webHookSubscription.getEventId();
for(WebHookSubscriptionHeaders header : webHookSubscription.getWebHookHeaders()) {
headers.put(header.getName(), header.getValue());
}
/*
* Register consumer through SimpleMessageListenerContainer
* add consumer handler with eventId
*/
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setPrefetchCount(10);
container.setRecoveryInterval(1000);
ConsumerHandler consumerHandler = new ConsumerHandler(webHookSubscription.getEventId(),
webHookSubscription.getWebHookURL(),headers, webHookSubscription.getNotificationEmail());
container.setConnectionFactory(connectionFactory());
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setAdviceChain(new Advice[]{retryAdvice()});
/*
* Register consumer to queue and all messages will be broadcasted to subscriber specific queue
*/
container.setQueues(new Queue(queueName,true,false,false));
container.setMessageListener(consumerHandler);
container.start();
}
private MethodInterceptor retryAdvice() {
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(15000);
backOffPolicy.setMultiplier(100);
backOffPolicy.setMaxInterval(604800);
return RetryInterceptorBuilder.stateless().backOffPolicy(backOffPolicy).maxAttempts(5).recoverer(new RejectAndDontRequeueRecoverer()).build();
}
/**
* Create Connection factory for RabbitMQ template
* #return
*/
private CachingConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(getHost());
connectionFactory.setUsername(getUserName());
connectionFactory.setPassword(getPassword());
connectionFactory.setPort(getPort());
return connectionFactory;
}
private RabbitTemplate createRabitTemplate() {
rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setMessageConverter(new JsonMessageConverter());
//Create retries configuration for message delivery
/*RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(messageRetriesInitialInterval);
backOffPolicy.setMaxInterval(messageRetriesMaxInterval);
backOffPolicy.setMultiplier(messageRetriesMultiplier);
retryTemplate.setBackOffPolicy(backOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(retries);
retryTemplate.setRetryPolicy(retryPolicy);
//Retries configuration ends here
rabbitTemplate.setRetryTemplate(retryTemplate);*/
return rabbitTemplate;
}
}
This is for consumer handler
public class ConsumerHandler implements ChannelAwareMessageListener {
public ConsumerHandler(Long eventId, String url, Map<String, String> headers, String email) {
this.eventId = eventId;
this.url = url;
this.headers = headers;
this.email = email;
}
#Override
public void onMessage(Message message, Channel channel) throws IOException, NoSuchAlgorithmException {
//process message body
if(error){
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
return;
}
//for success
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
What is missing in above code to make retry work?
logs:
2016-02-23 16:34:09 DEBUG [org.springframework.amqp.rabbit.listener.BlockingQueueConsumer] - <Storing delivery for Consumer: tags=[{amq.ctag-a1IonIIN7mf8uhIGXE4eIw=14947_0_4}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,1), acknowledgeMode=MANUAL local queue size=0>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor] - <Exiting proxied method in stateful retry with result: (null)>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor] - <Exiting proxied method in stateful retry with result: (null)>
2016-02-23 16:34:09 DEBUG [org.springframework.amqp.rabbit.listener.BlockingQueueConsumer] - <Retrieving delivery for Consumer: tags=[{amq.ctag-a1IonIIN7mf8uhIGXE4eIw=14947_0_4}], channel=Cached Rabbit Channel: AMQChannel(amqp://guest#127.0.0.1:5672/,1), acknowledgeMode=MANUAL local queue size=1>
2016-02-23 16:34:09 DEBUG [org.springframework.amqp.rabbit.listener.BlockingQueueConsumer] - <Received message: (Body:'[B#4109b410(byte[199])'MessageProperties [headers={eventId=4, retry=1, Content-Type=application/x-java-serialized-object}, timestamp=null, messageId=31b93494-d6ef-4d63-b90b-c7a7e73acb70, userId=null, appId=null, clusterId=null, type=null, correlationId=null, replyTo=null, contentType=application/octet-stream, contentEncoding=null, contentLength=0, deliveryMode=PERSISTENT, expiration=null, priority=0, redelivered=true, receivedExchange=14947_0, receivedRoutingKey=4, deliveryTag=10234, messageCount=0])>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor] - <Executing proxied method in stateful retry: public abstract void org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$ContainerDelegate.invokeListener(com.rabbitmq.client.Channel,org.springframework.amqp.core.Message) throws java.lang.Exception(27f49de4)>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.support.RetryTemplate] - <Retry: count=0>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.interceptor.StatefulRetryOperationsInterceptor] - <Executing proxied method in stateful retry: public abstract void org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$ContainerDelegate.invokeListener(com.rabbitmq.client.Channel,org.springframework.amqp.core.Message) throws java.lang.Exception(27f49de4)>
2016-02-23 16:34:09 DEBUG [org.springframework.retry.support.RetryTemplate] - <Retry: count=0>

There is no direct way to ensure delayed retry.
You may have to create a Dead letter exchange and create a retryQueue on that exchange with the message ttl set to the the delay interval you want.
Your main queue should look something like this
#Bean
public Queue mainQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "retryExchangeName");
return new Queue("mainQueue", true, false, false, args);
}
and retry Queue
#Bean
public Queue retryQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "mainExchangeName");
args.put("x-message-ttl", RETRY_MESSAGE_TTL);
return new Queue("retryQueue", true, false, false, args);
}
now for the messages you want a delayed retry Nack them with requeue=false
channel().basicNack(10, false, false);
now this message will be sent to retryQueue and after the specified ttl it will be put back to the mainQueue for reprocessing.

Related

Issue with RabbitMQ Delay message in spring boot

I am facing an issue in Rabbit MQ regarding x-delay while connecting to spring boot. I need to schedule the messages for a variable delay according to the message type. It can be one of the units MINUTE, DAY, WEEK, MONTH, and so on…
Below is my configuration class :
private final RabbitProperties rabbitProperties;
private final Environment environment;
#Bean
public Queue rabbitMQueue() {
return new Queue(environment.getProperty(RABBITMQ_QUEUE_NAME), false);
}
#Bean
public Exchange rabbitExchange() {
String exchangeName = "test_exchange";
Map<String, Object> exchangeArgs = new HashMap<>();
exchangeArgs.put("x-delayed-type", exchangeType.toLowerCase());
exchangeArgs.put("x-delayed-message",true);
exchangeArgs.put("x-message-ttl",9922);
log.info("Loading {} exchange with name {}.", exchangeType, exchangeName);
switch (exchangeType){
default: return new CustomExchange(exchangeName, exchangeType, true, false, exchangeArgs);
case "DIRECT" : return directExchange(exchangeName, exchangeArgs);
}
}
private Exchange directExchange(String exchangeName, Map<String, Object> exchangeArgs) {
// log.info("Generating directExchange");
// DirectExchange directExchange = new DirectExchange(exchangeName,true, false, exchangeArgs);
// directExchange.setDelayed(true);
// return directExchange;
return ExchangeBuilder.directExchange(exchangeName).withArguments(exchangeArgs)
.delayed()
.build();
}
#Bean
public Binding rabbitBinding(final Queue rabbitMQueue, final Exchange rabbitExchange){
log.info("Exchange to bind : {}", rabbitExchange.getName());
return BindingBuilder
.bind(rabbitMQueue)
.to(rabbitExchange)
.with(environment.getProperty(RABBITMQ_ROUTING_KEY)).noargs();
}
#Bean
public AmqpTemplate amqpTemplate(final ConnectionFactory rabbitMQConnectionFactory,
final MessageConverter rabbitMessageConvertor,
final Exchange rabbitExchange){
RabbitTemplate rabbitTemplate = new RabbitTemplate(rabbitMQConnectionFactory);
rabbitTemplate.setMessageConverter(rabbitMessageConvertor);
rabbitTemplate.setExchange(rabbitExchange.getName());
return rabbitTemplate;
}
#Bean
public ConnectionFactory rabbitMQConnectionFactory(){
Boolean isUriBased = environment.getProperty(URI_BASED_CONNECTION_ENABLED, Boolean.class);
CachingConnectionFactory connectionFactory;
if(!Objects.isNull(isUriBased) && isUriBased){
connectionFactory = new CachingConnectionFactory();
connectionFactory.setUri(environment.getProperty(RABBITMQ_URI));
}
else{
connectionFactory = new CachingConnectionFactory(rabbitProperties.getHost(), rabbitProperties.getPort());
connectionFactory.setUsername(rabbitProperties.getUsername());
connectionFactory.setPassword(rabbitProperties.getPassword());
}
return connectionFactory;
}
#Bean
public MessageConverter rabbitMessageConvertor(){
return new Jackson2JsonMessageConverter();
}
And publisher code :
public boolean sendMessage(String tenant, T message, int delay){
MyQueueMessage<T> myQueueMessage = getQueueMessage(tenant, message);
try{
amqpTemplate.convertAndSend(exchangeName, routingKey, myQueueMessage, messagePostProcessor -> {
// MessageProperties messageProperties = messagePostProcessor.getMessageProperties();
messagePostProcessor.getMessageProperties().setHeader("x-message-ttl", 5011);
messagePostProcessor.getMessageProperties().setHeader(MessageProperties.X_DELAY, 5012);
messagePostProcessor.getMessageProperties().setDelay(5013);
messagePostProcessor.getMessageProperties().setReceivedDelay(5014);
log.info("Setting delay in properties : {}", messagePostProcessor.getMessageProperties().getHeader(MessageProperties.X_DELAY).toString());
return messagePostProcessor;
});
} catch (Exception e){
return false;
}
return true;
}
And receiver :
#RabbitListener(queues = "INVOICE")
public void receiveMessage(Message message){
log.info("Message Received : " + message.toString() + " with delay " + message.getMessageProperties().getDelay());
}
}
Issue :
The value
message.getMessageProperties().getDelay()
comes as NULL in the receiver and the message is also not delayed. It’s getting received instantly.
Did I miss anything?
Please note that I am using docker, rabbitmq-management-3, and have already installed the rabbitmq_delayed_message_exchange plugin.

How can i intercept incomig messages before they reach methods annotated with #RabbitListener?

I Started by setting up an interceptor for outgoing messages which is working smoothly, but
when i try to intercept incomming messages in the consumers, the postProcessMessage method
is skipped and the message reaches the method annotated with #RabbitListener, bellow is my code for the whole proccess, i ommited unimportant code.
Producer
RabbitMQProducerInterceptor
#Component
#Slf4j
public class RabbitMQProducerInterceptor implements MessagePostProcessor {
#Override
public Message postProcessMessage(Message message) throws AmqpException {
log.info("Getting the current HttpServletRequest");
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("Extracting the X-REQUEST-ID from the header of the HttpServletRequest");
String XRequestId = req.getHeader(ShareableConstants.X_REQUEST_ID_HEADER);
log.info("Adding X-REQUEST-ID {} to the RabbitMQ Producer Header", XRequestId);
message.getMessageProperties().getHeaders().put(ShareableConstants.X_REQUEST_ID_HEADER, XRequestId);
return message;
}
}
RabbitMQProducerConfig
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setReplyTimeout(60000);
MessagePostProcessor[] processors = {new RabbitMQProducerInterceptor()};
rabbitTemplate.addBeforePublishPostProcessors(processors);
return rabbitTemplate;
}
Sending a message to the consumer
User Producer
public UserRegistrationResponseDTO register(UserRegistrationDTO userRegistrationDTO) {
log.info("Sending user registration request {}", userRegistrationDTO);
UserRegistrationDTO response = (UserRegistrationDTO) rabbitTemplate
.convertSendAndReceive(ShareableConstants.EXCHANGE_NAME,
ShareableConstants.CREATE_USER_ROUTING_KEY,
userRegistrationDTO);
return UserRegistrationResponseDTO.builder()
.username(response.getUsername())
.id(response.getId())
.createdAt(response.getCreatedAt()).build();
}
Consumer
RabbitMQConsumerConfig
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
MessagePostProcessor[] processors = {new RabbitMQConsumerInterceptor()};
rabbitTemplate.setAfterReceivePostProcessors(processors);
return rabbitTemplate;
}
RabbitMQConsumerInterceptor
#Component
public class RabbitMQConsumerInterceptor implements MessagePostProcessor {
#Override
public Message postProcessMessage(Message message) throws AmqpException {
String XRequestId = message.getMessageProperties().getHeader(ShareableConstants.X_REQUEST_ID_HEADER);
MDC.put(ShareableConstants.X_REQUEST_ID_HEADER, XRequestId);
return message;
}
}
User Consumer
#RabbitListener(bindings =
#QueueBinding(exchange = #Exchange(ShareableConstants.EXCHANGE_NAME),
key = ShareableConstants.CREATE_USER_ROUTING_KEY,
value = #Queue(ShareableConstants.USER_REGISTRATION_QUEUE_NAME)))
public UserRegistrationDTO receiveUser(UserRegistrationDTO userRegistrationDTO) {
log.info("receiving user {} to register ", userRegistrationDTO);
User user = Optional.of(userRegistrationDTO).map(User::new).get();
User createdUser = userService.register(user);
UserRegistrationDTO registrationDTO = UserRegistrationDTO.builder()
.id(createdUser.getId())
.username(createdUser.getUsername())
.createdAt(createdUser.getCreationDate())
.build();
return registrationDTO;
}
Here's the code, no exception is thrown, the only problem is the Interceptor being skipped
The RabbitTemplate is not used to receive messages for a #RabbitListener; messages are received by a listener container; you have to set the afterReceivePostProcessors on the listener container factory.
If you are using Spring Boot, just add the auto-configured SimpleRabbitListenerContainerFactory as a parameter to one of your other #Beans and set the MPP on it.

How to commit offset in spring Kafka if the message failed and processed by AfterRollbackProcessor

I am using spring boot 2.1.9 with spring Kafka 2.2.9.
If the message failed a number of times (defined in the afterRollbackProcessor), The consumer stops polling the record. but if the consumer restarted, it again re-poll the same message and processes.
But I don't want the messages to be re-polled again, What is the best way to stop it?
here is my config
#Configuration
#EnableKafka
public class KafkaReceiverConfig {
// Kafka Server Configuration
#Value("${kafka.servers}")
private String kafkaServers;
// Group Identifier
#Value("${kafka.groupId}")
private String groupId;
// Kafka Max Retry Attempts
#Value("${kafka.retry.maxAttempts:5}")
private Integer retryMaxAttempts;
// Kafka Max Retry Interval
#Value("${kafka.retry.interval:180000}")
private Long retryInterval;
// Kafka Concurrency
#Value("${kafka.concurrency:10}")
private Integer concurrency;
// Kafka Concurrency
#Value("${kafka.poll.timeout:300}")
private Integer pollTimeout;
// Kafka Consumer Offset
#Value("${kafka.consumer.auto-offset-reset:earliest}")
private String offset = "earliest";
#Value("${kafka.max.records:100}")
private Integer maxPollRecords;
#Value("${kafka.max.poll.interval.time:500000}")
private Integer maxPollIntervalMs;
#Value("${kafka.max.session.timeout:60000}")
private Integer sessionTimoutMs;
// Logger
private static final Logger log = LoggerFactory.getLogger(KafkaReceiverConfig.class);
#Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory(
ChainedKafkaTransactionManager<String, String> chainedTM, MessageProducer messageProducer) {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setConcurrency(concurrency);
factory.getContainerProperties().setPollTimeout(pollTimeout);
factory.getContainerProperties().setAckMode(AckMode.RECORD);
factory.getContainerProperties().setSyncCommits(true);
factory.getContainerProperties().setAckOnError(false);
factory.getContainerProperties().setTransactionManager(chainedTM);
AfterRollbackProcessor<String, String> afterRollbackProcessor = new DefaultAfterRollbackProcessor<>(
(record, exception) -> {
log.warn("failed to process kafka message (retries are exausted). topic name:" + record.topic()
+ " value:" + record.value());
messageProducer.saveFailedMessage(record, exception);
}, retryMaxAttempts);
factory.setAfterRollbackProcessor(afterRollbackProcessor);
log.debug("Kafka Receiver Config kafkaListenerContainerFactory created");
return factory;
}
#Bean
public ConsumerFactory<String, String> consumerFactory() {
log.debug("Kafka Receiver Config consumerFactory created");
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
#Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new ConcurrentHashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServers);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs);
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimoutMs);
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, offset);
props.put(ConsumerConfig.ISOLATION_LEVEL_CONFIG, "read_committed");
log.debug("Kafka Receiver Config consumerConfigs created");
return props;
}
}
How can I achieve this?
Set the commitRecovered property to true and inject a KafkaTemplate configured with the same producer factory as the transaction manager.
/**
* {#inheritDoc}
* Set to true and the container will run the
* {#link #process(List, Consumer, Exception, boolean)} method in a transaction and,
* if a record is skipped and recovered, we will send its offset to the transaction.
* Requires a {#link KafkaTemplate}.
* #param commitRecovered true to process in a transaction.
* #since 2.2.5
* #see #isProcessInTransaction()
* #see #process(List, Consumer, Exception, boolean)
* #see #setKafkaTemplate(KafkaTemplate)
*/
#Override
public void setCommitRecovered(boolean commitRecovered) { // NOSONAR enhanced javadoc
super.setCommitRecovered(commitRecovered);
}
EDIT
Here's the logic in process...
if (SeekUtils.doSeeks(((List) records), consumer, exception, recoverable,
getSkipPredicate((List) records, exception), this.logger)
&& isCommitRecovered() && this.kafkaTemplate != null && this.kafkaTemplate.isTransactional()) {
// if we get here it means retries are exhausted and we've skipped
ConsumerRecord<K, V> skipped = records.get(0);
this.kafkaTemplate.sendOffsetsToTransaction(
Collections.singletonMap(new TopicPartition(skipped.topic(), skipped.partition()),
new OffsetAndMetadata(skipped.offset() + 1)));
}
EDIT2
In 2.2.x, the property is
/**
* Set to true to run the {#link #process(List, Consumer, Exception, boolean)}
* method in a transaction. Requires a {#link KafkaTemplate}.
* #param processInTransaction true to process in a transaction.
* #since 2.2.5
* #see #process(List, Consumer, Exception, boolean)
* #see #setKafkaTemplate(KafkaTemplate)
*/
public void setProcessInTransaction(boolean processInTransaction) {
this.processInTransaction = processInTransaction;
}

Error - Rabbit Template publish Confirm - reply-code=403, reply-text=ACCESS_REFUSED - cannot publish to internal exchange

My Use Case is:
subscribe to Q1 and read messages in Batches of specified size.
Pass the read message collection for processing.
publish the collected messages to Q2 and ack message to Q1 upon sucessful confirmation of q2 publish.
Code
#Component
public class EPPQ2Subscriber {
private static final Logger LOGGER = LoggerFactory.getLogger(EPPQ2Subscriber.class);
#Autowired
RabbitMqConfig rabbitMqConfig;
#Autowired
AppConfig appConfig;
List<Message> messageList = new ArrayList<Message>();
List<Long> diliveryTag = new ArrayList<Long>();
/**
* Method is listener's receive message method , invoked when there is message ready to read
* #param message - Domain object of message encapsulated
* #param channel - rabitmq client channel
* #param messageId - #TODO Delete it later.
* #param messageProperties - amqp message properties contains message properties such as delivery tag etc..
*/
#RabbitListener(id="messageListener",queues = "#{rabbitMqConfig.getSubscriberQueueName()}",containerFactory="queueListenerContainer")
public void receiveMessage(Message message, Channel channel, #Header("id") String messageId,
MessageProperties messageProperties) {
LOGGER.info("Result:" + message.getClass() + ":" + message.toString());
if(messageList.size() <= appConfig.getSubscriberChunkSize() ) {
messageList.add(message);
diliveryTag.add(messageProperties.getDeliveryTag());
} else {
// call the service here to decrypt, read pan, call danger to scrub, encrypt pan and re-pack them in message again.
//after this branch messageList should have scrubbed and encrypted message objects ready to publish.
// Here is call for publish and ack messages..
}
}
}
#Component
#Configuration
public class TopicConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TopicConfiguration.class);
#Autowired
RabbitMqConfig rabbitMqConfig;
#Autowired EPPQ2Publisher eppQ2Publisher;
/**
* Caching connection factory
* #return CachingConnectionFactory
*/
#Bean
public CachingConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitMqConfig.getPublisherHosts(), rabbitMqConfig.getPublisherPort());
connectionFactory.setUsername(rabbitMqConfig.getPublisherUsername());
connectionFactory.setPassword(rabbitMqConfig.getPublisherPassword());
return connectionFactory;
}
/**
* Bean RabbitTemplate
* #return RabbitTemplate
*/
#Bean
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new
ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
rabbitTemplate.setRetryTemplate(retryTemplate);
/* rabbitTemplate.setExchange(rabbitMqConfig.getPublisherTopic());
rabbitTemplate.setRoutingKey(rabbitMqConfig.getRoutingKey());*/
rabbitTemplate.setConfirmCallback((correlation, ack, reason) ->
if(correlation != null ) {
LOGGER.info("Received " + (ack ? " ack " : " nack ") +
"for correlation: " + correlation);
if(ack) {
// this is confirmation received..
// here is code to ack Q1. correlation.getId and ack
eppQ2Publisher.ackMessage(new
Long(correlation.getId().toString()));
} else {
// no confirmation received and no need to do any
thing for retry..
}
}
});
rabbitTemplate.setReturnCallback((message, replyCode,
replyText, exchange, routingKey) ->
{
LOGGER.error("Returned: " + message + "\nreplyCode: " +
replyCode+ "\nreplyText: " + replyText +
"\nexchange/rk: " + exchange + "/" + routingKey);
});
return rabbitTemplate;
}
/**
* Bean Jackson2JsonMessageConverter
* #return Jackson2JsonMessageConverter
*/
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
public interface EPPQ2Publisher {
public void sendMessage(Message msg,Long deliveryTag);
public void sendMessages(List<Message> msgList, Channel channel, List<Long> deliveryTagList);
public void ackMessage(Long deliveryTag);
}
#Component
public class EPPQ2PublisherImpl implements EPPQ2Publisher{
#Autowired
RabbitMqConfig rabbitMqConfig;
#Autowired
private RabbitTemplate rabbitTemplate;
private Channel channel;
/**
* Method sendMessage for sending individual scrubbed and encrypted message to publisher queue (Q2).
* #param msg - message domain object
* #param deliveryTag - is message delivery tag.
*/
#Override
public void sendMessage(Message msg,Long deliveryTag) {
rabbitTemplate.convertAndSend(rabbitMqConfig.getPublisherTopic(), rabbitMqConfig.getRoutingKey(), msg,new CorrelationData(deliveryTag.toString()));
}
/**
* sendMessages for sending list of scrubbed and encrypted messages to publisher queue (Q2)
* #param msgList - is list of scrubbed and encrypted messages
* #param channel - is ampq client channel
* #param deliveryTagList - is list of incoming message delivery tags.
*/
#Override
public void sendMessages(List<Message> msgList, Channel channel, List<Long>deliveryTagList) {
if(this.channel == null) {
this.channel = channel;
}
for (int i = 0 ; i < msgList.size(); i ++) {
sendMessage(msgList.get(i),deliveryTagList.get(i));
}
}
/**
* Method ackMessage for sending acknowledgement to subscriber Q1
* #param deliveryTag - is deliveryTag for each individual message.
*/
#Override
public void ackMessage(Long deliveryTag) {
try {
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
e.printStackTrace();
}
}
org.springframework.amqp.rabbit.connection.CachingConnectionFactory Creating cached Rabbit Channel from AMQChannel(amqp://dftp_subscriber#10.15.190.18:5672/hydra.services,2)
I expected to be dftp_publisher and I guess my topic configuration is not injected properly.
Error Log:
org.springframework.amqp.rabbit.core.RabbitTemplate[0;39m: Executing callback RabbitTemplate$$Lambda$285/33478758 on RabbitMQ Channel: Cached Rabbit Channel: AMQChannel(amqp://dftp_subscriber#10.15.190.18:5672/hydra.services,2), conn: Proxy#1dc339f Shared Rabbit Connection: SimpleConnection#2bd7c8 [delegate=amqp://dftp_subscriber#10.15.190.18:5672/hydra.services, localPort= 55553]
org.springframework.amqp.rabbit.core.RabbitTemplate[0;39m: Publishing message (Body:'{"HEADER":{"RETRY_COUNT":0,"PUBLISH_EVENT_TYPE":"AUTH"},"PAYLOAD":{"MTI":"100","MTI_REQUEST":"100","PAN":"6011000000000000","PROCCODE":"00","PROCCODE_REQUEST":"00","FROM_ACCOUNT":"00","TO_ACCOUNT":"00","TRANSACTION_AMOUNT":"000000000100","TRANSMISSION_MMDDHHMMSS":"0518202930","STAN":"000001","LOCALTIME_HHMMSS":"010054","LOCALDATE_YYMMDD":"180522","EXPIRATION_DATE_YYMM":"2302","MERCHANT_TYPE":"5311","ACQUIRING_COUNTRY_CODE":"840","POS_ENTRY_MODE":"02","POS_PIN_ENTRY_CAPABILITIES":"0","FUNCTION_CODE":"100","ACQUIRING_ID_CODE":"000000","FORWARDING_ID_CODE":"000000","RETRIEVAL_REFERENCE_NUMBER":"1410N644D597","MERCHANT_NUMBER":"601100000000596","CARD_ACCEPTOR_NAME":"Discover Acq Simulator","CARD_ACCEPTOR_CITY":"Riverwoods","CARD_ACCEPTOR_STATE":"IL","CARD_ACCEPTOR_COUNTRY":"840","CARD_ACCEPTOR_COUNTRY_3NUMERIC":"840","NRID":"123456789012345","TRANSACTION_CURRENCY_CODE":"840","POS_TERMINAL_ATTENDANCE_INDICATOR":"0","POS_PARTIAL_APPROVAL_INDICATOR":"0","POS_TERMINAL_LOCATION_INDICATOR":"0","POS_TRANSACTION_STATUS_INDICATOR":"0","POS_ECOMMERCE_TRAN_INDICATOR":"0","POS_TYPE_OF_TERMINAL_DEVICE":"0","POS_CARD_PRESENCE_INDICATOR":"0","POS_CARD_CAPTURE_CAPABILITIES_INDICATOR":"0","POS_TRANSACTION_SECURITY_INDICATOR":"0","POS_CARD_DATA_TERMINAL_INPUT_CAPABILITY_INDICATOR":"C","POS_CARDHOLDER_PRESENCE_INDICATOR":"0","DFS_POS_DATA":"0000000000C00","GEODATA_STREET_ADDRESS":"2500 LAKE COOK ROAD ","GEODATA_POSTAL_CODE":"600150000","GEODATA_COUNTY_CODE":"840","GEODATA_STORE_NUMBER":"10001","GEODATA_MALL_NAME":"DISCOVER FINANCIAL SR","ISS_REFERENCE_ID":"72967956","ISS_PROCESSOR_REFERENCE_ID":"123459875","VERSION_INDICATOR":"03141"}}' MessageProperties [headers={TypeId=com.discover.dftp.scrubber.domain.Message}, contentType=application/json, contentEncoding=UTF-8, contentLength=1642, deliveryMode=PERSISTENT, priority=0, deliveryTag=0])on exchange [hydra.hash2Syphon.exc], routingKey = [100]
org.springframework.amqp.rabbit.connection.CachingConnectionFactory$DefaultChannelCloseLogger[0;39m: Channel shutdown: channel error; protocol method: #method(reply-code=403, reply-text=ACCESS_REFUSED - cannot publish to internal exchange 'hydra.hash2Syphon.exc' in vhost 'hydra.services', class-id=60, method-id=40)
EDIT 2.
#Component
#Configuration
public class ListenerContainerFactory {
static final Logger logger = LoggerFactory.getLogger(ListenerContainerFactory.class);
#Autowired
RabbitMqConfig rabbitMqConfig;
#Autowired
EPPQ2Subscriber receiver;
#Autowired
EPPQ2ChanelAwareSubscriber receiverChanel;
public ListenerContainerFactory(ConfigurableApplicationContext ctx) {
printContainerStartMsg();
}
private void printContainerStartMsg() {
logger.info("----------- Scrubber Container Starts --------------");
}
#Bean
public SimpleRabbitListenerContainerFactory queueListenerContainer(AbstractConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
connectionFactory.setAddresses(rabbitMqConfig.getSubscriberHosts());
connectionFactory.setVirtualHost("hydra.services");
connectionFactory.setPort(rabbitMqConfig.getSubscriberPort());
connectionFactory.setUsername(rabbitMqConfig.getSubscriberUsername());
connectionFactory.setPassword(rabbitMqConfig.getSubscriberPassword());
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setErrorHandler(errorHandler());
return factory;
}
#Bean
MessageListenerAdapter listenerAdapter(EPPQ2Subscriber receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
/*#Bean
MessageListenerAdapter listenerAdapterWithChanel(EPPQ2ChanelAwareSubscriber receiverChanel) {
return new MessageListenerAdapter(receiverChanel);
}*/
#Bean
public ErrorHandler errorHandler() {
return new ConditionalRejectingErrorHandler(fatalExceptionStrategy());
}
#Bean
public ScrubberFatalExceptionStrategy fatalExceptionStrategy() {
return new ScrubberFatalExceptionStrategy();
}
}
and Latest Topic Configuration.
#Component
#Configuration
public class TopicConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TopicConfiguration.class);
#Autowired
RabbitMqConfig rabbitMqConfig;
#Autowired EPPQ2Publisher eppQ2Publisher;
/**
* Bean Queue
* #return Queue
*/
#Bean
Queue queue() {
return new Queue(rabbitMqConfig.getPublisherQueueName(), false);
}
/**
* Bean TopicExchage
* #return TopicExchage
*/
#Bean
TopicExchange exchange() {
return new TopicExchange(rabbitMqConfig.getPublisherTopic());
}
/**
* Bean BindingBuilder
* #param queue - Queue
* #param exchange - TopicExchange
* #return BindingBuilder
*/
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(rabbitMqConfig.getRoutingKey());
}
/**
* Caching connection factory
* #return CachingConnectionFactory
*/
#Bean
public CachingConnectionFactory cachingConnectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(rabbitMqConfig.getPublisherHosts(),
rabbitMqConfig.getPublisherPort());
connectionFactory.setUsername(rabbitMqConfig.getPublisherUsername());
connectionFactory.setPassword(rabbitMqConfig.getPublisherPassword());
return connectionFactory;
}
/**
* Bean RabbitTemplate
* #return RabbitTemplate
*/
#Bean
public RabbitTemplate rabbitTemplate() {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory());
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
rabbitTemplate.setRetryTemplate(retryTemplate);
/* rabbitTemplate.setExchange(rabbitMqConfig.getPublisherTopic());
rabbitTemplate.setRoutingKey(rabbitMqConfig.getRoutingKey());*/
rabbitTemplate.setConfirmCallback((correlation, ack, reason) -> {
if(correlation != null ) {
LOGGER.info("Received " + (ack ? " ack " : " nack ") + "for correlation: " + correlation);
if(ack) {
// this is confirmation received..
// here is code to ack Q1. correlation.getId() and ack
eppQ2Publisher.ackMessage(new
Long(correlation.getId().toString()));
} else {
// no confirmation received and no need to do any
}
}
});
rabbitTemplate.setReturnCallback(
(message, replyCode, replyText,
exchange, routingKey) ->
{
LOGGER.error("Returned: " + message + "\nreplyCode: " +
replyCode
+ "\nreplyText: " + replyText + "\nexchange/rk: " +
exchange + "/" + routingKey);
});
return rabbitTemplate;
}
/**
* Bean Jackson2JsonMessageConverter
* #return Jackson2JsonMessageConverter
*/
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
It's not clear what you are asking. If you mean the subscriber user doesn't have permissions to write to that exchange, your wiring is wrong.
You don't show the subscriber configuration.
Is it possible the subscriber connection factory bean is also called connectionFactory? In that case one or the other will win.
They need different bean named.
Please check the permissions for your user if while initiating the consumer/producer we don't have the exchange name it will fallback to default

How to set the replyTo with JmsTemplate?

I have a consumer consumes a message do some transformation and create a new Pojo and pass to a producer.
The producer sends the message in queue using the JmsTemplate.
The producer should set the headers of the original message like (JMSType, JMSCorrelationID, JMSExpiration, JMSDeliveryMode) to the new message to send.
But the producer should to change the replyTo Destination of the original message.
I haven't found a way to create a Destination and set to the JMSReplyTo.
Some have an idea how I can do that?
Maybe the JmsTemplate is not the correct class to do that.
public class Producer {
private final JmsTemplate jmsTemplate;
#Value("${jms-destination}")
private String destination;
public Producer(#Autowired JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void send(final MessageHeaders headers, final Pojo pojo) {
Validate.notNull(order);
jmsTemplate.convertAndSend(destination, pojo, (message) -> {
final Destination replyToDestination = ???;
message.setJMSReplyTo(replyToDestination);
message.setJMSType((String) headers.get(JmsHeaders.TYPE));
message.setJMSCorrelationID((String) headers.get(JmsHeaders.CORRELATION_ID));
message.setJMSExpiration((long) headers.get(JmsHeaders.EXPIRATION));
message.setJMSDeliveryMode((int) headers.get(JmsHeaders.DELIVERY_MODE));
return message;
});
}
}
I have found only this way to do, but I don't like and I don't sure that this introduce side effect:
public class Producer {
private final JmsTemplate jmsTemplate;
#Value("${jms-destination}")
private String destination;
#Value("${jms-replyTo}")
private String replyTo;
public Producer(#Autowired JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void send(final MessageHeaders headers, Pojo pojo) {
Validate.notNull(order);
jmsTemplate.convertAndSend(destination, order, (message) -> {
final Destination replyToDestination = buildReplyTo();
message.setJMSReplyTo(replyToDestination);
message.setJMSType((String) headers.get(JmsHeaders.TYPE));
message.setJMSCorrelationID((String) headers.get(JmsHeaders.CORRELATION_ID));
message.setJMSExpiration((long) headers.get(JmsHeaders.EXPIRATION));
message.setJMSDeliveryMode((int) headers.get(JmsHeaders.DELIVERY_MODE));
return message;
});
}
private Destination buildReplyTo() throws JMSException {
final Session session = jmsTemplate.getConnectionFactory().createConnection()
.createSession(false, Session.AUTO_ACKNOWLEDGE);
final Destination queue =
jmsTemplate.getDestinationResolver().resolveDestinationName(session, replyTo, false);
return queue;
}
}
Your solution creates side-along connections, which are not closed.
You should use existing session object and send your pojo manually through send API. Use getRequiredMessageConverter to convert your pojo.
public void send(final MessageHeaders headers, Pojo pojo) {
Validate.notNull(order);
final String responseQueue = "responseQ";
jmsTemplate.send(destination,
session -> {
Message message = getRequiredMessageConverter().toMessage(message, session);
message.setJMSReplyTo(session.createQueue(responseQueue)); //fill queue
//any other setters
return message;
});
}
// based on Spring JmsTemplate library
private MessageConverter getRequiredMessageConverter() throws IllegalStateException {
MessageConverter converter = jmsTemplate.getMessageConverter();
if (converter == null) {
throw new IllegalStateException("No 'messageConverter' specified. Check configuration of JmsTemplate.");
} else {
return converter;
}
}

Resources