Camel:文件消费者组件“咬掉的东西超过它可以咀嚼”,管道因内存不足而死亡

时间:2017-02-18 22:14:54

标签: java apache-camel enterprise-integration

我在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>/inprogressmaxMessagesPerPoll选项。他们似乎一开始就缓解了这个问题,但经过一系列的民意调查后,我仍然以“开始”的方式结束了数千个文件。

唯一有效的方法是使eagerMaxMessagesPerPolldelay的瓶颈变窄,以使平均处理速度比文件轮询周期快。

显然,这不是我想要的。我希望我的管道尽可能快地处理文件,但速度不快。我希望文件消费者在路线繁忙时等待。

我犯了一个明显的错误吗?

(我在使用XFS的Redhat 7机器上运行了一个较旧的Camel 2.14.0,如果这是问题的一部分。)

3 个答案:

答案 0 :(得分:2)

尝试将maxMessagesPerPoll设置为来自文件端点的较低值,以便每次轮询仅获取最多X个文件,这也会限制您在Camel应用程序中将拥有的机上信息总数。

您可以在文件组件的Camel文档中找到有关该选项的更多信息

答案 1 :(得分:0)

除非你真的需要将数据保存为文件,否则我会提出另一种解决方案。

从您的restlet使用者,将每个请求发送到消息排队应用程序,如activemq或rabbitmq或类似的东西。您将很快收到该队列上的大量消息,但没关系。

然后用队列使用者替换您的文件使用者。这需要一些时间,但每条消息应单独处理并发送到您想要的任何地方。我用大约500 000条消息测试了rabbitmq,并且运行正常。这也应该减少消费者的负担。

答案 2 :(得分:0)

简短的回答是没有答案:Camel文件组件的sortBy选项对于容纳我的用例来说效率太低,无法满足:

  • 唯一性:如果文件已存在,我不想将文件放在队列中。
  • 优先级:应首先处理标记为高优先级的文件。
  • 性能:拥有几十万个文件,甚至几百万个文件应该没问题。
  • FIFO :(奖励)应首先获取最早的文件(优先级)。

问题似乎是,如果我正确读取source codedocumentation,所有文件详细信息都在内存中以执行排序,无论是内置语言还是自定义使用可插入的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);
        }
    }
}