繁体   English   中英

处理完所有行后,Spring Integration Java DSL流拆分器/聚合器删除文件

[英]Spring Integration Java DSL flow Splitter/Aggregator delete file after processing all lines

使用Spring Integration Java DSL,我构造了一个流程,在其中使用FileSplitter同步处理文件。 在将File中的每一行转换为Message以进行后续处理之后,我已经能够在AbstractFilePayloadTransformer上使用setDeleteFiles标志来删除文件,如下所示:

@Bean
protected IntegrationFlow s3ChannelFlow() {
    // do not exhaust filesystem w/ files downloaded from S3
    FileToInputStreamTransformer transformer = new FileToInputStreamTransformer();
    transformer.setDeleteFiles(true);

    // @see http://docs.spring.io/spring-integration/reference/html/files.html#file-reading
    // @formatter:off
    return IntegrationFlows
        .from(s3Channel())
        .channel(StatsUtil.createRunStatsChannel(runStatsRepository))
        .transform(transformer)
        .split(new FileSplitter())
        .transform(new JsonToObjectViaTypeHeaderTransformer(new Jackson2JsonObjectMapper(objectMapper), typeSupport))
        .publishSubscribeChannel(p -> p.subscribe(persistenceSubFlow()))
        .get();
    // @formatter:on
}

这可以正常工作,但是很慢。 因此,我尝试在上述.split之后添加ExecutorChannel ,如下所示:

.channel(c -> c.executor(Executors.newFixedThreadPool(10)))

但是随后,上述删除标志不允许流在完全读取文件之前成功完成删除文件。

如果删除该标志,则有可能耗尽从S3同步文件的本地文件系统。

我上面可以介绍什么:a)完全处理每个文件,b)完成后从本地文件系统删除文件? 换句话说,是否有一种方法可以确切地知道何时完全处理了文件(何时通过缓冲池中的线程异步处理了行)?

如果您好奇,这是FileToInputStreamTransformer

public class FileToInputStreamTransformer extends AbstractFilePayloadTransformer<InputStream> {

    private static final int BUFFER_SIZE = 64 * 1024; // 64 kB

    @Override
    // @see http://java-performance.info/java-io-bufferedinputstream-and-java-util-zip-gzipinputstream/
    protected InputStream transformFile(File payload) throws Exception {
        return new GZIPInputStream(new FileInputStream(payload), BUFFER_SIZE);
    }
}

UPDATE

那么,下游流程中的某些信息如何知道需要什么呢?

顺便说一句,如果我正确地遵循了您的建议,当我使用上面的new FileSplitter(true, true)更新.split时,我得到

2015-10-20 14:26:45,288 [pool-6-thread-1] org.springframework.integration.handler.LoggingHandler ERROR org.springframework.integration.transformer.MessageTransformationException: failed to transform message; nested exception is java.lang.IllegalArgumentException: 'json' argument must be an instance of: [class java.lang.String, class [B, class java.io.File, class java.net.URL, class java.io.InputStream, class java.io.Reader] , but gotten: class org.springframework.integration.file.splitter.FileSplitter$FileMarker
    at org.springframework.integration.transformer.AbstractTransformer.transform(AbstractTransformer.java:44)

FileSplitter具有用于此目的的markers 选项

设置为true以在文件数据之前和之后发出文件标记消息的开始/结束。 标记是带有FileSplitter.FileMarker负载的消息(在mark属性中具有STARTEND值)。 在顺序流中过滤某些行的下游流中顺序处理文件时,可以使用标记。 它们使下游处理可以知道何时已完全处理文件。 END标记包括行数。 默认值: false 如果为true ,则默认情况下apply-sequencefalse

您可以在下游流程中使用它来确定是否可以删除文件。

谢谢阿特姆。

我确实设法解决了这个问题,但是也许以更重的方式解决了。

引入ExecutorChannel造成执行调整相当的波动,包括:关闭setDeleteFiles的标志AbtractFilePayloadTransformer ,更新JPA @EntityRunStats和存储库这样,捕捉文件的处理状态以及处理状态为整个运行。 总而言之,处理状态更新使流程知道何时从本地文件系统中删除文件(即何时完全处理文件)以及在/stats/{run}端点中返回状态,以便用户可以知道何时运行完成。

这是我的实现的摘要(如果有人好奇的话)...

class FileToInputStreamTransformer extends AbstractFilePayloadTransformer<InputStream> {

private static final int BUFFER_SIZE = 64 * 1024; // 64 kB

@Override
// @see http://java-performance.info/java-io-bufferedinputstream-and-java-util-zip-gzipinputstream/
protected InputStream transformFile(File payload) throws Exception {
    return new GZIPInputStream(new FileInputStream(payload), BUFFER_SIZE);
}
}

public class RunStatsHandler extends AbstractMessageHandler {

private final SplunkSlf4jLogger log = new SplunkSlf4jLogger(LoggerFactory.getLogger(getClass()));
private static final int BUFFER_SIZE = 64 * 1024; // 64 kB

private final RunStatsRepository runStatsRepository;

public RunStatsHandler(RunStatsRepository runStatsRepository) {
    this.runStatsRepository = runStatsRepository;
}

// Memory efficient routine, @see http://www.baeldung.com/java-read-lines-large-file
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
    RunStats runStats = message.getHeaders().get(RunStats.RUN, RunStats.class);
    String token = message.getHeaders().get(RunStats.FILE_TOKEN, String.class);
    if (runStats != null) {
        File compressedFile = (File) message.getPayload();
        String compressedFileName = compressedFile.getCanonicalPath();
        LongAdder lineCount = new LongAdder();
        // Streams and Scanner implement java.lang.AutoCloseable
        InputStream fs = new FileInputStream(compressedFile);
        InputStream gzfs = new GZIPInputStream(fs, BUFFER_SIZE);
        try (Scanner sc = new Scanner(gzfs, "UTF-8")) {
            while (sc.hasNextLine()) {
                sc.nextLine();
                lineCount.increment();
            }
            // note that Scanner suppresses exceptions
            if (sc.ioException() != null) {
                log.warn("file.lineCount", ImmutableMap.of("run", runStats.getRun(), "file", compressedFileName, 
                        "exception", sc.ioException().getMessage()));
                throw sc.ioException();
            }
            runStats.addFile(compressedFileName, token, lineCount.longValue());
            runStatsRepository.updateRunStats(runStats);
            log.info("file.lineCount",
                    ImmutableMap.of("run", runStats.getRun(), "file", compressedFileName, "lineCount", lineCount.intValue()));
        }
    }
}

}

更新流程

@Bean
protected IntegrationFlow s3ChannelFlow() {
    // @see http://docs.spring.io/spring-integration/reference/html/files.html#file-reading
    // @formatter:off
    return IntegrationFlows
        .from(s3Channel())
        .enrichHeaders(h -> h.headerFunction(RunStats.FILE_TOKEN, f -> UUID.randomUUID().toString()))
        .channel(runStatsChannel())
        .channel(c -> c.executor(Executors.newFixedThreadPool(persistencePoolSize)))
        .transform(new FileToInputStreamTransformer())
        .split(new FileSplitter())
        .transform(new JsonToObjectViaTypeHeaderTransformer(new Jackson2JsonObjectMapper(objectMapper), typeSupport))
        .publishSubscribeChannel(p -> p.subscribe(persistenceSubFlow()))
        .get();
    // @formatter:on
}

@Bean
public MessageChannel runStatsChannel() {
    DirectChannel wiretapChannel = new DirectChannel();
    wiretapChannel.subscribe(new RunStatsHandler(runStatsRepository));
    DirectChannel loggingChannel = new DirectChannel();
    loggingChannel.addInterceptor(new WireTap(wiretapChannel));
    return loggingChannel;
}

不幸的是,我无法共享RunStats和回购实现。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM