I am working on a Spring application which will receive around 500 xml messages per minute. The xml configuration below only allows to process around 60 messages per minute, rest of the messages are stored in the queue (persisted in DB) and they are retrieved at the rate of 60 messages per minute.
Tried reading documentation from multiple sources but still not clear on the role of Poller combined with task executor. My understanding of why 60 messages per minute are processed currently is because the "fixed-delay" value in the poller configuration is set to 10 (so it will poll 6 times in 1 minute) and the "max-messages-per-poll" is set to 10 so 6x10=60 messages are being processed per minute.
Please advise if my understanding is not correct and help to modify the xml configuration to achieve processing of incoming messages at a higher rate.
The role of task executor is unclear too - does it mean that pool-size="50" will allow 50 threads to run in parallel to process the messages polled by the poller?
What I want in entirety is:
JdbcChannelMessageStore is used to store the incoming xml messages in the database (INT_CHANNEL_MESSAGE) table. This is required so in case of server restart messages are still stored in the table and not lost.
Incoming messages to be executed in parallel but in a controlled/limited amount. Based on the capacity of system processing these messages, I would like to limit how many messages system should process in parallel.
As this configuration will be used on multiple servers in a cluster, any server can pickup any message so it should not cause any conflict of same message being processed by two servers. Hopefully that is handled by Spring Integration.
Apologies if this has been answered elsewhere but after reading numerous posts I still don't understand how this works.
Thanks in advance.
<!-- Message Store configuration start -->
<!-- JDBC message store configuration -->
<bean id="store" class="org.springframework.integration.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="dataSource"/>
<property name="channelMessageStoreQueryProvider" ref="queryProvider"/>
<property name="region" value="TX_TIMEOUT"/>
<property name="usingIdCache" value="true"/>
</bean>
<bean id="queryProvider" class="org.springframework.integration.jdbc.store.channel.MySqlChannelMessageStoreQueryProvider" />
<int:transaction-synchronization-factory
id="syncFactory">
<int:after-commit expression="#store.removeFromIdCache(headers.id.toString())" />
<int:after-rollback expression="#store.removeFromIdCache(headers.id.toString())" />
</int:transaction-synchronization-factory>
<task:executor id="pool" pool-size="50" queue-capacity="100" rejection-policy="CALLER_RUNS" />
<int:poller id="messageStorePoller" fixed-delay="10"
receive-timeout="500" max-messages-per-poll="10" task-executor="pool"
default="true" time-unit="SECONDS">
<int:transactional propagation="REQUIRED"
synchronization-factory="syncFactory" isolation="READ_COMMITTED"
transaction-manager="transactionManager" />
</int:poller>
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<!-- 1) Store the message in persistent message store -->
<int:channel id="incomingXmlProcessingChannel">
<int:queue message-store= "store" />
</int:channel>
<!-- 2) Check in, Enrich the headers, Check out -->
<!-- (This is the entry point for WebService requests) -->
<int:chain input-channel="incomingXmlProcessingChannel" output-channel="incomingXmlSplitterChannel">
<int:claim-check-in message-store="simpleMessageStore" />
<int:header-enricher >
<int:header name="CLAIM_CHECK_ID" expression="payload"/>
<int:header name="MESSAGE_ID" expression="headers.id" />
<int:header name="IMPORT_ID" value="XML_IMPORT"/>
</int:header-enricher>
<int:claim-check-out message-store="simpleMessageStore" />
</int:chain>
Added after response from Artem:
Thanks Artem. So, on every poll which happens after a fixed delay of 10 seconds (as per the config above), the task executor will check the task queue and if possible (and required) start a new task? And each pollingTask (thread) will receive "10" messages, as per the "maxMessagesPerPoll" config, from the message store (queue).
In order to achieve higher processing time of incoming messages, should I reduce the fixedDelay on poller so that more threads can be started by the task executor? If I set the fixedDelay to 2 seconds, a new thread will be started to execute 10messages and roughly 30 such threads will be started in a minute, processing "roughly" 300 incoming messages in a minute.
Sorry for asking too much in one question - just wanted to explain the complete problem.
The main logic is behind this class:
private final class Poller implements Runnable {
private final Callable<Boolean> pollingTask;
Poller(Callable<Boolean> pollingTask) {
this.pollingTask = pollingTask;
}
#Override
public void run() {
AbstractPollingEndpoint.this.taskExecutor.execute(() -> {
int count = 0;
while (AbstractPollingEndpoint.this.initialized
&& (AbstractPollingEndpoint.this.maxMessagesPerPoll <= 0
|| count < AbstractPollingEndpoint.this.maxMessagesPerPoll)) {
try {
if (!Poller.this.pollingTask.call()) {
break;
}
count++;
}
catch (Exception e) {
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
else {
Message<?> failedMessage = null;
if (AbstractPollingEndpoint.this.transactionSynchronizationFactory != null) {
Object resource = TransactionSynchronizationManager.getResource(getResourceToBind());
if (resource instanceof IntegrationResourceHolder) {
failedMessage = ((IntegrationResourceHolder) resource).getMessage();
}
}
throw new MessagingException(failedMessage, e);
}
}
finally {
if (AbstractPollingEndpoint.this.transactionSynchronizationFactory != null) {
Object resource = getResourceToBind();
if (TransactionSynchronizationManager.hasResource(resource)) {
TransactionSynchronizationManager.unbindResource(resource);
}
}
}
}
});
}
}
As you see the taskExecutor is responsible to spin the pollingTask until the maxMessagesPerPoll in one thread. The other threads from the pool are going to be involved if the current polling task is too long for a new schedule. But all the messages in one poll are processed in the same thread, not in parallel .
That is how it works. Since you are asking too much in one SO question, I hope this information is enough to figure out next your steps.
Related
I am using a (already set up) Spring Integration worfklow in which I try to add a service-activator that will basically count messages.
In this example I log the payload of the message but only for debugging purposes.
<bean id="messageCounterActivator" class="com.my.company.activators.MessageCounterActivator"/>
<bean id="jmsQueue2" class="com.ibm.mq.jms.MQQueue" depends-on="jmsConnectionFactory">...</bean>
<int:channel id="channelMQ_MQ" ></int:channel>
<int:service-activator input-channel="channelMQ_MQ" method="countMessage" ref="messageCounterActivator"/>
<int-jms:message-driven-channel-adapter id="jmsIn" channel="channelMQ_MQ" destination="jmsQueue"/>
<int-jms:outbound-channel-adapter channel="channelMQ_MQ"
id="jmsOut2"
destination="jmsQueue2"
connection-factory="connectionFactoryCaching2"
delivery-persistent="true"
explicit-qos-enabled="true"
session-transacted="true" />
And from the activator bean:
public Message<?> countMessage(Message<?> message) {
LOG.debug("=> " + message.getPayload());
someCounter.increment();
return message;
}
Once this bidge is up, it works well : all messages are routed from A to B (MqSeries => MqSeries), and the counter is updated.... every 2 messages!
MessageCounterActivator - => message 1
MessageCounterActivator - => message 3
MessageCounterActivator - => message 5
MessageCounterActivator - => message 7
MessageCounterActivator - => message 9
MessageCounterActivator - => message 11
MessageCounterActivator - => message 13
MessageCounterActivator - => message 15
MessageCounterActivator - => message 17
MessageCounterActivator - => message 19
This is very odd to me, but I suspect some embedded activator/listener is in competition with my service-activator (I read from some posts here that when 2 activators, including logger ones, are active at once : you may have such behavior). Maybe it's because I have 2 channel adapters? I don't see here who is the competitor here.
Any idea how to have my activator working the right way?
I would say your counting logic is not a part of main flow. So, better to look into a wire-tap pattern and consider to use an <int:outbound-channel-adapter> instead. Just because you not going to return anything from that counting method.
Another approach for calling different endpoints for the same message is an <int:publish-subscribe-channel>.
Right now you declare a DirectChannel with that <int:channel id="channelMQ_MQ">, which has a round-robing distribution strategy by default for its subscribers. That's exactly why you see that odd-even behavior for your subscribers.
Not sure why you are missing that, but you indeed have two subscribers for this direct channel:
<int:service-activator input-channel="channelMQ_MQ"
<int-jms:outbound-channel-adapter channel="channelMQ_MQ"
See more in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/core.html#channel-implementations-directchannel
I have a Integration flow configured using Java DSL which pulls file from Ftp server using Ftp.inboundChannelAdapter then transforms it to JobRequest, then I have a .handle() method which triggers my batch job, everything is working as per required but the process in running sequentially for each file inside the FTP folder
I added currentThreadName in my Transformer Endpoint it was printing same thread name for each file
Here is what I have tried till now
1.task executor bean
#Bean
public TaskExecutor taskExecutor(){
return new SimpleAsyncTaskExecutor("Integration");
}
2.Integration flow
#Bean
public IntegrationFlow integrationFlow(JobLaunchingGateway jobLaunchingGateway) throws IOException {
return IntegrationFlows.from(Ftp.inboundAdapter(myFtpSessionFactory)
.remoteDirectory("/bar")
.localDirectory(localDir.getFile())
,c -> c.poller(Pollers.fixedRate(1000).taskExecutor(taskExecutor()).maxMessagesPerPoll(20)))
.transform(fileMessageToJobRequest(importUserJob(step1())))
.handle(jobLaunchingGateway)
.log(LoggingHandler.Level.WARN, "headers.id + ': ' + payload")
.route(JobExecution.class,j->j.getStatus().isUnsuccessful()?"jobFailedChannel":"jobSuccessfulChannel")
.get();
}
3.I also read in another SO thread that I need ExecutorChannel so I configured one but I don't know how to inject this channel into my Ftp.inboundAdapter, from logs is see that the channel is always integrationFlow.channel#0 which I guess is a DirectChannel
#Bean
public MessageChannel inputChannel() {
return new ExecutorChannel(taskExecutor());
}
I dont know what I'm missing here, or I might have not properly understood Spring Messaging System as I'm very much new to Spring and Spring-Integration
Any help is appreciated
Thanks
The ExecutorChannel you can simply inject into the flow and it is going to be applied to the SourcePollingChannelAdapter by the framework. So, having that inputChannel defined as a bean you just do this:
.channel(inputChannel())
before your .transform(fileMessageToJobRequest(importUserJob(step1()))).
See more in docs: https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-channels
On the other hand to process your files in parallel according your .taskExecutor(taskExecutor()) configuration, you just need to have a .maxMessagesPerPoll(20) as 1. The logic in the AbstractPollingEndpoint is like this:
this.taskExecutor.execute(() -> {
int count = 0;
while (this.initialized && (this.maxMessagesPerPoll <= 0 || count < this.maxMessagesPerPoll)) {
if (pollForMessage() == null) {
break;
}
count++;
}
So, we do have tasks in parallel, but only when they reach that maxMessagesPerPoll where it is 20 in your current case. There is also some explanation in the docs: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#endpoint-pollingconsumer
The maxMessagesPerPoll property specifies the maximum number of messages to receive within a given poll operation. This means that the poller continues calling receive() without waiting, until either null is returned or the maximum value is reached. For example, if a poller has a ten-second interval trigger and a maxMessagesPerPoll setting of 25, and it is polling a channel that has 100 messages in its queue, all 100 messages can be retrieved within 40 seconds. It grabs 25, waits ten seconds, grabs the next 25, and so on.
We have an application with around 75 partitioned steps spread around 100 jobs. Our configuration for the outbound gateway is:
<int-jms:outbound-gateway
id="outbound-gateway_1"
auto-startup="true"
connection-factory="jmsConnectionFactory"
request-channel="jms.requests_1"
request-destination="jms.requestsQueue"
reply-channel="jms.reply_1"
reply-destination="jms.repliesQueue"
receive-timeout="${timeout}"
correlation-key="JMSCorrelationID" >
<int-jms:reply-listener receive-timeout="1000"/>
</int-jms:outbound-gateway>
When autostart="true" we see the replyListener thread for each outbound gateway. To remove this extra load and resource consumption, we change to autostart="false" and added a step listener for the partitioned step the start and stop the gateway in the beforeStep and afterStep methods. At server startup the replyListener threads are not there as expected. They appear during step execution but are not removed after the call to stop on the outbound gateway (even after waiting a prolonged period).
Is something else needed to cleanup the replyListener?
OK, I see what you mean. That looks like:
while ((active = isActive()) && !isRunning()) {
if (interrupted) {
throw new IllegalStateException("Thread was interrupted while waiting for " +
"a restart of the listener container, but container is still stopped");
}
if (!wasWaiting) {
decreaseActiveInvokerCount();
}
wasWaiting = true;
try {
lifecycleMonitor.wait();
}
catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
interrupted = true;
}
}
in the DefaultMessageListenerContainer.AsyncMessageListenerInvoker.executeOngoingLoop()
The point there is lifecycleMonitor.wait(); and pay attention to the message of the IllegalStateException.
Not sure what is the purpose of such a design, but we dontt have choice unless live with that as is.
Further the logic in the start() is based on the this.pausedTasks local cache, which is filled during doInitialize() if container !this.running.
Feel free to raise a JIRA, if you think that logic must be changed somehow.
From what I understand (please correct me if I am wrong), in tomcat incoming websocket messages are processed sequentially. Meaning that if you have 100 incoming messages in one websocket, they will be processed using only one thread one-by-one from message 1 to message 100.
But this does not work for me. I need to concurrently process incoming messages in a websocket in order to increase my websocket throughput. The messages coming in do not depend on each other hence do not need to be processed sequentially.
The question is how to configure tomcat such that it would assign multiple worker threads per websocket to process incoming messages concurrently?
Any hint is appreciated.
This is where in tomcat code that I think it is blocking per websocket connection (which makes sense):
/**
* Called when there is data in the ServletInputStream to process.
*
* #throws IOException if an I/O error occurs while processing the available
* data
*/
public void onDataAvailable() throws IOException {
synchronized (connectionReadLock) {
while (isOpen() && sis.isReady()) {
// Fill up the input buffer with as much data as we can
int read = sis.read(
inputBuffer, writePos, inputBuffer.length - writePos);
if (read == 0) {
return;
}
if (read == -1) {
throw new EOFException();
}
writePos += read;
processInputBuffer();
}
}
}
You can't configure Tomcat to do what you want. You need to write a message handler that consumes the message, passes it to an Executor (or similar for processing) and then returns.
My integration context is as follows :
<int:channel id="fileInboundChannelAdapter"/>
<int-file:inbound-channel-adapter directory="${directory}" channel="fileInboundChannelAdapter" auto-startup="false" >
<int:poller fixed-rate="5000" max-messages-per-poll="1" />
</int-file:inbound-channel-adapter>
And I am manually triggering this channel after some condition is met:
#Resource(name = "fileInboundChannelAdapter")
private MessageChannel messageChannel;
Inside some method
Message<File> fileMessage = MessageBuilder.withPayload(fileObject).build();
boolean success = messageChannel.send(fileMessage, 1000 * 60);
At this line, the messageChannel.send doesnot respond even after the time out exceeds and no other request is served, And needs to restart the server.
You must share a subscriber for that fileInboundChannelAdapter. Having that we will try to understand what's going on. And take a look to logs to figure the issue from your side.
timeout param (1000 * 60 in your case) doesn't have value for the DirectChannel:
protected boolean doSend(Message<?> message, long timeout) {
try {
return this.getRequiredDispatcher().dispatch(message);
}
catch (MessageDispatchingException e) {
String description = e.getMessage() + " for channel '" + this.getFullChannelName() + "'.";
throw new MessageDeliveryException(message, description, e);
}
}
So, it looks like your subscriber just blocks the calling thread somehow...
Need to see its code.