concurrentConsumers not created right away from beginning - apache-camel

I am using Camel in a Spring-Boot application to route from AMQ-Queue. Messages from this queue will be sent to a REST-Webservice. It is already working with this code line:
from("amq:queue:MyQueue").process("jmsToHttpProcessor").to(uri);
My uri looks like this:
http4://localhost:28010/application/createCustomer
Now I have the requirement that the routing to the Webservice should be done parallely:
In order to achive that, I configured concurrentConsumers in JmsConfiguration as follows:
#Bean
public JmsComponent amq(#Qualifier("amqConnectionFactory") ConnectionFactory amqConnectionFactory, AMQProperties amqProperties) {
JmsConfiguration jmsConfiguration = new JmsConfiguration(amqConnectionFactory);
jmsConfiguration.setConcurrentConsumers(50);
jmsConfiguration.setMaxConcurrentConsumers(50);
return new JmsComponent(jmsConfiguration);
}
#Bean
public ConnectionFactory amqConnectionFactory(AMQProperties amqProperties) throws Exception {
ConnectionFactoryParser parser = new ConnectionFactoryParser();
ConnectionFactory returnValue = parser.newObject(parser.expandURI(amqProperties.getUrl()), "amqConnectionFactory");
return returnValue;
}
It is working as expected, BUT not right away from the beginning. I have the phenomenon:
I have 100 messages in the ActiveMQ queue
I start my Spring application
Camel creates only 1 thread consuming 1 message after the previous one gets response
I observe that the amount of messages in queue only decreasing slowly(99.... 98... 97... 96...)
I am filling the queue with new 100 messages
NOW the concurrent consumers are being created as I can observe that the messages decreasing rapidly.
Does someone have any idea, why the concurrentConsumers is not working right away from the beginning?

I tried the advices. Unfortunately they dont change the behaviour. I found out, that the problem is that Camel already starts consuming the messages from the queue before the Spring boot application is startet. I can observe this from the log:
2021-04-01T20:26:33,901 INFO (Camel (CamelBridgeContext) thread #592 - JmsConsumer[MyQueue]) [message]; ...
2021-04-01T20:26:33,902 INFO (Camel (CamelBridgeContext) thread #592 - JmsConsumer[MyQueue]) [message]; ...
2021-04-01T20:26:33,915 INFO (main) [AbstractConnector]; _; Started ServerConnector#5833f5cd{HTTP/1.1,[http/1.1]}{0.0.0.0:23500}
2021-04-01T20:26:33,920 INFO (main) [BridgeWsApplication]; _; Started BridgeWsApplication in 12.53 seconds (JVM running for 13.429)
In this case, only one consumer with thread #592 is consuming all the messages.
In fact, if I start my Spring application first, and then fill the queue with messages, then concurrentConsumers will be used:
2021-04-01T20:30:20,159 INFO (Camel (CamelBridgeContext) thread #594 - JmsConsumer[MyQueue])
2021-04-01T20:30:20,159 INFO (Camel (CamelBridgeContext) thread #599 - JmsConsumer[MyQueue])
2021-04-01T20:30:20,178 INFO (Camel (CamelBridgeContext) thread #593 - JmsConsumer[MyQueue])
2021-04-01T20:30:20,204 INFO (Camel (CamelBridgeContext) thread #564 - JmsConsumer[MyQueue])
In this case, messages are being consumed from concurrentConsumers parallely.
In order to solve the problem, I tried setting autoStartUp to false in my RouteBuilder component:
#Override
public void configure() {
CamelContext context = getContext();
context.setAutoStartup(false);
// My Route
}
In my naive thinking, I let Camel starting after the Spring boot is started and running:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(BridgeWsApplication.class, args);
SpringCamelContext camel = (SpringCamelContext) context.getBean("camelContext");
camel.start();
try {
camel.startAllRoutes();
} catch (Exception e) {
e.printStackTrace();
}
}
Unfortunately, this does not change the behaviour. There must be a configuration to let Camel starts after Spring is started.

Related

Write to s3 from blocking queue java using camel

I have a use case where I want to write in memory Java blocking queue contents using apache camel route to S3. Is this even possible??
The workaround I can think of is to pull records from blocking queue and flush to local file and then to S3 via file-s3 route.
Update:
Route :
fromF("seda:awsquue?concurrentConsumers=3&queue=#NonLimitQueue%s", getSourceId())
.convertBodyTo(byte[].class)
.setHeader(S3Constants.CONTENT_LENGTH, simple("${in.header.CamelFileLength}"))
.setHeader(S3Constants.KEY,simple("${in.header.CamelFileNameOnly}"))
.log(LoggingLevel.INFO, "route started")
.to("aws-s3://" +"bucket"
+ "?amazonS3Client=#s3ClientProfiler&serverSideEncryption=AES256&multiPartUpload=true");
Queue creation in different workflow :
context.registerBean("NonLimitQueue"+sourceId,ArrayBlockingQueue.class, () -> queue);
camelContext.addRoutes(new S3RouteBuilder(sourceId));
queue.add("qqqq");
When route starts it fails with exception :
java.lang.ClassCastException: class java.lang.String cannot be cast to class org.apache.camel.Exchange (java.lang.String is in module java.base of loader 'bootstrap'; org.apache.camel.Exchange is in unnamed module of loader 'app')
at org.apache.camel.component.seda.SedaConsumer.doRun(SedaConsumer.java:171)
at org.apache.camel.component.seda.SedaConsumer.run(SedaConsumer.java:125)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
which I believe is failing at queue.add("qqqq"). How to write to queue explicitly is not clear to me.
camel seda is a blocking queue for apache camel. default queue size is 1000 .you can increase or decrease this size.
from("aws-s3://helloBucket?accessKey=yourAccessKey&secretKey=yourSecretKey")
.to("seda:awsquue");
from("seda:awsquue?concurrentConsumers=3&queue=#NonLimitQueue").
// do something
// for quarkus it is
#ApplicationScoped
public class ConnectionConf {
#Named("NonLimitQueue")
#Produces
public BlockingQueue arrayDeque(){
return new ArrayBlockingQueue(30000);
}
}
What seems to be happening is Camel trying to read "qqqq" as an Exchange... Camel routes can only handle Exchange-objects and not arbitrary data-structures. What you should do is place the stream you wish to use in the body of an Exchange and send that exchange to the queue.
Something to the effect of this:
camelContext.addRoutes(new S3RouteBuilder(sourceId));
Exchange exchange = new DefaultExchange(camelContext);
exchange.getIn().setBody("qqqq");
queue.add(exchange);

Exception from recipientList EIP of camel not caught at route level

Camel Version 2.22.0
Runtime: SpringBoot : 2.0.2.RELEASE
JDK version: 1.8.0_121
EIP: recipientList.
Problem: Exception raised from parallel process of recipientList is not caught at route level onException clause.
Below is the DSL
#Override
public void configure() throws Exception {
restConfiguration().clientRequestValidation(true)
//.contextPath("/pss/v1.0/")
.port("8080").host("0.0.0.0")
.enableCORS(true)
.apiContextPath("/api-doc")
.apiProperty("api.title", "Test REST API")
.apiProperty("api.version", "v1")
.apiContextRouteId("doc-api")
.component("servlet")
.bindingMode(RestBindingMode.json);
rest("/api/").clientRequestValidation(true)
.id("api-route")
.consumes("application/json")
.get("/bean/{name}")
.bindingMode(RestBindingMode.json)
.to("direct:remoteService");
from("direct:remoteService")
.onException(Exception.class).handled(true)
.log("Exception Caught : ${exception.message}")
.end()
.recipientList(constant("direct:route1, direct:route2"), ",").parallelProcessing().aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.end()
.log("The final Exchange data : ${exception.message}");
from("direct:route1")
.setHeader( Exchange.CONTENT_ENCODING, simple("gzip"))
.setBody(simple("RESPONSE - [ { \"id\" : \"bf383eotal length is 16250]]"))
.log("${body}");
from("direct:route2")
.log("${body}")
.process(e-> {
List<String> myList = new ArrayList();
myList.add("A");
myList.add("b");
myList.add("C");
e.getIn().setBody(myList);
})
.split(body())
.parallelProcessing(true)
.aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.log("${body}")
.choice()
.when(simple("${body} == 'b'"))
.throwException(new Exception("jsdhfjkASDf"));
}
Try make onException as global like this:
onException(Exception.class).handled(true)
.log("Exception Caught : ${exception.message}")
.end();
from("direct:remoteService")
.recipientList(constant("direct:route1, direct:route2"), ",").parallelProcessing().aggregationStrategy(new GroupedBodyAggregationStrategy())
.stopOnException()
.end()
.log("The final Exchange data : ${exception.message}")
;
UPD: So you need to disable error handlers in recipient routes. Try like this (can't insert normally code sample)
That's a classical mistake: (exactly like the split EIP) each recipient will process a copy of the original Exchange. Any failure on these copies will not affect (raise an exception on) the route processing the master Exchange, as every single exchange runs in a completely separate unit of work.
If you enable the "shareUnitOfWork" option (on the recipientList), exceptions should be propagated.

Rollback the message to Dead Letter Queue - Apache Camel

I have set up the Apache camel in which i consumes the message from one queue and do some kind of operation on it and then transfers it to other queue .
Now if the exception comes then i want that it should rollback and then after 6 attempts it to send to dead letter queue , Currently rollback happens 5-6 times but my message is not transferred to dead letter queue .
Here What happens -->
Queue1-->(Consumes)-->Operation(Exception thrown)--> rollback --> again Queue1-->(Consumes) --> Operation(Exception thrown)-->rollback -->... this happens 5-6 times and then my message is lost
I dont know where my message is going and why it is getting lost , and from my Active MQ GUI i can see it is dequeued.
#Bean
public RedeliveryPolicy redeliveryPolicy() {
RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(2);
redeliveryPolicy.setMaximumRedeliveryDelay(10000);
redeliveryPolicy.setRedeliveryDelay(10000);
return redeliveryPolicy;
}
---------------------Route extends SpringRouteBuilder-------------------
onException(MyException.class)
.markRollbackOnly()
.redeliveryPolicy(redeliveryPolicy)
.useExponentialBackOff()
.handled(true)
from("jms:queue:Queue1")
.process(new Processor(){
public void process(Exchange ex){
throw new RuntimeException();
}
}).to("jms:queue:myQueue)
I assume there are multiple problems.
markRollbackOnly stops the message. After this statement no further routing is done.
That is the reason why your RedeliveryPolicy and the rest of your onException route is completely ignored. You configure 2 redelivery attempts but you write it does 5 (the default redelivery of ActiveMQ).
To fix this, move markRollbackOnly to the end of your onException route
If you consume transacted from your JMS broker the message must not get lost.
Since you lose it in case of an error, there is a problem with your transaction config. Configure the ActiveMQ component of Camel to use local JMS transactions when consuming.
#Bean(name = "activemq")
#ConditionalOnClass(ActiveMQComponent.class)
public ActiveMQComponent activeMQComponent(ConnectionFactory connectionFactory) {
ActiveMQComponent activeMQComponent = new ActiveMQComponent();
activeMQComponent.setConnectionFactory(connectionFactory);
activeMQComponent.setTransacted(true);
activeMQComponent.setLazyCreateTransactionManager(false);
return activeMQComponent;
}
When this is in place, you can in fact remove the onException route because the redelivery is done by the JMS broker, so you have to configure the redelivery settings on your JMS connection. If the configured redelivery is exhausted and the message still produces a rollback, it is moved to the DLQ.
Be aware when using an additional onException route because this is pure Camel. The Camel error handler does NOT redeliver on route level, but on processor level. So if you configure both broker and Camel redelivery, it can multiply them.

Camel message redelivery not behaving as expected

I have a route in Camel that I want to retry when an exception occurs, but I want to set a property so that the route can do something slightly differently the second time to try to stop the error happening again on the retry. Here's a route that illustrates the idea I'm trying at the moment.
from("direct:onExceptionTest")
.onException(Exception.class)
.maximumRedeliveries(1)
.log("Retrying")
.setProperty("retrying", constant(true))
.end()
.log("Start")
.choice()
.when(property("retrying").isNull())
.log("Throwing")
.throwException(new Exception("Hello world"))
.end()
.end()
.log("Done")
Obviously this isn't the real route; the whole choice body just simulates my component erroring in certain cases. I'm expecting to see the following messages logged:
Start
Throwing
Retrying
Start
Done
But what I actually see is:
Start
Throwing
Retrying
Failed delivery for (MessageId: ... on ExchangeId: ...). Exhausted after delivery attempt: 2 caught: java.lang.Exception: Hello world. Processed by failure processor: FatalFallbackErrorHandler[Pipeline[[Channel[Log(onExceptionTest)[Retrying]], Channel[setProperty(retrying, true)]]]]
I've tried adding handled(true) to the exception handler, but all this does is suppress the error message. I don't see the second Start or Done log message.
Why doesn't my route behave as I expect, and what do I need to do to get it to behave the way I want?
Update
#ProgrammerDan points out that the problem is that redelivery isn't intended for what I'm trying to achieve, which would explain why my route doesn't work! So I need to do the work in my handler, but my route calls a web service and has a few other steps and I don't want to duplicate all this in the handler. I've come up with this, which works as expected but it involves the route calling itself again from the start. Is this a bad idea? Will I get myself into knots with this approach?
from("direct:onExceptionTest")
.onException(Exception.class)
.onWhen(property("retrying").isNull()) // don't retry forever
.log("Retrying")
.setProperty("retrying", constant(true))
.handled(true)
.to("direct:onExceptionTest") // is recursion bad?
.end()
.log("Start")
.choice()
.when(property("retrying").isNull())
.log("Throwing")
.throwException(new Exception("Hello world"))
.end()
.end()
.log("Done")
Use onRedelivery with a Processor to set the property:
String KEY = "retrying";
from("direct:onExceptionTest")
.onException(RuntimeException.class)
.onRedelivery(new Processor() { // Sets a processor that should be processed before a redelivery attempt.
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("Retrying");
exchange.setProperty(KEY, true);
}
})
.maximumRedeliveries(1)
.handled(true)
.end()
.log("Start")
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("No problem");
}
})
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
if (exchange.getProperty(KEY) == null) {
LOG.info("Throwing");
throw new RuntimeException("Hello World");
}
else {
LOG.info("No throwing");
}
}
})
.log("Done");
This prints
[ main] route1 INFO Start
[ main] OnExceptionHandler INFO No problem
[ main] OnExceptionHandler INFO Throwing
[ main] OnExceptionHandler INFO Retrying
[ main] OnExceptionHandler INFO No throwing
[ main] route1 INFO Done
As #ProgrammerDan noted, only the processor that failed is re-executed but not the first processor that passed without any problems.
Edit:
If all the processing has to be re-done then you may use a sub-route with doTry and doCatch as follows:
from("direct:onExceptionTest")
.doTry()
.to("direct:subroute")
.doCatch(RuntimeException.class)
.setProperty(KEY, constant(true))
.to("direct:subroute")
.end()
.log("Done");
from("direct:subroute")
.log("Start")
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
LOG.info("No problem");
}
})
.process(new Processor() {
#Override
public void process(final Exchange exchange) throws Exception {
if (exchange.getProperty(KEY) == null) {
LOG.info("Throwing");
throw new RuntimeException("Hello World");
}
else {
LOG.info("No throwing");
}
}
});
From the Camel Docs:
When using doTry .. doCatch .. doFinally then the regular Camel Error Handler does not apply. That means any onException or the likes does not trigger. The reason is that doTry .. doCatch .. doFinally is in fact its own error handler and that it aims to mimic and work like how try/catch/finally works in Java.
Couple of points to consider about Camel's redelivery mechanism. First, check out the docs on the topic which might challenge your assumptions about how Camel handles redelivery. The point I've linked to is that Camel attempts redelivery at point of failure, it does not start over from the beginning of the route (as you appear to assume). If I'm understanding the docs correctly (I haven't tried this pattern in a while) you are basically telling it to retry throwing an exception several times, which I doubt is what you want to test.
Second, I'd recommend just doing the alternate handling directly in the onException() processor chain, as demonstrated a little further down in the same docs. Basically, you could specify how you want the message handled via a custom processor, and use both handled(true) and stop() to indicate that no further processing is necessary.
To sum it up, redelivery is generally meant to handle typical endpoint delivery failures, like intermittent connectivity drops, receiving server momentary unavailability, etc. where it makes the most sense to just "try again" and have a reasonable expectation of success. If you need more complex logic to handle retries, use a custom processor or series of processors within your onException() processor chain.

Camel Stopping With No Apparent Reason

I have a bean producer and a bean consumer, used in a single route. The producer is spawned via a thread and listens for data on an hazelcast queue (it could be anything else, even randomly generated data locally I believe).
The data are sent to a seda endpoint, to ensure concurrency.
The consumer gets data and forwards it to another hazelcast queue. But again it could be anything else.
It works well but after a while, Camel shuts down and I can't find why.
Here are some of the messages I see:
Processing a lot of data...
[ main] MainSupport INFO Apache Camel 2.10.3 stopping
[ main] DefaultCamelContext INFO Apache Camel 2.10.3 (CamelContext: camel-1) is shutting down
[ main] DefaultShutdownStrategy INFO Starting to graceful shutdown 1 routes (timeout 300 seconds)
[el-1) thread #2 - ShutdownTask] DefaultShutdownStrategy INFO Waiting as there are still 1 inflight and pending exchanges to complete, timeout in 300 seconds.
then processing still during 300 seconds and stopping.
Here some of the code:
Producer:
public void run()
{
try
{
IRequest service = ProxyHelper.createProxy(context.getEndpoint("seda:echo"), IRequest.class);
BlockingQueue<Request> q = client.getQueue(MainApp.sQueueReceive);
while(true)
{
Request request;
request = q.take();
// no response awaited
service.request(request);
}
}
Consumer:
public void onMessage(Request request)
{
nb_forwarded++;
BlockingQueue<Request> q = MainApp.client.getQueue(MainApp.sQueueForward);
try
{
q.put(request);
}
catch (InterruptedException e)
{
exit(2); --> it does not happen
}
And finally, the route:
from("seda:echo")
.setExchangePattern(ExchangePattern.InOnly)
.bean(new HazelcastForwarder(), "onMessage");
It's in InOnly as no response is awaited from the producer, it is just a forward.
So why Camel is stopping. There is no message appart from those saying that it is stopping. Is there such a default behaviour in Camel. In which cases?
Thanks!
Enable DEBUG or Trace logging to reveil the true reason why camel is stopping. It can be that the enclosing container is stopping (if you are running camel inside something) or similar.
I was facing the similar issue, where Camel Context is closing immediately after starting the process. I am posting here so that it would also help others with similar issue.
In my case, I am using Spring for loading the Camel context using 'FileSystemXmlApplicationContext' and instantiating it with in try block,
try(AbstractXmlApplicationContext appContext = new FileSystemXmlApplicationContext(camelContextPath)) {
}
as my Eclipse was complaining about Resource leak. So, as soon as the call was coming out of the try/catch it was closing the Spring context, which again closing the Camel Context.
To fix the issue need to initialize Spring context out side the try block.
AbstractXmlApplicationContext appContext = null;
try {
appContext = new FileSystemXmlApplicationContext(camelContextPath);
}

Resources