如何在Apache Camel中重新使用重新启动后调用Web服务?

时间:2016-08-31 20:40:49

标签: error-handling apache-camel fuseesb dead-letter apache-servicemix

我未能找到促进此问题解决方案的企业集成模式或配方:

重新递送尝试用完后,我需要将一个Web服务请求发送回原始来源,以通知发送方发送失败。

在完成所有重新传递尝试后,我应该将邮件移至死信队列吗?然后创建一个新的消费者监听该DL队列?我的每个源消息队列都需要一个唯一的死信队列吗?在将其移动到死信队列之前,是否应该添加消息头,注意源队列?如果所有消息都转到一个死信队列,我的消费者应该如何知道发送Web服务请求的位置?

你能指点我一本书,博客文章或文章吗?规定的方法是什么?

我正在使用Fuse ESB的旧版本,但我希望ServiceMix中的解决方案同样适用。

或许,我要求的是反模式或代码嗅觉。请指教。

1 个答案:

答案 0 :(得分:1)

如果您是Camel的新手并且真的希望深入了解它,我建议Camel in Action, a book by Claus Ibsen。有a second edition in the works,已经完成了19章中的14章,所以你也可以试一试。

如果这有点太多,在线文档是相当不错的,你可以从中找到基本信息。对于错误处理,我建议从general error handling page开始,然后转到error handler docsexception policy documentation

通常情况下,dead letter channel是可行的方法 - 在重试耗尽后,Camel会自动发送到DLC,您只需自己定义DLC即可。它的名字暗示,它是一个通道,并不需要成为一个队列 - 您可以写入文件,调用Web服务,向消息队列提交消息或只写入日志,这完全取决于您。

// error-handler DLC, will send to HTTP endpoint when retries are exhausted
errorHandler(deadLetterChannel("http4://my.webservice.hos/path")
    .useOriginalMessage()
    .maximumRedeliveries(3)
    .redeliveryDelay(5000))

// exception-clause DLC, will send to HTTP endpoint when retries are exhausted
onException(NetworkException.class)
    .handled(true)
    .maximumRedeliveries(5)
    .backOffMultiplier(3)
    .redeliveryDelay(15000)
    .to("http4://my.webservice.hos/otherpath");

我自己总是喜欢使用消息队列,然后从那里进行消费以进行任何其他恢复或报告。我通常包括失败详细信息,如交换ID和路由ID,消息标题,错误消息,有时甚至堆栈跟踪。您可以想象,生成的消息会增长很多,但它极大地简化了故障排除和调试,尤其是在您拥有大量组件和服务的环境中。这是我的一个项目的示例DLC消息:

public class DeadLetterChannelMessage {
  private String timestamp = Times.nowInUtc().toString();
  private String exchangeId;
  private String originalMessageBody;
  private Map<String, Object> headers;
  private String fromRouteId;
  private String errorMessage;
  private String stackTrace;

  @RequiredByThirdPartyFramework("jackson")
  private DeadLetterChannelMessage() {
  }

  @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
  public DeadLetterChannelMessage(Exchange e) {
    exchangeId = e.getExchangeId();
    originalMessageBody = e.getIn().getBody(String.class);
    headers = Collections.unmodifiableMap(e.getIn().getHeaders());
    fromRouteId = e.getFromRouteId();

    Optional.ofNullable(e.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class))
      .ifPresent(throwable -> {
        errorMessage = throwable.getMessage();
        stackTrace = ExceptionUtils.getStackTrace(throwable);
      });
  }

  // getters
}

从死信队列中消费时,路由ID可以告诉您故障源自哪里,这样您就可以实现特定于处理来自那里的错误的路由:

// general DLC handling route
from("{{your.dlc.uri}}")
    .routeId(ID_REPROCESSABLE_DLC_ROUTE)
    .removeHeaders(Headers.ALL)
    .unmarshal().json(JsonLibrary.Jackson, DeadLetterChannelMessage.class)
    .toD("direct:reprocess_${body.fromRouteId}"); // error handling route

// handle errors from `myRouteId`
from("direct:reprocess_myRouteId")  
    .log("Error: ${body.errorMessage} for ${body.originalMessageBody}"); 
    // you'll probably do something better here, e.g.
    // .convertBodyTo(WebServiceErrorReport.class) // requires a converter
    // .process(e -> { //do some pre-processing, like setting headers/properties })
    // .toD("http4://web-service-uri/path"); // send to web-service


// for routes that have no DLC handling supplied
onException(DirectConsumerNotAvailableException.class)
     .handled(true)
     .useOriginalMessage()
     .removeHeaders(Headers.ALL)
     .to({{my.unreprocessable.dlc}}); // errors that cannot be recovered from