基于Apache Camel配置异步Web服务:仅当System B可用时触发路由
Nice work setting up the async decoupling with ActiveMQ so far! The problem you're facing—where the integrationToB route still tries to call System B even when it's offline—is a common one, and there are a few solid ways to fix this with Apache Camel. Let's break down the best approaches:
1. Implement a Circuit Breaker Pattern (Resilience4j Recommended)
Circuit breakers monitor the success/failure rate of calls to System B, and "trip" (stop making calls) when B is unavailable. Once B recovers, the circuit automatically closes to resume processing.
First, add the Resilience4j Camel component dependency to your project. Then modify your integrationToB route to wrap the HTTP call with circuit breaker logic:
<camel:route id="integrationToB"> <camel:from uri="activemq:requestqueue" /> <camel:transacted /> <camel:bean ref="afterQueueProcessor" /> <!-- Configure circuit breaker to handle System B unavailability --> <camel:resilience4jCircuitBreaker id="systemBCircuitBreaker" failureRateThreshold="50" waitDurationInOpenState="10000" permittedNumberOfCallsInHalfOpenState="3" slidingWindowSize="10"> <camel:setHeader headerName="CamelHttpMethod"> <camel:constant>POST</camel:constant> </camel:setHeader> <camel:setHeader headerName="Content-Type"> <camel:constant>application/x-www-form-urlencoded</camel:constant> </camel:setHeader> <camel:to uri="http://localhost:8080/systemB/newOrder?test=testData" /> </camel:resilience4jCircuitBreaker> <!-- Requeue messages when circuit is open instead of sending to DLQ --> <camel:onException> <camel:exception>io.github.resilience4j.circuitbreaker.CallNotPermittedException</camel:exception> <camel:delay> <camel:simple>15000</camel:simple> <!-- 15-second delay before retry --> </camel:delay> <camel:inOnly uri="activemq:requestqueue" /> <camel:handled> <camel:constant>true</camel:constant> </camel:handled> </camel:onException> </camel:route>
When the circuit opens (System B is down), messages are requeued with a delay instead of being sent to the dead letter queue. Once B recovers, the circuit closes and processing resumes.
2. Add a Pre-Call Health Check
Add a lightweight check to verify System B's availability before making the actual POST request. Use a health endpoint (like /health) on System B for this:
<camel:route id="integrationToB"> <camel:from uri="activemq:requestqueue" /> <camel:transacted /> <camel:bean ref="afterQueueProcessor" /> <!-- Check if System B is reachable first --> <camel:choice> <camel:when> <camel:simple>${header.CamelHttpResponseCode} == 200</camel:simple> <camel:to uri="http://localhost:8080/systemB/health?httpMethod=HEAD" /> <!-- Proceed with order request if B is healthy --> <camel:setHeader headerName="CamelHttpMethod"> <camel:constant>POST</camel:constant> </camel:setHeader> <camel:setHeader headerName="Content-Type"> <camel:constant>application/x-www-form-urlencoded</camel:constant> </camel:setHeader> <camel:to uri="http://localhost:8080/systemB/newOrder?test=testData" /> </camel:when> <camel:otherwise> <!-- Requeue message with delay if B is unavailable --> <camel:delay> <camel:simple>20000</camel:simple> <!-- 20-second delay --> </camel:delay> <camel:inOnly uri="activemq:requestqueue" /> </camel:otherwise> </camel:choice> </camel:route>
Ensure System B has a simple /health endpoint that returns a 200 OK status when it's ready to handle requests.
3. Dynamic Route Control with Camel Health Checks
Use Camel's camel-health component to monitor System B's status and dynamically start/stop the integrationToB route.
First, create a custom health check bean:
@Component public class SystemBHealthCheck extends AbstractHealthCheck { @Override protected void doCall(HealthCheckResultBuilder builder, Map<String, Object> options) { try (CloseableHttpClient client = HttpClients.createDefault()) { HttpHead request = new HttpHead("http://localhost:8080/systemB/health"); HttpResponse response = client.execute(request); if (response.getStatusLine().getStatusCode() == 200) { builder.up(); } else { builder.down().detail("statusCode", response.getStatusLine().getStatusCode()); } } catch (IOException e) { builder.down().detail("error", e.getMessage()); } } }
Then add a monitoring route to control the integrationToB route:
<camel:route id="healthMonitorRoute"> <camel:from uri="health-check:systemBHealthCheck?frequency=30000" /> <!-- Check every 30 seconds --> <camel:choice> <camel:when> <camel:simple>${body.state} == 'UP'</camel:simple> <camel:to uri="controlbus:route?routeId=integrationToB&action=start" /> </camel:when> <camel:otherwise> <camel:to uri="controlbus:route?routeId=integrationToB&action=stop" /> </camel:otherwise> </camel:choice> </camel:route>
This stops the integrationToB route when System B is down (so it won't consume queue messages) and restarts it once B is back online.
Bonus: Tune ActiveMQ Redelivery Policies
Complement the above solutions by configuring ActiveMQ to avoid fast DLQ routing:
<bean id="activemq" class="org.apache.camel.component.activemq.ActiveMQComponent"> <property name="connectionFactory"> <bean class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://localhost:61616" /> <property name="redeliveryPolicy"> <bean class="org.apache.activemq.RedeliveryPolicy"> <property name="maximumRedeliveries" value="5" /> <property name="initialRedeliveryDelay" value="5000" /> <property name="redeliveryDelay" value="10000" /> <property name="useExponentialBackOff" value="true" /> </bean> </property> </bean> </property> </bean>
This adds increasing delays between retries before messages reach the DLQ.
内容的提问来源于stack exchange,提问作者Gajendra Kumar

