使用基于时序的PollingConsumer到直接端点

时间:2016-01-11 14:57:58

标签: java apache-camel

从功能上讲,我希望在从JMS(WMQ)端点使用之前检查URL是否处于活动状态 如果无法访问URL或服务器错误,那么我不想从队列中获取。所以我想通过轮询消费者继续尝试(无限次重试)URL。所以只要它可用,我就可以从JMS中获取。

我有一个设置了直接端点的RouteBuilder,它被配置为运行将对服务执行ping操作的处理器。

所以:

public class PingRoute extends RouteBuilder {
        @Override
        public void configureCamel() {
            from("direct:pingRoute").routeId(PingRoute.class.getSimpleName())
                .process(new PingProcessor(url))
                .to("log://PingRoute?showAll=true");
        }
    }

在另一条路线中,我正在设置我的计时器:

    @Override
public void configureCamel() {
       from(timerEndpoint).beanRef(PollingConsumerBean.class.getSimpleName(), "checkPingRoute");
   ...
    }

使用PollingConsumerBean我试图通过消费者接收身体:

public void checkPingRoute(){
    // loop to check the consumer.  Check we can carry on with the pick up from the JMS queue.
    while(true){
        Boolean pingAvailable = consumer.receiveBody("direct:pingRoute", Boolean.class);
   ...
    }

我将路线添加到上下文并使用生产者发送:

context.addRoutes(new PingRoute());
context.start();
producer.sendBody(TimerPollingRoute.TIMER_POLLING_ROUTE_ENDPOINT, "a body");

我得到以下IllegalArgumentException

Cannot add a 2nd consumer to the same endpoint. Endpoint Endpoint[direct://pingRoute] only allows one consumer.

有没有办法将直接路由设置为轮询消费者?

4 个答案:

答案 0 :(得分:1)

遗憾的是,业务逻辑并不十分清楚。据我了解 - 您需要等待服务的响应。恕我直言,你必须使用 Content Enricher EIP http://camel.apache.org/content-enricher.htmlpollEnrich是您在计时器路线上所需要的。

.pollEnrich("direct:waitForResponce", -1).pollEnrich("seda:waitForResponce", -1)

public class PingRoute extends RouteBuilder {
        @Override
        public void configureCamel() {
             from("direct:pingRoute").routeId(PingRoute.class.getSimpleName())
                .process(new PingProcessor(url))
             .choice().when(body())                       
                  .to("log://PingRoute?showAll=true")
                  .to("direct:waitForResponce") 
                .otherwise()
                  .to("direct:pingRoute")
                .end(); 
        }
};

定时器:

    @Override
    public void configureCamel() {                           
      from(timerEndpoint)
      .inOnly("direct:pingRoute")
      .pollEnrich("direct:waitForResponce", -1)
       ...
    }

答案 1 :(得分:1)

基于其用例的OP's clarification,他们有几个问题需要解决:

  • 当且仅当对网址的ping 为正时,才从JMS队列中获取消息。
  • 如果URL没有响应,则JMS消息不应从队列中消失,并且必须发生重试,直到URL再次响应为止,在这种情况下,消息将最终消耗。
  • OP未指定重试次数是限制还是无限

根据此问题情况,我建议重新设计他们的解决方案,利用ActiveMQ retriesbroker-side redeliveryJMS transactions in Camel来:

  1. 如果URL ping失败(通过事务回滚),则将消息返回队列。
  2. 确保消息不会丢失(通过使用JMS持久性和代理端重新传输,AMQ将持续安排重试周期)。
  3. 能够为每条消息指定复杂的重试周期,例如具有指数退避,最大重试等等。
  4. 如果重试周期耗尽而没有正面结果,则可选择将消息发送到Dead Letter Queue,以便可以计划其他一些(可能是手动的)操作。
  5. 现在,实施方式

    from("activemq:queue:abc?transacted=true")          // (1)
        .to("http4://host.endpoint.com/foo?method=GET") // (2) (3)
        .process(new HandleSuccess());                  // (4)
    

    评论:

    1. 请注意transacted标志。
    2. 如果HTTP调用失败,HTTP4端点将引发异常。
    3. 由于没有配置的异常处理程序,Camel会将异常传播到消费者端点(activemq),这将回滚该事务。
    4. 如果调用成功,流程将继续,交换体现在将包含HTTP服务器返回的有效负载,您可以按照您希望的方式处理它。我在这里使用处理器。
    5. 接下来,重要的是您在ActiveMQ中配置重新传递策略,以及启用代理端重新传递。您可以在activemq.xml配置文件中执行此操作:

      <plugins>
        <redeliveryPlugin fallbackToDeadLetter="true" sendToDlqIfMaxRetriesExceeded="true">
          <redeliveryPolicyMap>
            <redeliveryPolicyMap>
              <redeliveryPolicyEntries>
                <redeliveryPolicy queue="my.queue" 
                                  initialRedeliveryDelay="30000" 
                                  maximumRedeliveries="17" 
                                  maximumRedeliveryDelay="259200000" 
                                  redeliveryDelay="30000" 
                                  useExponentialBackOff="true"
                                  backOffMultiplier="2" />
              </redeliveryPolicyEntries>
            </redeliveryPolicyMap>
          </redeliveryPolicyMap>
        </redeliveryPlugin>
      </plugins>
      

      并确保在顶级<broker />元素中启用了调度程序支持:

      <broker xmlns="http://activemq.apache.org/schema/core" 
              brokerName="mybroker" 
              schedulerSupport="true">
          ...
      </broker>
      

      我希望有所帮助。

      编辑1: OP正在使用IBM WebSphere MQ作为经纪人,我错过了。您可以使用JMS QueueBrowser来查看消息并在实际使用消息之前尝试相应的URL,但是不可能有选择地使用单个消息 - 这不是MOM(面向消息传递的中间件)所针对的。

      所以我坚持你应该探索JMS事务,而不是让它留给代理重新传递消息,你可以开始ping事件到TX体内的URL。关于Camel,您可以按如下方式实现它:

      from("jms:queue:myqueue?transacted=true")
          .bean(new UrlPinger());
      

      UrlPinger.java:

      public class UrlPinger {
      
          @EndpointInject
          private ProducerTemplate template;
      
          private Pattern pattern = Pattern.compile("^(http(?:s)?)\\:");
      
          @Handler
          public void pingUrl(@Body String url, CamelContext context) throws InterruptedException {
              // Replace http(s): with http(s)4: to use the Camel HTTP4 endpoint.
              Matcher m = pattern.matcher(url);
              if (m.matches()) {
                  url = m.replaceFirst(m.group(1) + "4:");
              }
      
              // Try forever until the status code is 200.
              while (getStatusCode(url, context) != 200) {
                  Thread.sleep(5000);
              }
          }
      
          private int getStatusCode(String url, CamelContext context) {
              Exchange response = template.request(url + "?method=GET&throwExceptionOnFailure=false", new Processor() {
                  @Override public void process(Exchange exchange) throws Exception {
                      // No body since this is a GET request.
                      exchange.getIn().getBody(null);
                  }
              });
      
              return response.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE, Integer.class);
          }
      
      }
      

      注意:

      1. 请注意throwExceptionOnFailure=false选项。不会引发异常,因此循环将执行,直到条件为真。
      2. 在bean内部,我将永远循环,直到HTTP状态为200.当然,你的逻辑会有所不同。
      3. 在尝试和尝试之间,我正在睡觉5000毫秒。
      4. 我假设ping的URL位于传入JMS消息的正文中。我正在使用http(s):替换前导http(s)4:以使用Camel HTTP4 endpoint
      5. 在TX内部执行ping操作可确保只有在ping条件为真时才会使用该消息(在这种情况下,HTTP状态== 200)。
      6. 你可能想引入一个desist条件(你不想永远尝试)。也许会引入一些补偿而不是压倒另一方。
      7. 如果Camel或代理在重试周期内发生故障,该消息将自动回滚。
      8. 考虑到JMS事务是Session - 绑定的,因此如果要启动许多并发使用者(concurrentConsumers JMS端点选项),则需要设置cacheLevelName=CACHE_NONE每个线程使用不同的JMS Session

答案 2 :(得分:0)

我确实想知道你想要做什么有点困难,但在我看来你想要在一个时间间隔内消耗来自端点的数据。为此,最佳模式是投票消费者:http://camel.apache.org/polling-consumer.html

您当前收到的错误是因为您有两个消费者都试图读取“direct:// pingRoute”如果这是打算您可以将直接更改为seda:// pingRoute所以它在内存队列中你的数据将在。

答案 3 :(得分:0)

这里的所有答案都指向了正确的方向,但我终于想出了一个能够适应我们的代码库和框架的解决方案。

首先,我发现不需要让bean充当投票消费者,但可以使用处理器。

@Override
public void configureCamel() {
    from("timer://fnzPoller?period=2000&delay=2000").processRef(UrlPingProcessor.class.getSimpleName())
           .processRef(StopStartProcessor.class.getSimpleName()).to("log://TimerPollingRoute?showAll=true");

}

然后在UrlPingProcessor中有CXF服务来ping网址并可以检查响应:

@Override
public void process(Exchange exchange) {
    try {
        // CXF service
        FnzPingServiceImpl fnzPingService = new FnzPingServiceImpl(url);
        fnzPingService.getPing();
    } catch (WebApplicationException e) {
        int responseCode = e.getResponse().getStatus();
        boolean isValidResponseCode = ResponseCodeUtil.isResponseCodeValid(responseCode);
        if (!isValidResponseCode) {
            // Sets a flag to stop for the StopStartProcessor
            stopRoute(exchange);
        }
    } 
}

然后在StopStartProcessor中,它使用ExecutorService来停止或通过新线程启动路线。:

    @Override
public void process(final Exchange exchange) {
    // routeBuilder is set on the constructor.
    final String routeId = routeBuilder.getClass().getSimpleName();
    Boolean stopRoute = ExchangeHeaderUtil.getHeader(exchange, Exchange.ROUTE_STOP, Boolean.class);
    boolean stopRoutePrim = BooleanUtils.isTrue(stopRoute);
    if (stopRoutePrim) {
        StopRouteThread stopRouteThread = new StopRouteThread(exchange, routeId);
        executorService.execute(stopRouteThread);
    } else {
        CamelContext context = exchange.getContext();
        Route route = context.getRoute(routeId);
        if (route == null) {
            try {
                context.addRoutes(routeBuilder);
            } catch (Exception e) {
                String msg = "Unable to add a route: " + routeBuilder;
                LOGGER.warn(msg, e);
            }
        }
    }
}