[英]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属性中具有START
和END
值)。 在顺序流中过滤某些行的下游流中顺序处理文件时,可以使用标记。 它们使下游处理可以知道何时已完全处理文件。END
标记包括行数。 默认值:false
。 如果为true
,则默认情况下apply-sequence
为false
。
您可以在下游流程中使用它来确定是否可以删除文件。
谢谢阿特姆。
我确实设法解决了这个问题,但是也许以更重的方式解决了。
引入ExecutorChannel
造成执行调整相当的波动,包括:关闭setDeleteFiles
的标志AbtractFilePayloadTransformer
,更新JPA @Entity
, RunStats
和存储库这样,捕捉文件的处理状态以及处理状态为整个运行。 总而言之,处理状态更新使流程知道何时从本地文件系统中删除文件(即何时完全处理文件)以及在/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.