[英]Camel: File consumer component “bites off more than it can chew”, pipeline dies from out-of-memory error
我在Camel中定义了一条路由,该路由类似于以下内容:收到GET请求,在文件系统中创建了一个文件。 文件使用者将其拾取,从外部Web服务中获取数据,然后通过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();
多播中的三个端点只是将结果消息发布到其他Web服务。
当队列(即文件目录cameldest
)为空时,这一切都很好。 正在使用cameldest/<subdir>
创建文件,由文件使用者将其拾取并移入cameldest/<subdir>/inprogress
,并且将东西发送到三个外发POST都没问题。
但是,一旦传入的请求堆积了大约300,000个文件,进度就会变慢,最终由于内存不足错误 (超出了GC开销限制), 管道将失败 。
通过增加日志记录,我可以看到文件使用者轮询基本上从未运行,因为它似乎对每次看到的所有文件负责 ,等待它们完成处理,然后才开始另一轮轮询。 除了(我假设)导致资源瓶颈之外,这还干扰了我的排序要求:一旦队列中塞满了数千条等待处理的消息,新消息将被天真地排序得更高-如果它们仍然被捡起-仍在等待那些已经“开始”的人。
现在,我已经试过maxMessagesPerPoll
和eagerMaxMessagesPerPoll
选项。 一开始它们似乎可以缓解问题,但经过多次调查后,我仍然在“开始的”困境中得到了数千个文件。
唯一maxMessages...
就是使delay
和maxMessages...
瓶颈变得如此狭窄,以至于平均而言,处理将比文件轮询周期更快。
显然,那不是我想要的。 我希望我的管道尽快处理文件,但不要更快。 我期望文件使用者在路由繁忙时等待。
我犯了一个明显的错误吗?
(如果这是问题的一部分,那么我将在带有XFS的Redhat 7机器上运行稍旧的Camel 2.14.0。)
尝试将源文件端点上的maxMessagesPerPoll设置为一个较低的值,以使每次轮询最多只能拾取X个文件,这也限制了您的Camel应用程序中的运行中消息总数。
您可以在Camel文档中找到有关该选项的更多信息,该文件组件
除非您确实需要将数据另存为文件,否则我将提出一种替代解决方案。
从您的restlet使用者处,将每个请求发送到消息队列应用程序,例如activemq或rabbitmq或类似的东西。 您很快就会在该队列中收到很多消息,但这没关系。
然后,将文件使用方替换为队列使用方。 这将需要一些时间,但是每条消息都应分别处理并发送到所需的任何地方。 我已经用大约500 000条消息测试了rabbitmq,并且效果很好。 这也应减轻消费者的负担。
简短的答案是没有答案:Camel的文件组件的sortBy
选项太内存sortBy
,无法适应我的用例:
问题是,如果我正确阅读了源代码和文档 ,则无论使用内置语言还是自定义可插拔sorter
,所有文件详细信息都在内存中以执行排序。 文件组件总是会创建一个包含所有细节对象的列表,而且显然会导致垃圾收集开销的疯狂额时轮询许多文件经常。
大多数情况下,我的用例都能正常工作,而不必通过以下步骤使用数据库或编写自定义组件:
cameldest/queue
移动到两个使用方 ,子目录递归地对子目录中的文件( cameldest/queue/high/
cameldest/queue/low/
)进行分类,每个目录一个,不进行任何排序。 /cameldest/queue/high/
仅设置使用者以通过我的实际业务逻辑处理文件。 /cameldest/queue/low
设置使用者,以简单地将文件从“ low”升级为“ high”(将其复制,即.to("file://cameldest/queue/high");
) ThrottlingInflightRoutePolicy
添加到“高”,以防止它一次影响太多的交换。 想象一下,就像在机场办理登机手续一样,如果那里是空的话,就会邀请游客进入商务舱专用道。
这在负载下就像是一种魅力,即使数十万个文件处于“低”队列中,新消息(文件)也可以在几秒钟内直接处理成“高”。
该解决方案不能满足的唯一要求是顺序性:不能保证首先拾取较旧的文件,而是随机拾取它们。 可以想象这样一种情况,一堆稳定的传入文件流可能导致一个特定的文件X总是很不走运,而且永远不会被拾取。 但是,发生这种情况的机会很小。
可能的改进:当前,允许/中止将文件从“低”提升为“高”的提升的阈值设置为“高”飞行中的0条消息。 一方面,这保证了放到“高”位置的文件将在执行从“低”位置进行的另一次升级之前得到处理,另一方面,这会导致有点停止启动模式,尤其是在多线程环境中场景。 虽然这不是一个真正的问题,但其性能还是令人印象深刻的。
资源:
我的路线定义:
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);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.