一个Camel Multicast组件聚合策略问题的解决过程
摘要:本文通过案例,发现了一个Camel Multicast组件聚合策略相关的问题。通过查看Camel源代码,找到了问题原因并给出了解决方案。希望本文可以帮助到遇到同样问题的Camel用户。
本文分享自华为云社区《使用Apache Camel Multicast组件遇到的一个问题》,作者:中间件小哥。
1 前言
本文翻译自华为加拿大研究所的Reji Mathews发表于Apache Camel社区的《ROUTING MULTICAST OUTPUT AFTER ENCOUNTERING PARTIAL FAILURES》一文。在征得原作者同意后,本文对原文的部分内容作了少许修改。
2 Multicast组件简介
Multicast是Apache Camel(以下简称“Camel”)中一个功能强大的EIP组件,可以将消息发送至多条子路径,然后并行地执行它们。
参考官网文档,我们可以使用两种方式配置Multicast组件:
- 独立执行所有子路径,并将最后响应的子路径的结果作为最终输出。这也是Multicast组件的默认配置。
- 通过实现Camel的聚合策略(Aggregation Strategy),使用自定义的聚合器来处理所有子路径的输出。
3 问题描述
本文使用案例如下:使用Jetty组件发布一个API,调用该API后,消息会分别发送至"direct:A"和"direct:B"两条子路径。在使用自定义的聚合策略处理后,继续执行后续步骤。其中在"direct:A"中抛出一个异常,来模拟运行失败;"direct:B"正常运行。同时在onException中定义了异常处理策略。
本文使用的Camel版本为3.8.0
@Override
public void configure() throws Exception {
onException(Exception.class)
.useOriginalMessage()
.handled(true)
.log("Exception handler invoked")
.transform().constant("{\"data\" : \"err\"}")
.end(); from("jetty:http://localhost:8081/myapi?httpMethodRestrict=GET")
.log("received request")
.log("Entering multicast")
.multicast(new SimpleFlowMergeAggregator())
.parallelProcessing().to("direct:A", "direct:B")
.end()
.log("Aggregated results ${body}")
.log("Another log")
.transform(simple("{\"result\" : \"success\"}"))
.end(); from("direct:A")
.log("Executing PATH_1 - exception path")
.transform(constant("DATA_FROM_PATH_1"))
.log("Starting exception throw")
.throwException(new Exception("USER INITIATED EXCEPTION"))
.log("PATH_1")
.end(); from("direct:B")
.log("Executing PATH_2 - success path")
.delayer(1000)
.transform(constant("DATA_FROM_PATH_2"))
.log("PATH_2")
.end();
}
自定义聚合器SimpleFlowMergeAggregator定义如下,其中我们将所有子路径的结果放入一个list对象。
public class SimpleFlowMergeAggregator implements AggregationStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleFlowMergeAggregator.class.getName());
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
LOGGER.info("Inside aggregator " + newExchange.getIn().getBody());
if(oldExchange == null) {
String data = newExchange.getIn().getBody(String.class);
List<String> aggregatedDataList = new ArrayList<>();
aggregatedDataList.add(data);
newExchange.getIn().setBody(aggregatedDataList);
return newExchange;
}
List<String> oldData = oldExchange.getIn().getBody(List.class);
oldData.add(newExchange.getIn().getBody(String.class));
oldExchange.getIn().setBody(oldData);
return oldExchange;
}
}
基于对Multicast组件执行逻辑的理解,我们认为存在多个子路径时,其运行结果应该为:如果其中有一条子路径能运行成功,则使用聚合的结果继续执行后续步骤;如果所有子路径都运行失败,则停止整个路由(route)。本案例中,由于子路径"direct:A"运行异常,子路径"direct:B"运行正常,则应该正常执行后续两个步骤日志(log)和转换(transform)。
运行上述案例,日志信息如下:
2021-05-06 12:43:18.565 INFO 13956 --- [qtp916897446-42] route1 : received request
2021-05-06 12:43:18.566 INFO 13956 --- [qtp916897446-42] route1 : Entering multicast
2021-05-06 12:43:18.575 INFO 13956 --- [ #4 - Multicast] route2 : Executing PATH_1 - exception path
2021-05-06 12:43:18.575 INFO 13956 --- [ #4 - Multicast] route2 : Starting exception throw
2021-05-06 12:43:18.578 INFO 13956 --- [ #4 - Multicast] route2 : Exception handler invoked
2021-05-06 12:43:18.579 INFO 13956 --- [ #4 - Multicast] c.e.d.m.SimpleFlowMergeAggregator : Inside aggregator {"data" : "err"}
2021-05-06 12:43:19.575 INFO 13956 --- [ #3 - Multicast] route3 : Executing PATH_2 - success path
2021-05-06 12:43:21.576 INFO 13956 --- [ #3 - Multicast] route3 : PATH_2
2021-05-06 12:43:21.576 INFO 13956 --- [ #3 - Multicast] c.e.d.m.SimpleFlowMergeAggregator : Inside aggregator DATA_FROM_PATH_2
观察上述日志,我们发现完成两条子路径结果的聚合后,后续的两个步骤日志(log)和转换(transform)并未执行。这并不符合我们期望的结果。
经过多次测试,我们还发现,只有当到达聚合器SimpleFlowMergeAggregator的第一个子路径("direct:A")执行异常时,便会发生这种后续步骤未执行的情况;而如果第一个子路径("direct:A")执行成功,即使另一个子路径("direct:B")执行失败,也会继续执行后续的步骤。
4 问题分析
接下来,我们通过查看Camel源代码,来找出上述现象的原因。
在camel-core-processors模块的Pipeline.java 中,其run()方法中有这样一段代码:
@Override
public void run() {
boolean stop = exchange.isRouteStop();
int num = index;
boolean more = num < size;
boolean first = num == 0; if (!stop && more && (first || continueProcessing(exchange, "so breaking out of pipeline", LOG))) { // prepare for next run
if (exchange.hasOut()) {
exchange.setIn(exchange.getOut());
exchange.setOut(null);
} // get the next processor
AsyncProcessor processor = processors.get(index++); processor.process(exchange, this);
} else {
// copyResults is needed in case MEP is OUT and the message is not an OUT message
ExchangeHelper.copyResults(exchange, exchange); // logging nextExchange as it contains the exchange that might have altered the payload and since
// we are logging the completion if will be confusing if we log the original instead
// we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
if (LOG.isTraceEnabled()) {
LOG.trace("Processing complete for exchangeId: {} >>> {}", exchange.getExchangeId(), exchange);
} AsyncCallback cb = callback;
taskFactory.release(this);
reactiveExecutor.schedule(cb);
}
}
其中,这个if判断决定了是否继续执行后续步骤:
if (!stop && more && (first || continueProcessing(exchange, "so breaking out of pipeline", LOG)))
可以看出,在如下三种情况下,后续步骤将不会被执行:
1. 之前的步骤已经将exchange 对象标记为停止状态。
boolean stop = exchange.isRouteStop();
2. 后续没有步骤可执行。
boolean more = num < size;
3. continueProcessing()方法返回false。
我们来看看continueProcessing()方法的代码。
public final class PipelineHelper {
public static boolean continueProcessing(Exchange exchange, String message, Logger log) {
ExtendedExchange ee = (ExtendedExchange) exchange;
boolean stop = ee.isFailed() || ee.isRollbackOnly() || ee.isRollbackOnlyLast()
|| (ee.isErrorHandlerHandledSet() && ee.isErrorHandlerHandled());
if (stop) {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Message exchange has failed: ").append(message).append(" for exchange: ").append(exchange);
if (exchange.isRollbackOnly() || exchange.isRollbackOnlyLast()) {
sb.append(" Marked as rollback only.");
}
if (exchange.getException() != null) {
sb.append(" Exception: ").append(exchange.getException());
}
if (ee.isErrorHandlerHandledSet() && ee.isErrorHandlerHandled()) {
sb.append(" Handled by the error handler.");
}
log.debug(sb.toString());
}
return false;
}
if (ee.isRouteStop()) {
if (log.isDebugEnabled()) {
log.debug("ExchangeId: {} is marked to stop routing: {}", exchange.getExchangeId(), exchange);
}
return false;
}
return true;
}
}
可以看出,当执行过程发生异常并且被异常处理器捕获时,continueProcessing()方法将返回false。
再回到我们的案例,第一个到达聚合器SimpleFlowMergeAggregator的子路径("direct:A"),会作为后续聚合的基础,其它子路径("direct:B")会在此基础上追加各自的body数据。实际上,很多Camel用户都会采用这种方式来实现自定义聚合策略。但这样做存在一个问题:在异常处理时,子路径"direct:A"的exchange对象会被设置一个状态标识,而此状态标识会被传递到下游,用于判断是否继续执行后续步骤。由于作为聚合基础的"direct:A"子路径的exchange对象状态为“异常”,最终continueProcessing()方法将返回false,后续的步骤也就不会再执行。
5 解决方案
对于上述问题,用户可以使用多种方式来设置异常处理时exchange对象的状态。本文采用如下解决方案:如果第一个子路径执行正常,则继续执行后续步骤;如果第一个子路径执行异常,则将其与其它执行成功的子路径交换,然后继续执行后续步骤。
更新后的自定义聚合器SimpleFlowMergeAggregator如下:
public class SimpleFlowMergeAggregator implements AggregationStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleFlowMergeAggregator.class.getName());
@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
LOGGER.info("Inside aggregator " + newExchange.getIn().getBody());
if(oldExchange == null) {
String data = newExchange.getIn().getBody(String.class);
List<String> aggregatedDataList = new ArrayList<>();
aggregatedDataList.add(data);
newExchange.getIn().setBody(aggregatedDataList);
return newExchange;
}
if(hadException(oldExchange)) {
if(!hadException(newExchange)) {
// aggregate and swap the base
LOGGER.info("Found new exchange with success. swapping the base exchange");
List<String> oldData = oldExchange.getIn().getBody(List.class);
oldData.add(newExchange.getIn().getBody(String.class));
// swapped the base here
newExchange.getIn().setBody(oldData);
return newExchange;
}
}
List<String> oldData = oldExchange.getIn().getBody(List.class);
oldData.add(newExchange.getIn().getBody(String.class));
oldExchange.getIn().setBody(oldData);
return oldExchange;
}
private boolean hadException(Exchange exchange) {
if(exchange.isFailed()) {
return true;
}
if(exchange.isRollbackOnly()) {
return true;
}
if(exchange.isRollbackOnlyLast()) {
return true;
}
if(((ExtendedExchange)exchange).isErrorHandlerHandledSet()
&& ((ExtendedExchange)exchange).isErrorHandlerHandled()) {
return true;
}
return false;
}
}
再次运行上述案例,日志信息如下:
2021-05-06 12:46:19.122 INFO 2576 --- [qtp174245837-45] route1 : received request
2021-05-06 12:46:19.123 INFO 2576 --- [qtp174245837-45] route1 : Entering multicast
2021-05-06 12:46:19.130 INFO 2576 --- [ #3 - Multicast] route2 : Executing PATH_1 - exception path
2021-05-06 12:46:19.130 INFO 2576 --- [ #3 - Multicast] route2 : Starting exception throw
2021-05-06 12:46:19.134 INFO 2576 --- [ #3 - Multicast] route2 : Exception handler invoked
2021-05-06 12:46:19.135 INFO 2576 --- [ #3 - Multicast] c.e.d.m.SimpleFlowMergeAggregator : Inside aggregator {"data" : "err"}
2021-05-06 12:46:20.130 INFO 2576 --- [ #4 - Multicast] route3 : Executing PATH_2 - success path
2021-05-06 12:46:22.132 INFO 2576 --- [ #4 - Multicast] route3 : PATH_2
2021-05-06 12:46:22.132 INFO 2576 --- [ #4 - Multicast] c.e.d.m.SimpleFlowMergeAggregator : Inside aggregator DATA_FROM_PATH_2
2021-05-06 12:46:22.132 INFO 2576 --- [ #4 - Multicast] c.e.d.m.SimpleFlowMergeAggregator : Found new exchange with success. swapping the base exchange
2021-05-06 12:46:22.133 INFO 2576 --- [ #4 - Multicast] route1 : Aggregated results {"data" : "err"},DATA_FROM_PATH_2
2021-05-06 12:46:22.133 INFO 2576 --- [ #4 - Multicast] route1 : Another log
可以看出,使用新的自定义聚合策略后,后续的日志(log)和转换(transform)步骤都成功执行。
6 结语
本文通过案例,发现了一个Camel Multicast组件聚合策略相关的问题。通过查看Camel源代码,找到了问题原因并给出了解决方案。
希望本文可以帮助到遇到同样问题的Camel用户。
一个Camel Multicast组件聚合策略问题的解决过程的更多相关文章
- 一个有意思的Ruby Webdriver超时问题的解决过程
rescue in receive 由于写ruby的时候感觉混身上下都拽起来了,所以比較喜欢用ruby写代码. 今天遇到了一个webdriver timeout的问题,问题本身还是由于我对webdri ...
- Griddle, griddle-react 一个REACT 表格组件
Griddle, griddle-react 一个REACT 表格组件: http://griddlegriddle.github.io/Griddle/index.html
- vue实现一个简易Popover组件
概述 之前写vue的时候,对于下拉框,我是通过在组件内设置标记来控制是否弹出的,但是这样有一个问题,就是点击组件外部的时候,怎么也控制不了下拉框的关闭,用户体验非常差. 当时想到的解决方法是:给根实例 ...
- React自己写的一个地图小组件
由于今天比较闲,就玩了玩react,然后就封装了一个地图的组件,当然功能比较简单,因为就是随手写的小东西,但是由于引用了百度API和bee-mobile,所以用起来可能要薛微麻烦一点点,但是我保证,只 ...
- Ionic 2 中的创建一个闪视卡片组件
闪视卡片是记忆信息的重要工具,它的使用可以追溯到19世纪.我们将要创建一个很酷的短暂动画来实现它.看起来像是这个样子的: 闪视卡片示例 Ionic 2 实例开发 新增章节将为你介绍如何在Ionic 2 ...
- 基于element-ui封装一个Table模板组件
大家在做后台管理系统的时候,写的最多的可能就是表格页面了,一般分三部分:搜索功能区.表格内容区和分页器区.一般这些功能都是使用第三方组件库实现,比如说element-ui,或者vuetify.这两个组 ...
- SpringCloud升级之路2020.0.x版-9.如何理解并定制一个Spring Cloud组件
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 我们实现的 S ...
- 过年了,基于Vue做一个消息通知组件
前言 今天除夕,在这里祝大家新年快乐!!!今天在这个特别的日子里我们做一个消息通知组件,好,我们开始行动起来吧!!!项目一览 效果很简单,就是这种的小卡片似的效果. 我们先开始写UI页面,可自定义消息 ...
- Ajax跨域、Json跨域、Socket跨域和Canvas跨域等同源策略限制的解决方法
同源是指同样的协议.域名.port,三者都同样才属于同域.不符合上述定义的请求,则称为跨域. 相信每一个开发者都曾遇到过跨域请求的情况,尽管情况不一样,但问题的本质都能够归为浏览器出于安全考虑下的同源 ...
- Win10提示“无法打开此计算机上的组策略对象”如何解决
为了更好地管理电脑,很多朋友都会去编辑Windows10的组策略.不过,有部分用户反馈自己在打开组策略的时候,遇到了“无法打开此计算机上的组策略对象”提示,无法打开组策略,这是怎么回事呢?下面,小编就 ...
随机推荐
- LVS+keepalived结合
LVS+Keepalived实现高可用负载均衡(web集群) LVS+Keepalived架构图: 测试环境: 名称 操作系统 IP地址 LVS-MASTER Centos7.x 192.168. ...
- C++基础杂记(2)
将数组传入函数 禁止修改数组的值 函数的地址与函数的指针 函数的指针数组 函数的 static 与 inline 引用左值和引用传参 C++11 的数组 for 循环 64 位 Linux 操作系统中 ...
- 神经网络入门篇:神经网络的梯度下降(Gradient descent for neural networks)
神经网络的梯度下降 在这篇博客中,讲的是实现反向传播或者说梯度下降算法的方程组 单隐层神经网络会有\(W^{[1]}\),\(b^{[1]}\),\(W^{[2]}\),\(b^{[2]}\)这些参数 ...
- 用友NCC产品API使用指南
轻易云用友NCC产品API集成专题 open api简介 Open API即开放API,也称开放平台. 所谓的开放API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成 ...
- Acwing4244牛的比赛
Acwing4244.牛的比赛 题目部分 N 头奶牛,编号 1∼N,一起参加比赛. 奶牛的战斗力两两不同. 这些奶牛之间已经进行了 M轮两两对决. 在对决中,战斗力高的奶牛一定会战胜战斗力低的奶牛. ...
- Codeforces Round #691 (Div. 2) D - Glass Half Spilled(DP)
题目 补下因实验漏掉的CF(还以为是晚上,没想到是下午开始).前三题过的很顺利,到D题时想了会发现数据很小爆搜貌似能过,就以为是道水题,交了一发T了,胡乱加了点剪枝还是T.逐渐意识到事情的严重性.考虑 ...
- 全面的.NET微信网页开发之JS-SDK使用步骤、配置信息和接口请求签名生成详解
JSSDK使用步骤 步骤一:绑定安全域名: 先登录微信公众平台进入"公众号设置"的"功能设置"里填写"JS接口安全域名". 步骤二:引入JS ...
- 前端优化之路:git commit 校验拦截
[前言] 前面在git分支规范那篇文章里,介绍了commit提交规范,如下图 但是想要做到高效落地执行,就需要做些别的功课,先展示下成果图 没错,对不符合规范的commit进行了拦截,符合才可以成功提 ...
- 神经网络优化篇:详解偏差,方差(Bias /Variance)
偏差,方差 注意到,几乎所有机器学习从业人员都期望深刻理解偏差和方差,这两个概念易学难精,即使自己认为已经理解了偏差和方差的基本概念,却总有一些意想不到的新东西出现.关于深度学习的误差问题,另一个趋势 ...
- C/C++ 实现动态资源文件释放
当我们开发Windows应用程序时,通常会涉及到使用资源(Resource)的情况.资源可以包括图标.位图.字符串等,它们以二进制形式嵌入到可执行文件中.在某些情况下,我们可能需要从可执行文件中提取自 ...