Spring rabbitmq message ordering not working anymore - spring-integration

I am dealing with a message ordering issue, after having fixed it a while ago, now the fix does not work anymore.
Just for overview, I have the following environment:
The order is lost somewhere between tcpAdapter and the message receiver.
This issue I have fixed using:
on the producer side - using publisher confirms and returns
rabbitmq:
publisher-confirms: true
publisher-returns: true
on the consumer side - enforcing single thread executor:
The idea I found here: RabbitMQ - Message order of delivery, and I used a post processor for this.
#Component
public class RabbitConnectionFactoryPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof CachingConnectionFactory) {
((CachingConnectionFactory) bean).setExecutor(Executors.newSingleThreadExecutor());
}
return bean;
}
}
And now, after some master-pom updates (we do not control the master pom, it is at project level) the fix suddenly does not work anymore. After checking the differences, I did not see any changes on the spring-rabbit or spring-amqp, I do not understand why there is an impact.
Here are more details if you want concrete examples:
Producer.
The TCP Server sends a message to the tcpAdapter app, which uses spring-integration flow to take the message from TCP and send it to rabbitmq.
Here is the code that does this (inboundAdapterClient I did not post here because I do not think it is important):
#Bean
public IntegrationFlow tcpToRabbitFlowClient() {
return IntegrationFlows.from(inboundAdapterClient())
.transform(tcpToRabbitTransformer)
.channel(TCP_ADAPTER_SOURCE);
.get();
}
Message are received by tcpAdapter app from TCP in the right order, but then the tcpAdapter rabbitmq stack does not send them in the correct order every time (80% of the time ok, 20% wrong order)
Here is the spring boot yml configuration (only relevant info):
spring:
rabbitmq:
publisher-confirms: true
publisher-returns: true
cloud:
stream:
bindings:
tcpAdapterSource:
binder: rabbit
content-type: application/json
destination: tcpadapter.messagereceiver
Consumer.
The message receiver has the single thread executor enforced plus the configuration as below.
Here is the spring boot yml configuration (only relevant info)
spring:
cloud:
stream:
bindings:
fromTcpAdapter:
binder: rabbit
content-type: application/json
destination: tcpadapter.messagereceiver
rabbit:
default:
producer:
exchangeDurable: false
exchangeAutoDelete: true
consumer:
exchangeDurable: false
exchangeAutoDelete: true
Note: There is only one producer and one consumer.
Some versions from pom, maybe it helps:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream</artifactId>
<version>3.0.1.RELEASE</version>
</dependency>

Solved by removing yml configuration and using explicit bean declarations and factory configurations as described below. The only issue is that is slow performance wise, but this is expected with publisher confirms.
So indeed it was a producer problem.
#Bean
public CachingConnectionFactory connectionFactory() {
com.rabbitmq.client.ConnectionFactory connectionFactoryClient = new com.rabbitmq.client.ConnectionFactory();
connectionFactoryClient.setUsername(username);
connectionFactoryClient.setPassword(password);
connectionFactoryClient.setHost(hostname);
connectionFactoryClient.setVirtualHost(vhost);
return new CachingConnectionFactory(connectionFactoryClient);
}
#Bean("rabbitTemplateAdapter")
#Primary
public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory) {
connectionFactory.setPublisherConfirmType(CORRELATED);
connectionFactory.setPublisherReturns(true);
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback((correlationData, ack, cause)
-> log.debug("correlationData({}),ack({}),cause ({})", correlationData, ack, cause));
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey)
-> log.debug("exchange({}),route({}),replyCode({}),replyText({}),message:{}",
exchange, routingKey, replyCode, replyText, message));
return rabbitTemplate;
}
And for sending messages:
rabbitTemplateAdapter.invoke(t -> {
t.convertAndSend(
exchange,
DESTINATION,
jsonMessage.getPayload(),
m -> {outboundMapper().fromHeadersToRequest(jsonMessage.getHeaders(), m.getMessageProperties());
return m;
});
t.waitForConfirmsOrDie(10_000);
return true;
});
I did this using the spring rabbit and amqp versions:
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-amqp</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
Spring amqp documentation helped a lot, the technique used is called "Scoped Operations":
https://docs.spring.io/spring-amqp/docs/2.2.7.RELEASE/reference/html/#scoped-operations

Related

How to set signalfx.accessToken in spring application

I have have tried pushing custom metrics to splunk apm using below dependency and setting the properties using springboot application
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-signalfx</artifactId>
</dependency>
Properties
management.metrics.export.signalfx.access-token= <token>
management.metrics.export.signalfx.enabled=true
management.metrics.export.signalfx.uri=<uri>
management.metrics.export.signalfx.source=testservice
Now there is a requirement to try pushing from spring mvc application and I have added same dependency in the pom and created custom metric code as below but I am getting error while spring application is deployed
MeterRegistry resgRegistry = new SignalFxMeterRegistry(new SignalFxConfig() {
#Override
public String get(String key) {
// TODO Auto-generated method stub
return null;
}
}, Clock.SYSTEM);
Timer myTimer = Timer.builder("surya_timer").register(Metrics.globalRegistry);
Timer timer = Timer.builder("test").register(resgRegistry);
timer.record(Duration.ofMillis(123));
myTimer.record(Duration.ofMillis(567));
Error
io.micrometer.core.instrument.config.validate.ValidationException: signalfx.accessToken was 'null' but it is required
io.micrometer.core.instrument.config.validate.Validated$Either.orThrow(Validated.java:375)
io.micrometer.core.instrument.config.MeterRegistryConfig.requireValid(MeterRegistryConfig.java:49)
io.micrometer.core.instrument.push.PushMeterRegistry.<init>(PushMeterRegistry.java:42)
io.micrometer.core.instrument.step.StepMeterRegistry.<init>(StepMeterRegistry.java:43)
io.micrometer.signalfx.SignalFxMeterRegistry.<init>(SignalFxMeterRegistry.java:78)
io.micrometer.signalfx.SignalFxMeterRegistry.<init>(SignalFxMeterRegistry.java:74)
Please help me How to set this access token in the spring application

What is it for redisQueueInboundGateway.setReplyChannelName

just want to ask what is the redisQueueInboundGateway.setReplyChannelName for
I got a log B and then a log.
1.My question is in what situation will the log C be printed when I set it to the RedisQueueInboundGateway.
the doc in "https://docs.spring.io/spring-integration/reference/html/redis.html#redis-queue-inbound-gateway" seems incorrect for class name and class explanation such like:
2.1 the 'RedisOutboundChannelAdapter' is named in 'RedisPublishingMessageHandler'.
2.2 the 'RedisQueueOutboundChannelAdapter' is named in 'RedisQueueMessageDrivenEndpoint'.
2.3 the explanation of Redis Queue Outbound Gateway is exactly the copy of Redis Queue Inbound Gateway.
#GetMapping("test")
public void test() {
this.teller.test("testing 1");
#Gateway(requestChannel = "inputA")
void test(String transaction);
#Bean("A")
PublishSubscribeChannel getA() {
return new PublishSubscribeChannel();
}
#Bean("B")
PublishSubscribeChannel getB() {
return new PublishSubscribeChannel();
}
#Bean("C")
PublishSubscribeChannel getC() {
return new PublishSubscribeChannel();
}
#ServiceActivator(inputChannel = "A")
void aTesting(Message message) {
System.out.println("A");
System.out.println(message);
}
#ServiceActivator(inputChannel = "B")
String bTesting(Message message) {
System.out.println("B");
System.out.println(message);
return message.getPayload() + "Basdfasdfasdfadsfasdf";
}
#ServiceActivator(inputChannel = "C")
void cTesting(Message message) {
System.out.println("C");
System.out.println(message);
}
#ServiceActivator(inputChannel = "inputA")
#Bean
RedisQueueOutboundGateway getRedisQueueOutboundGateway(RedisConnectionFactory connectionFactory) {
val redisQueueOutboundGateway = new RedisQueueOutboundGateway(Teller.CHANNEL_CREATE_INVOICE, connectionFactory);
redisQueueOutboundGateway.setReceiveTimeout(5);
redisQueueOutboundGateway.setOutputChannelName("A");
redisQueueOutboundGateway.setSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
return redisQueueOutboundGateway;
}
#Bean
RedisQueueInboundGateway getRedisQueueInboundGateway(RedisConnectionFactory connectionFactory) {
val redisQueueInboundGateway = new RedisQueueInboundGateway(Teller.CHANNEL_CREATE_INVOICE, connectionFactory);
redisQueueInboundGateway.setReceiveTimeout(5);
redisQueueInboundGateway.setRequestChannelName("B");
redisQueueInboundGateway.setReplyChannelName("C");
redisQueueInboundGateway.setSerializer(new GenericJackson2JsonRedisSerializer(new ObjectMapper()));
return redisQueueInboundGateway;
}
Your concern is not clear.
2.1
There is a component (pattern name) and there is a class on background covering the logic.
Sometime they are not the same.
So, Redis Outbound Channel Adapter is covered by the RedisPublishingMessageHandler, just because there is a ConsumerEndpointFactoryBean to consume messages from the input channel and RedisPublishingMessageHandler to handle them. In other words the framework creates two beans to make such a Redis interaction working. In fact all the outbound channel adapters (gateways) are handled the same way: endpoint plus handler. Together they are called adapter or gateway depending on the type of the interaction.
2.2
I don't see such a misleading in the docs.
2.3
That's not true.
See difference:
Spring Integration introduced the Redis queue outbound gateway to perform request and reply scenarios. It pushes a conversation UUID to the provided queue,
Spring Integration 4.1 introduced the Redis queue inbound gateway to perform request and reply scenarios. It pops a conversation UUID from the provided queue
All the inbound gateways are supplied with an optional replyChannel to track the replies. It is not where this type of gateways is going to send something. It is fully opposite: the place where this inbound gateway is going to take a reply channel to send a reply message into Redis back. The Inbound gateway is initiated externally. In our case as a request message in the configured Redis list. When you Integration flow does its work, it sends a reply message to this gateway. In most cases it is done automatically using a replyChannel header from the message. But if you would like to track that reply, you add a PublishSubscribeChannel as that replyChannel option on the inbound gateway and both your service activator and the gateway get the same message.
The behavior behind that replyChannel option is explained in the Messaging Gateway chapter: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints.html#gateway-default-reply-channel
You probably right about this section in the docs https://docs.spring.io/spring-integration/reference/html/redis.html#redis-queue-inbound-gateway and those requestChannel and replyChannel are really a copy of the text from the Outbound Gateway section. That has to be fixed. Feel free to raise a GH issue so we won't forget to address it.
The C logs are going to be printed when you send a message into that C channel, but again: if you want to make a reply correlation working for the Redis Inbound Gateway it has to be as a PublishSubscribeChannel. Otherwise just omit it and your String bTesting(Message message) { will send its result to the replyChannel header.

Spring Integration writing to IBM MQ

Using Boot 2.2.2 and Spring Integration 5.2.2 interacting with another application via IBM MQ Series 9; the messages need to be purely text (not JMS). SI can get the text messages correctly, however I cannot seem to put to MQ without a JMS header.
Using JMS without SI, I can write a pure text message by using;
jmsTemplate.send(myQueue, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(message);
}
});
When using SI I have the following;
#Bean
public IntegrationFlow toQueue(
ConnectionFactory connectionFactory,
#Value("${app.outQueue}") String myQueue
) {
return IntegrationFlows
.from("myIncomingChannel")
.headerFilter("*")
.handle(
Jms
.outboundAdapter(connectionFactory)
.configureJmsTemplate(jts -> jts.jmsMessageConverter(new SimpleMessageConverter()))
.extractPayload(true)
.destination(myQueue)
)
.get();
}
I have tried 8 combinations of;
With/without configureJmsTemplate
extractPayload true or false.
With/without headerFilter
All tests give me a JMS message on the queue. How do I get SI JMS to write a plain text message?
I resolved it using the answer from this post How to remove default Spring JMS Template headers when sending a message to an MQ?
The working version is;
.handle(
Jms
.outboundAdapter(connectionFactory)
.destination("queue:///" + myQueue + "?targetClient=1")
)
.get();

Spring Integration JMS assured message delivery using DSL

I am trying to create a flow(1) in which message is received from TCP adapter which can be client or server and it sends the message to ActiveMQ broker.
My another flow(2) pick the message from required queue and send to the destination
TCP(client/server) ==(1)==> ActiveMQ Broker ==(2)==> HTTP Outbound adapter
I want to ensure that in case my message is not delivered to the required destination then it re-attempt to send the message again.
My current flow(1) to broker is :
IntegrationFlow flow = IntegrationFlows
.from(Tcp
.inboundAdapter(Tcp.netServer(Integer.parseInt(1234))
.serializer(customSerializer).deserializer(customSerializer)
.id("server").soTimeout(5000))
.id(hostConnection.getConnectionNumber() + "adapter"))).channel(directChannel())
.wireTap("tcpInboundMessageLogChannel").channel(directChannel())
.handle(Jms.outboundAdapter(activeMQConnectionFactory)
.destination("jmsInbound"))
.get();
this.flowContext.registration(flow).id("outflow").register();
and My flow(2) from broker to http outbound :
flow = IntegrationFlows
.from(Jms.messageDrivenChannelAdapter(activeMQConnectionFactory)
.destination("jmsInbound"))
.channel(directChannel())
.handle(Http.outboundChannelAdapter(hostConnection.getUrl()).httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.mappedRequestHeaders("abc"))
.get();
this.flowContext.registration(flow).id("inflow").register();
Issue:
In case of any exception during delivery for example my destination URL is not working then it re attempt to send the message.
After unsuccessfull attempt it retry 7 times i.e max attempt to 7
If still the attempt is not successful then it send the message to ActiveMQ.DLQ (Dead letter Queue) and does not re-attempt again as message is dequeued from actual queue and send to ActiveMQ.DLQ.
So, i want the scenario that no message will be lost and message will be processed in order.
First: I believe that you can configure jmsInbound for the infinite retries:
/**
* Configuration options for a messageConsumer used to control how messages are re-delivered when they
* are rolled back.
* May be used server side on a per destination basis via the Broker RedeliveryPlugin
*
* #org.apache.xbean.XBean element="redeliveryPolicy"
*
*/
public class RedeliveryPolicy extends DestinationMapEntry implements Cloneable, Serializable {
On the other hand you can configure a .handle(Http.outboundChannelAdapter( for the RequestHandlerRetryAdvice for similar retry behavior but inside the application without round trips to the JMS and back: https://docs.spring.io/spring-integration/docs/5.0.6.RELEASE/reference/html/messaging-endpoints-chapter.html#retry-advice
Here is some sample how it can be configured from the Java DSL perspective:
#Bean
public IntegrationFlow errorRecovererFlow() {
return IntegrationFlows.from(Function.class, "errorRecovererFunction")
.handle((GenericHandler<?>) (p, h) -> {
throw new RuntimeException("intentional");
}, e -> e.advice(retryAdvice()))
.get();
}
#Bean
public RequestHandlerRetryAdvice retryAdvice() {
RequestHandlerRetryAdvice requestHandlerRetryAdvice = new RequestHandlerRetryAdvice();
requestHandlerRetryAdvice.setRecoveryCallback(new ErrorMessageSendingRecoverer(recoveryChannel()));
return requestHandlerRetryAdvice;
}
#Bean
public MessageChannel recoveryChannel() {
return new DirectChannel();
}
The RequestHandlerRetryAdvice can be configured with the RetryTemplate to apply something like AlwaysRetryPolicy. See Spring Retry project for more info: https://github.com/spring-projects/spring-retry

How to Nak a ServiceStack RabbitMQ message within the RegisterHandler?

I'd like to be able to requeue a message from within my Service Endpoint that has been wired up through the RegisterHandler method of RabbitMQ Server. e.g.
mqServer.RegisterHandler<OutboundILeadPhone>(m =>
{
var db = container.Resolve<IFrontEndRepository>();
db.SaveMessage(m as Message);
return ServiceController.ExecuteMessage(m);
}, noOfThreads: 1);
or here.
public object Post(OutboundILeadPhone request)
{
throw new OutBoundAgentNotFoundException(); // added after mythz posted his first response
}
I don't see any examples how this is accomplished, so I'm starting to believe that it may not be possible with the ServiceStack abstraction. On the other hand, this looks promising.
Thank you, Stephen
Update
Throwing an exception in the Service does nak it, but then the message is sent to the OutboundILeadPhone.dlq which is normal ServiceStack behavior. Guess what I'm looking for is a way for the message to stay in the OutboundILeadPhone.inq queue.
Throwing an exception in your Service will automatically Nak the message. This default exception handling behavior can also be overridden with RabbitMqServer's RegisterHandler API that takes an Exception callback, i.e:
void RegisterHandler<T>(
Func<IMessage<T>, object> processMessageFn,
Action<IMessage<T>, Exception> processExceptionEx);
void RegisterHandler<T>(
Func<IMessage<T>, object> processMessageFn,
Action<IMessage<T>, Exception> processExceptionEx,
int noOfThreads)

Resources