Spring Integration Framework - Slowness in registering flows dynamically - spring-boot

We are developing a Spring Boot (2.4.0) application that uses Spring Integration framework(5.4.1) to build SOAP Integration flows and register them dynamically. The time taken to register ‘IntegrationFlow’ with ‘FlowContext’ is increasing exponentially as the number of flows being registered increase.
Following is a quick snapshot of time taken to register flows:
5 flows – 500 ms
100 flows – 80 sec
300 flows – 300 sec
We see that first few flows are taking about 100ms to register, and as it reaches 300 it is taking up to 7 sec to register each flow. These flows are identical in nature (and they simply log an info message and return).
Any help to resolve this issue would be highly appreciated.
SoapFlowsAutoConfiguration.java (Auto Configuration class that registers Flows dynamically(manually))
#Bean
public UriEndpointMapping uriEndpointMapping(
ServerProperties serverProps,
WebServicesProperties webServiceProps,
IntegrationFlowContext flowContext,
FlowMetadataProvider flowMetadataProvider,
#ErrorChannel(Usage.SOAP) Optional<MessageChannel> errorChannel,
BeanFactory beanFactory) {
UriEndpointMapping uriEndpointMapping = new UriEndpointMapping();
uriEndpointMapping.setUsePath(true);
Map<String, Object> endpointMap = new HashMap<>();
flowMetadataProvider
.flowMetadatas()
.forEach(
metadata -> {
String contextPath = serverProps.getServlet().getContextPath();
String soapPath = webServiceProps.getPath();
String serviceId = metadata.id();
String serviceVersion = metadata.version();
String basePath = contextPath + soapPath;
String endpointPath = String.join("/", basePath, serviceId, serviceVersion);
SimpleWebServiceInboundGateway inboundGateway = new SimpleWebServiceInboundGateway();
errorChannel.ifPresent(inboundGateway::setErrorChannel);
endpointMap.put(endpointPath, inboundGateway);
IntegrationFlowFactory flowFactory = beanFactory.getBean(metadata.flowFactoryClass());
IntegrationFlow integrationFlow =
IntegrationFlows.from(inboundGateway).gateway(flowFactory.createFlow()).get();
flowContext.registration(integrationFlow).register();
});
uriEndpointMapping.setEndpointMap(endpointMap);
return uriEndpointMapping;
}
SoapFlow.java (Integration Flow)
#Autowired private SoapFlowResolver soapFlowResolver;
#Autowired private CoreFlow delegate;
#Override
public IntegrationFlow createFlow() {
IntegrationFlow a =
flow -> flow.gateway(soapFlowResolver.resolveSoapFlow(delegate.createFlow()));
return a;
}
SoapFlowResolver.java (Common class used by all integration flows to delegate request to a Coreflow that is responsible for business logic implementation)
public IntegrationFlow resolveSoapFlow(
IntegrationFlow coreFlow) {
return flow -> {
flow.gateway(coreFlow);
};
}
CoreFlow.java (Class that handles the business logic)
#Override
public IntegrationFlow createFlow() {
return flow -> flow.logAndReply("Reached CoreFlow");
}

You are crating too many beans, where each of them checks the rest if it wasn't created before. That's how you get increase with the start time when you add more and more flows dynamically.
What I see is an abuse of the dynamic flows purpose. Each time we decide to go this way we need to think twice if we definitely need to have the whole flow as a fresh instance. Again: the flow is not volatile object, it registers a bunch of beans in the application context which are going to stay there until you remove them. And they are singletons, so can be reused in any other places of your application.
Another concern that you don't count with the best feature of Spring Integration MessageChannel pattern implementation. You definitely can have some common flows in advance and connect your dynamic with those through channel between them. You probably just need to create dynamically a SimpleWebServiceInboundGateway and wire it with the channel for your target logic which is the same for all the flows and so on.

Related

Spring Integration Flow with #Restcontoller Timing issue

A simple #RestController is connected with a #MessagingGateway to an IntegrationFlow.
After a load test we saw within the tracing that we lose "a lot of time" before even starting the processing within the flow:
Tracing result
In this example we can see that over 90ms spend befor sending the message to the flow.
Did anyone have some idea what leads to this behavior?
As far as I understood the documentation, everything is handled in the sender thread and therefore no special worker threads are created.
We use the Restcontroller since we need to create the documentation with springdoc-openapi-ui
ExampleCode:
RestController
#RestController
public class DescriptionEndpoint {
HttpMessageGateway httpMessageGateway;
public Result findData(#Valid dataRequest dataRequest) {
final Map<String, Object> headerParams = new HashMap<>();
return httpMessageGateway.basicDataDescriptionFlow(dataRequest, headerParams);
}
}
Gateway
#MessagingGateway
public interface HttpMessageGateway {
#Gateway(requestChannel = "startDataFlow.input")
Result basicDataDescriptionFlow(#Payload dataRequest prDataRequest, #Headers Map<String, Object> map);
}
IntegrationFlow
public class ExampleFlow {
#Bean
public IntegrationFlow startDataFlow() {
return new FlowExtension()
.handle(someHandler1)
.handle(someHandler2)
.handle(someHandler3)
.get();
}
}
After adding some more traces I realized, that this timing issue is caused by my spring security configuration.
Unfortunatelly, i thought, the span is only representing the time after the start of findData(..). But it seems, the tracing starts already in the proxy methods and security chain.
After improving some implementation on our JWTToken filter, the spend times for these endpoints are OK.

WebServiceTemplate performance issue with spring boot

I am consuming soap web services using WebServiceTemplate its working fine with good performance in spring3+ .
spring :- 3.2.4.RELEASE
spring-ws-core :- 2.1.4.RELEASE
spring-ws-support :- 2.1.4.RELEASE
spring-ws-security :-2.1.4.RELEASE
Class for calling soap service
SaajSoapMessageFactory messageFactory = new SaajSoapMessageFactory(MessageFactory.newInstance());
messageFactory.afterPropertiesSet();
WebServiceTemplate webServiceTemplate = new WebServiceTemplate(messageFactory);
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("some package");
marshaller.afterPropertiesSet();
webServiceTemplate.setMarshaller(marshaller);
webServiceTemplate.setUnmarshaller(marshaller);
webServiceTemplate.afterPropertiesSet();
webServiceTemplate.setInterceptors(clientInterceptors);
webServiceTemplate.setMessageSender(webServiceMessageSenderWithAuth);
webServiceTemplate.setDefaultUri(url);
Output result= ((JAXBElement<Output >) webServiceTemplate.marshalSendAndReceive(jaxbRequest)).getValue();
Configuration File
#Configuration
public class WebServiceConfiguration {
#Autowired
private SaajSoapMessageFactory messageFactory;
#Autowired
private WebServiceMessageSenderWithAuth webServiceMessageSenderWithAuth;
#Bean
public Wss4jSecurityInterceptor getWss4jSecurityInterceptor(#Value("${WSDL.UserName}") String userName,
#Value("${WSDL.Password}") String password) {
Wss4jSecurityInterceptor wss4jSecurityInterceptor = new Wss4jSecurityInterceptor();
wss4jSecurityInterceptor.setSecurementActions("UsernameToken");
wss4jSecurityInterceptor.setSecurementPasswordType("PasswordText");
wss4jSecurityInterceptor.setSecurementUsername(userName);
wss4jSecurityInterceptor.setSecurementPassword(password);
return wss4jSecurityInterceptor;
}
#Bean
public SaajSoapMessageFactory getSaajSoapMessageFactory() {
return new SaajSoapMessageFactory();
}
#Bean
public ClientInterceptor[] clientInterceptors(Wss4jSecurityInterceptor wsSecurityInterceptor) {
return new ClientInterceptor[] { wsSecurityInterceptor };
}
}
Performance result
Timings -- around 500ms average time , max time :- 1 sec
Spring Boot 1.5.20.RELEASE and 2.2.2.RELEASE
With spring boot same code without any change takes around 4sec for first call and if continue hitting the same then takes around 2sec for
Performance result with spring boot
First Call :- 4 sec
Subsequent calls without interval (1-10 sec gap) :- 2 sec to 800 ms
Its keep on decreasing while keep hitting the same call again and again with less interval and goes down to spring mvc 3 like result but if tried again after some interval like 5 min then again follow the same pattern
If same tried after 5 mins then again the result is same for first and further calls.
Note:- With spring boot i have tried wss4j as well instead of wss4j2
Also tried AxiomSoapMessageFactory but no luck
i have tried connection keep alive etc etc but still no luck
Caching could be one of the factors for the results above
Caching is a mechanism to enhance the performance of a system. It is a temporary memory that lies between the application and the persistent database. Cache memory stores recently used data items in order to reduce the number of database hits as much as possible.
JVM warm-up effect
When a JVM based app is launched, the first requests it receives are generally significantly slower than the average response time. This warm-up effect is usually due to class loading and bytecode interpretation at startup.
For further optimizing the application, use the Hypersistence Optimizer, which allows you to get the most out of JPA and Spring boot by scanning your application configuration and mappings.
Running Hypersitence Optimizer is very easy, as you just have to pass the EntityManagerFactory instance to the HypersistenceOptimizer object constructor, and call the init method
I suppose you already did, but if you haven't, take a look at Faster StartUp and implement the fixes suggested there.
For disabling scanning with embedded tomcat, there is a suggestion in the comments here Tomcat JarScanning
Enable Asynchronous Calls in a SpringBootApplication
#EnableSync
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
So the issue is not with code. I finally deployed it on jboss Wildfly and bang..
It just start performing really well without a single line change.
Now its taking around 300ms to 500ms. So the problem is with embedded tomcat and embedded jetty is not good

Spring Integration splitter and aggregator configuration

I have a scenario where I need to invoke A and B system's REST call in parallel and aggregate the responses and transform into single FinalResponse.
To achieve this, I am using Spring Integration splitter and agreggator and the configuration is as given below.
I have exposed a REST endpoint, when the request(request has co-relationId in the header) comes to the controller, we invoke gateway and the splitter sends requests to A and B channels .
Service activator A listens to channel A and invokes A system's REST call and service Activator B listens to B channel and invokes B system's REST call.
Then I need to aggregate the responses from A and B system and then transform it into FinalResponse. Currently the aggregation and transformation is working fine.
Sometimes when multiple requests come to controller, the FinalResponse takes more time when compared to single request to controller. All the responses to the requests come almost at the same time not sure why (even though the last request to controller was sent 6-7 secs after the 1st request). Is there something wrong in my configuration related to threads? Not sure why it takes more time to respond when multiple requests comes to the controller.
Also, I am not using any CorrelationStrategy, do we need to use it? Will I face any issues in multi threading environment with the below configuration? Any feedback on the configuration would be helpful
// Controller
{
FinalResponse aggregatedResponse = gateway.collateServiceInformation(inputData);
}
//Configuration
#Autowired
Transformer transformer;
//Gateway
#Bean
public IntegrationFlow gatewayServiceFlow() {
return IntegrationFlows.from("input_channel")
.channel("split_channel").get();
}
//splitter
#Bean
public IntegrationFlow splitAggregatorFlow() {
return IntegrationFlows.from("split_channel").
.split(SomeClass.class, SomeClass::getResources)
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.<Resource, String>route(Resource::getName,
mapping -> mapping.channelMapping("A", "A")
.channelMapping("B", "B"))
.get();
}
//aggregator
#Bean
public IntegrationFlow aggregateFlow() {
return IntegrationFlows.from("aggregate_channel").aggregate()
.channel("transform_channel").transform(transformer).get();
}
.
.
.
//Transformer
#Component
#Scope("prototype")
public class Transformer {
#Transformer
public FinalResponse transform(final List<Result> responsesFromAAndB) {
//transformation logic and then return final response
}
}
The splitter provides a default strategy for correlation details in headers. The Aggregator will use them afterwards. What you talk about is called scatter-gather: https://docs.spring.io/spring-integration/docs/5.0.8.RELEASE/reference/html/messaging-routing-chapter.html#scatter-gather. There is a Java DSL equivalent.
I think your problem that some request in the splitted set fails, so am Aggregator can’t finish a group for that request. Nothing obvious so far in your config...

Spring Cloud Stream RabbitMQ

I am trying to understand why I would want to use Spring cloud stream with RabbitMQ. I've had a look at the RabbitMQ Spring tutorial 4 (https://www.rabbitmq.com/tutorials/tutorial-four-spring-amqp.html) which is basically what I want to do. It creates a direct exchange with 2 queues attached and depending on the routing key a message is either routed to Q1 or to Q2.
The whole process is pretty straight forward if you look at the tutorial, you create all the parts, bind them together and youre ready to go.
I was wondering what benefit I would gain in using Sing Cloud Stream and if that is even the use case for it. It was easy to create a simple exchange and even defining destination and group was straight forward with stream. So I thought why not go further and try to handle the tutorial case with stream.
I have seen that Stream has a BinderAwareChannelResolver which seems to do the same thing. But I am struggling to put it all together to achieve the same as in the RabbitMQ Spring tutorial. I am not sure if it is a dependency issue, but I seem to misunderstand something fundamentally here, I thought something like:
spring.cloud.stream.bindings.output.destination=myDestination
spring.cloud.stream.bindings.output.group=consumerGroup
spring.cloud.stream.rabbit.bindings.output.producer.routing-key-expression='key'
should to the trick.
Is there anyone with a minimal example for a source and sink which basically creates a direct exchange, binds 2 queues to it and depending on routing key routes to either one of those 2 queues like in https://www.rabbitmq.com/tutorials/tutorial-four-spring-amqp.html?
EDIT:
Below is a minimal set of code which demonstrates how to do what I asked. I did not attach the build.gradle as it is straight forward (but if anyone is interested, let me know)
application.properties: setup the producer
spring.cloud.stream.bindings.output.destination=tut.direct
spring.cloud.stream.rabbit.bindings.output.producer.exchangeType=direct
spring.cloud.stream.rabbit.bindings.output.producer.routing-key-expression=headers.type
Sources.class: setup the producers channel
public interface Sources {
String OUTPUT = "output";
#Output(Sources.OUTPUT)
MessageChannel output();
}
StatusController.class: Respond to rest calls and send message with specific routing keys
/**
* Status endpoint for the health-check service.
*/
#RestController
#EnableBinding(Sources.class)
public class StatusController {
private int index;
private int count;
private final String[] keys = {"orange", "black", "green"};
private Sources sources;
private StatusService status;
#Autowired
public StatusController(Sources sources, StatusService status) {
this.sources = sources;
this.status = status;
}
/**
* Service available, service returns "OK"'.
* #return The Status of the service.
*/
#RequestMapping("/status")
public String status() {
String status = this.status.getStatus();
StringBuilder builder = new StringBuilder("Hello to ");
if (++this.index == 3) {
this.index = 0;
}
String key = keys[this.index];
builder.append(key).append(' ');
builder.append(Integer.toString(++this.count));
String payload = builder.toString();
log.info(payload);
// add kv pair - routingkeyexpression (which matches 'type') will then evaluate
// and add the value as routing key
Message<String> msg = new GenericMessage<>(payload, Collections.singletonMap("type", key));
sources.output().send(msg);
// return rest call
return status;
}
}
consumer side of things, properties:
spring.cloud.stream.bindings.input.destination=tut.direct
spring.cloud.stream.rabbit.bindings.input.consumer.exchangeType=direct
spring.cloud.stream.rabbit.bindings.input.consumer.bindingRoutingKey=orange
spring.cloud.stream.bindings.inputer.destination=tut.direct
spring.cloud.stream.rabbit.bindings.inputer.consumer.exchangeType=direct
spring.cloud.stream.rabbit.bindings.inputer.consumer.bindingRoutingKey=black
Sinks.class:
public interface Sinks {
String INPUT = "input";
#Input(Sinks.INPUT)
SubscribableChannel input();
String INPUTER = "inputer";
#Input(Sinks.INPUTER)
SubscribableChannel inputer();
}
ReceiveStatus.class: Receive the status:
#EnableBinding(Sinks.class)
public class ReceiveStatus {
#StreamListener(Sinks.INPUT)
public void receiveStatusOrange(String msg) {
log.info("I received a message. It was orange number: {}", msg);
}
#StreamListener(Sinks.INPUTER)
public void receiveStatusBlack(String msg) {
log.info("I received a message. It was black number: {}", msg);
}
}
Spring Cloud Stream lets you develop event driven micro service applications by enabling the applications to connect (via #EnableBinding) to the external messaging systems using the Spring Cloud Stream Binder implementations (Kafka, RabbitMQ, JMS binders etc.,). Apparently, Spring Cloud Stream uses Spring AMQP for the RabbitMQ binder implementation.
The BinderAwareChannelResolver is applicable for dynamically binding support for the producers and I think in your case it is about configuring the exchanges and binding of consumers to that exchange.
For instance, you need to have 2 consumers with the appropriate bindingRoutingKey set based on your criteria and a single producer with the properties(routing-key-expression, destination) you mentioned above (except the group). I noticed that you have configured group for the outbound channel. The group property is applicable only for the consumers (hence inbound).
You might also want to check this one: https://github.com/spring-cloud/spring-cloud-stream-binder-rabbit/issues/57 as I see some discussion around using routing-key-expression. Specifically, check this one on using the expression value.

How to set a Message Handler programmatically in Spring Cloud AWS SQS?

maybe someone has an idea to my following problem:
I am currently on a project, where i want to use the AWS SQS with Spring Cloud integration. For the receiver part i want to provide a API, where a user can register a "message handler" on a queue, which is an interface and will contain the user's business logic, e.g.
MyAwsSqsReceiver receiver = new MyAwsSqsReceiver();
receiver.register("a-queue-name", new MessageHandler(){
#Override
public void handle(String message){
//... business logic for the received message
}
});
I found examples, e.g.
https://codemason.me/2016/03/12/amazon-aws-sqs-with-spring-cloud/
and read the docu
http://cloud.spring.io/spring-cloud-aws/spring-cloud-aws.html#_sqs_support
But the only thing i found there to "connect" a functionality for processing a incoming message is a annotation on a method, e.g. #SqsListener or #MessageMapping.
These annotations are fixed to a certain queue-name, though. So now i am at a loss, how to dynamically "connect" my provided "MessageHandler" (from my API) to the incoming message for the specified queuename.
In the Config the example there is a SimpleMessageListenerContainer, which gets a QueueMessageHandler set, but this QueueMessageHandler does not seem
to be the right place to set my handler or to override its methods and provide my own subclass of QueueMessageHandler.
I already did something like this with the Spring Amqp integration and RabbitMq and thought, that it would be also similar here with AWS SQS.
Does anyone have an idea, how to accomplish this?
thx + bye,
Ximon
EDIT:
I found, that Spring JMS could actually do that, e.g. www.javacodegeeks.com/2016/02/aws-sqs-spring-jms-integration.html. Does anybody know, what consequences using JMS protocol has here, good or bad?
I am facing the same issue.
I am trying to go in an unusual way where I set up an Aws client bean at build time and then instead of using sqslistener annotation to consume from the specific queue I use the scheduled annotation which I can programmatically pool (each 10 secs in my case) from which queue I want to consume.
I did the example that iterates over queues defined in properties and then consumes from each one.
Client Bean:
#Bean
#Primary
public AmazonSQSAsync awsSqsClient() {
return AmazonSQSAsyncClientBuilder
.standard()
.withRegion(Regions.EU_WEST_1.getName())
.build();
}
Consumer:
// injected in the constructor
private final AmazonSQSAsync awsSqsClient;
#Scheduled(fixedDelay = 10000)
public void pool() {
properties.getSqsQueues()
.forEach(queue -> {
val receiveMessageRequest = new ReceiveMessageRequest(queue)
.withWaitTimeSeconds(10)
.withMaxNumberOfMessages(10);
// reading the messages
val result = awsSqsClient.receiveMessage(receiveMessageRequest);
val sqsMessages = result.getMessages();
log.info("Received Message on queue {}: message = {}", queue, sqsMessages.toString());
// deleting the messages
sqsMessages.forEach(message -> {
val deleteMessageRequest = new DeleteMessageRequest(queue, message.getReceiptHandle());
awsSqsClient.deleteMessage(deleteMessageRequest);
});
});
}
Just to clarify, in my case, I need multiple queues, one for each tenant, with the queue URL for each one passed in a property file. Of course, in your case, you could get the queue names from another source, maybe a ThreadLocal which has the queues you have created in runtime.
If you wish, you can also try the JMS approach where you create message consumers and add a listener to each one you wish (See the doc Aws Jms documentation).
When we do Spring and SQS we use the spring-cloud-starter-aws-messaging.
Then just create a Listener class
#Component
public class MyListener {
#SQSListener(value="myqueue")
public void listen(MyMessageType message) {
//process the message
}
}

Resources