我在Camel中定义了一个类似于此的路由:GET请求进来,文件在文件系统中创建。文件消费者选择它,从外部Web服务获取数据,并通过POST将结果消息发送到其他Web服务。
以下简化代码:
selected
多播中的三个端点只是将生成的消息POST到其他Web服务。
当队列(即文件目录 // Update request goes on queue:
from("restlet:http://localhost:9191/update?restletMethod=post")
.routeId("Update via POST")
[...some magic that defines a directory and file name based on request headers...]
.to("file://cameldest/queue?allowNullBody=true&fileExist=Ignore")
// Update gets processed
from("file://cameldest/queue?delay=500&recursive=true&maxDepth=2&sortBy=file:parent;file:modified&preMove=inprogress&delete=true")
.routeId("Update main route")
.streamCaching() //otherwise stuff can't be sent to multiple endpoints
[...enrich message from some web service using http4 component...]
.multicast()
.stopOnException()
.to("direct:sendUpdate", "direct:dependencyCheck", "direct:saveXML")
.end();
)相当空时,这一切都运行良好。文件正在cameldest
中创建,由文件使用者提取并移至cameldest/<subdir>
,并且正在向三个外发POST发送内容没问题。
但是,一旦传入的请求堆积到大约300,000个文件进度减慢,并且最终管道由于内存不足错误而失败(超出GC开销限制)。
通过增加日志记录,我可以看到文件消费者轮询基本上从不运行,因为似乎每次都对它看到的所有文件负责,等待它们完成处理,并且只然后开始另一轮民意调查。除此之外(我假设)造成资源瓶颈,这也会影响我的排序要求:一旦队列被数千条等待处理的消息堵塞,那些天真地排序得更高的新消息是 - 如果他们甚至还是被拿起来的话 - 仍在等待那些已经“开始”的人。
现在,我尝试了cameldest/<subdir>/inprogress
和maxMessagesPerPoll
选项。他们似乎一开始就缓解了这个问题,但经过一系列的民意调查后,我仍然以“开始”的方式结束了数千个文件。
唯一有效的方法是使eagerMaxMessagesPerPoll
和delay
的瓶颈变窄,以使平均处理速度比文件轮询周期快。
显然,这不是我想要的。我希望我的管道尽可能快地处理文件,但速度不快。我希望文件消费者在路线繁忙时等待。
我犯了一个明显的错误吗?
(我在使用XFS的Redhat 7机器上运行了一个较旧的Camel 2.14.0,如果这是问题的一部分。)
答案 0 :(得分:2)
尝试将maxMessagesPerPoll设置为来自文件端点的较低值,以便每次轮询仅获取最多X个文件,这也会限制您在Camel应用程序中将拥有的机上信息总数。
您可以在文件组件的Camel文档中找到有关该选项的更多信息
答案 1 :(得分:0)
除非你真的需要将数据保存为文件,否则我会提出另一种解决方案。
从您的restlet使用者,将每个请求发送到消息排队应用程序,如activemq或rabbitmq或类似的东西。您将很快收到该队列上的大量消息,但没关系。
然后用队列使用者替换您的文件使用者。这需要一些时间,但每条消息应单独处理并发送到您想要的任何地方。我用大约500 000条消息测试了rabbitmq,并且运行正常。这也应该减少消费者的负担。
答案 2 :(得分:0)
简短的回答是没有答案:Camel文件组件的sortBy
选项对于容纳我的用例来说效率太低,无法满足:
问题似乎是,如果我正确读取source code和documentation,所有文件详细信息都在内存中以执行排序,无论是内置语言还是自定义使用可插入的sorter
。文件组件始终创建包含所有详细信息的对象列表,并且在经常轮询许多文件时显然会导致疯狂数量的垃圾收集开销。
我使用大多数用例,无需使用数据库或编写自定义组件,使用以下步骤:
cameldest/queue
上的一个文件使用者移动,该目录以递归方式将子目录中的文件(cameldest/queue/high/
之前的cameldest/queue/low/
)排序到两个使用者,一个对于每个目录,根本没有排序。/cameldest/queue/high/
设置消费者以通过我的实际业务逻辑处理文件。/cameldest/queue/low
设置为简单地将文件从“低”提升为“高”(将其复制,即.to("file://cameldest/queue/high");
)ThrottlingInflightRoutePolicy
到“高”,以防止它一次过多地进行交换。想象一下这就像在机场办理登机手续一样,如果空的话,旅游者会被邀请进入商务舱车道。
这就像一个负载下的魅力,即使成千上万的文件在“低”排队,直接下降到“高”的新消息(文件)在几秒钟内得到处理。
此解决方案未涵盖的唯一要求是有序性:无法保证先找到旧文件,而是随机拾取它们。人们可以想象一种情况,即稳定的传入文件流可能导致一个特定的文件X总是不吉利而且从未被拾取。然而,发生这种情况的可能性非常低。
可能的改进:目前,允许/暂停文件升级的门槛从“低”到“高”设置为0消息以“高”为单位。一方面,这个保证在执行“low”的另一次促销之前将处理掉入“high”的文件,另一方面它会导致一些停止 - 启动模式,尤其是在多线程场景中。虽然不是一个真正的问题,但是表现却令人印象深刻。
<强>来源:强>
我的路线定义:
ThrottlingInflightRoutePolicy trp = new ThrottlingInflightRoutePolicy();
trp.setMaxInflightExchanges(50);
SuspendOtherRoutePolicy sorp = new SuspendOtherRoutePolicy("lowPriority");
from("file://cameldest/queue/low?delay=500&maxMessagesPerPoll=25&preMove=inprogress&delete=true")
.routeId("lowPriority")
.log("Copying over to high priority: ${in.headers."+Exchange.FILE_PATH+"}")
.to("file://cameldest/queue/high");
from("file://cameldest/queue/high?delay=500&maxMessagesPerPoll=25&preMove=inprogress&delete=true")
.routeId("highPriority")
.routePolicy(trp)
.routePolicy(sorp)
.threads(20)
.log("Before: ${in.headers."+Exchange.FILE_PATH+"}")
.delay(2000) // This is where business logic would happen
.log("After: ${in.headers."+Exchange.FILE_PATH+"}")
.stop();
我的SuspendOtherRoutePolicy
,松散地构建为ThrottlingInflightRoutePolicy
public class SuspendOtherRoutePolicy extends RoutePolicySupport implements CamelContextAware {
private CamelContext camelContext;
private final Lock lock = new ReentrantLock();
private String otherRouteId;
public SuspendOtherRoutePolicy(String otherRouteId) {
super();
this.otherRouteId = otherRouteId;
}
@Override
public CamelContext getCamelContext() {
return camelContext;
}
@Override
public void onStart(Route route) {
super.onStart(route);
if (camelContext.getRoute(otherRouteId) == null) {
throw new IllegalArgumentException("There is no route with the id '" + otherRouteId + "'");
}
}
@Override
public void setCamelContext(CamelContext context) {
camelContext = context;
}
@Override
public void onExchangeDone(Route route, Exchange exchange) {
//log.info("Exchange done on route " + route);
Route otherRoute = camelContext.getRoute(otherRouteId);
//log.info("Other route: " + otherRoute);
throttle(route, otherRoute, exchange);
}
protected void throttle(Route route, Route otherRoute, Exchange exchange) {
// this works the best when this logic is executed when the exchange is done
Consumer consumer = otherRoute.getConsumer();
int size = getSize(route, exchange);
boolean stop = size > 0;
if (stop) {
try {
lock.lock();
stopConsumer(size, consumer);
} catch (Exception e) {
handleException(e);
} finally {
lock.unlock();
}
}
// reload size in case a race condition with too many at once being invoked
// so we need to ensure that we read the most current size and start the consumer if we are already to low
size = getSize(route, exchange);
boolean start = size == 0;
if (start) {
try {
lock.lock();
startConsumer(size, consumer);
} catch (Exception e) {
handleException(e);
} finally {
lock.unlock();
}
}
}
private int getSize(Route route, Exchange exchange) {
return exchange.getContext().getInflightRepository().size(route.getId());
}
private void startConsumer(int size, Consumer consumer) throws Exception {
boolean started = super.startConsumer(consumer);
if (started) {
log.info("Resuming the other consumer " + consumer);
}
}
private void stopConsumer(int size, Consumer consumer) throws Exception {
boolean stopped = super.stopConsumer(consumer);
if (stopped) {
log.info("Suspending the other consumer " + consumer);
}
}
}