拆分大流时弹簧集成背压误差

时间:2017-12-09 06:11:22

标签: spring spring-integration reactive-programming project-reactor enterprise-integration

目标是将大型json.gz文件(4 GB压缩,大约12 GB未压缩,1200万行)从Web服务器直接流式传输到数据库,而无需在本地下载。由于Spring集成出站网关不支持gzip格式,我自己使用okhttp自动解压缩响应:

body = response.body().byteStream(); // thanks okhttp
reader = new InputStreamReader(body, StandardCharsets.UTF_8);
br = new BufferedReader(reader, bufferSize);

Flux<String> flux = Flux.fromStream(br.lines())
    .onBackpressureBuffer(10000, x -> log.error("Buffer overrun!"))
    .doAfterTerminate(() -> closeQuietly(closeables))
    .doOnError(t -> log.error(...))

在整合流程中:

.handle(new MessageTransformingHandler(new GzipToFluxTransformer(...)))
.split()
.log(LoggingHandler.Level.DEBUG, CLASS_NAME, Message::getHeaders)
.channel(repositoryInputChannel())

但是

2017-12-08 22:48:47.846 [task-scheduler-7] [ERROR] c.n.d.y.s.GzipToFluxTransformer - Buffer overrun!
2017-12-08 22:48:48.337 [task-scheduler-7] [ERROR] o.s.i.h.LoggingHandler - org.springframework.messaging.MessageHandlingException: 
error occurred in message handler [org.springframework.integration.splitter.DefaultMessageSplitter#1]; 
nested exception is reactor.core.Exceptions$OverflowException: The receiver is overrun by more signals than expected (bounded queue...), 
failedMessage=...}]
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:153)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:132)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)

使用桥接轮询的无界队列在运行时连接输出通道。这是为了便于测试,以便可以用DirectChannel代替队列进行测试。

@Bean(name = "${...}")
public PollableChannel streamingOutputChannel() {
    return new QueueChannel();
}

@Bean
public IntegrationFlow srcToSinkBridge() {
    return IntegrationFlows.from(streamingOutputChannel())
        .bridge(e -> e.poller(Pollers.fixedDelay(500)))
        .channel(repositoryInputChannel())
        .get();
}

我在这里有几个疑问。

  1. 我不确定bean名称中使用SPEL的动态绑定是否有效,但我不知道如何验证它。
  2. 由于队列无限制,我能想到的只是轮询不够快。但是,例外情况表明分离器在跟上时存在问题。

1 个答案:

答案 0 :(得分:1)

问题是log声明!它使用窃听将分离器的输出通道更改为DirectChannel,这会混淆AbstractMessageSplitter.

的逻辑
boolean reactive = getOutputChannel() instanceof ReactiveStreamsSubscribableChannel;

引用文档:

  

从5.0版开始,......如果Splitter的输出通道是   ReactiveStreamsSubscribableChannel的实例,   AbstractMessageSplitter生成Flux结果而不是Iterator   并且输出通道订阅了此Flux以​​获得背压   基于对下游流量需求的分割。

工作代码如下 - 只需在分离器后立即将日志语句移动到最后修复背压问题:

IntegrationFlows.from(inputChannel)
.filter(Message.class, msg -> msg.getHeaders().containsKey(FILE_TYPE_HEADER))
.handle(new GzipToFluxTransformer(...))
.transform((Flux<String> payload) -> payload
        .onBackpressureBuffer(getOnBackpressureBufferSize(),
                s -> log.error("Buffer overrun!")))
.split()
.channel(c -> c.flux(outputChannel))
.log(LoggingHandler.Level.DEBUG, CLASS_NAME, Message::getHeaders)
.get();

我在Spring集成GitHub上打开了问题2302