数据缓冲区限制异常:超过了WebFlux错误缓冲最大字节数的限制。

118

在发送文件时,我会收到一个字节数组。但是使用webflux接收数组时总是出现问题。

抛出的错误如下:

org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
    at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:101)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException

你知道如何在webflux中解决这个问题吗?


3
https://github.com/spring-projects/spring-framework/issues/23961 - Martin Tarjányi
您也可以参考:https://www.baeldung.com/spring-webflux-databufferlimitexception - user2173372
大多数(全部?)答案都建议配置缓冲区的大小。我认为没有其他选择。这感觉像是WebClient API的失败。实现应该在需要时增加后备缓冲区,就像ArrayList在需要时会增加后备数组一样。 - JohnC
14个回答

157

这对我有用:

  1. 在你的配置类或主要的 SpringBootApplication 类中创建一个 @Bean

@Bean
public WebClient webClient() {
    final int size = 16 * 1024 * 1024;
    final ExchangeStrategies strategies = ExchangeStrategies.builder()
        .codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(size))
        .build();
    return WebClient.builder()
        .exchangeStrategies(strategies)
        .build();
}
  • 接下来,进入您想要使用WebClient的目标类:

  • @Service
    public class TestService {
    
        @Autowired
        private WebClient webClient;
    
        public void test() {
            String out = webClient
                .get()
                .uri("/my/api/endpoint")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    
            System.out.println(out);
        }
    }
    

    6
    不知道为什么,这是在WebFlux 2.3.2.RELEASE上运作的唯一解决方案。 - zerologiko
    1
    如果您已经声明了自定义的Web配置(例如通过实现WebMvcConfigurer),则可以覆盖properties.yaml中的选项。因此,如果设置了maxInMemorySize属性,则直接配置您的webclient,绕过spring-boot的Web配置。 - Georgios Syngouroglou

    86

    我认为这个问题是关于在Spring Boot中添加一个新的spring.codec.max-in-memory-size配置属性。 将其添加到application.yml文件中,如下所示:

    spring:
      codec:
        max-in-memory-size: 10MB
    

    8
    我在我的Spring Boot应用程序配置中使用了这个,但它没有起到帮助作用。 - mareck_ste
    11
    @mareck_ste 你好!也许你正在使用一些自定义配置来覆盖此选项。例如,你有WebClient配置,所以只需在WebClientBuilder.exchangeStrategies()中设置'maxInMemorySize'属性即可。 - David
    3
    @mareck_ste 确实,我也有同样的问题,针对 spring-boot-starter-webflux 2.3.5-RELEASE 版本。请查看这个优秀的答案 - bernard paulus
    谢谢Bernard,修改编解码器起作用了。 - Bobin
    4
    如果上述基于配置的解决方案不起作用,那么以下可能会解释原因 - 您需要使用预配置的Spring WebClient.Builder而不是创建自己的https://github.com/spring-projects/spring-boot/issues/27836。 - kellyfj
    在我的情况下,我需要将此设置放置在API网关设置中,而不是端点服务设置中。 - PowerAktar

    31

    请按以下方式在您的 Spring Boot application.properties 配置文件中设置最大字节数(以为单位):

    spring.codec.max-in-memory-size=20MB
    

    2
    如果那不起作用,可能是因为这个:https://github.com/spring-projects/spring-boot/issues/27836 - kellyfj

    24
    这是官方的方法(请参阅文档)来解决这个异常的问题。
    webTestClient.mutate()
      .codecs(configurer -> configurer
              .defaultCodecs()
              .maxInMemorySize(16 * 1024 * 1024))
      .build().get()
      .uri("/u/r/l")
      .exchange()
      .expectStatus()
      .isOk()
    

    1
    谢谢!不确定为什么使用WebClient的属性或bean对我无效,但这个方法有效! - JayC
    仅补充一下,"codecs" 是重要的部分。 - Younes El Ouarti

    17

    在一个简单的RestController中(我发送了一个大的JSON字符串),我一直遇到这个错误。

    这里是我如何成功更改maxInMemorySize的方法。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.config.ResourceHandlerRegistry;
    import org.springframework.web.reactive.config.WebFluxConfigurer;
    
    @Configuration
    public class WebfluxConfig implements WebFluxConfigurer {
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
    
            registry.addResourceHandler("/swagger-ui.html**")
                .addResourceLocations("classpath:/META-INF/resources/");
    
            registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    
        @Override
        public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
            configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024);
        }
    }
    

    这个竟然很难找到。


    4
    为什么你要展示addResourceHandlers方法?这与问题有关吗? - Zucca

    17

    不需要一次性检索数据,你可以使用流式传输:

    Mono<String> string = webClient.get()
        .uri("end point of an API")
        .retrieve()
        .bodyToFlux(DataBuffer.class)
        .map(buffer -> {
            String string = buffer.toString(Charset.forName("UTF-8"));
            DataBufferUtils.release(buffer);
            return string;
        });
    

    或者转换为流:

        .map(b -> b.asInputStream(true))
        .reduce(SequenceInputStream::new)
        .map(stream -> {
            // consume stream
            stream.close();
            return string;
        });
    

    大多数情况下,您不想真正聚合流,而是直接处理它。需要在内存中加载大量数据的需求主要是改变为更具响应性的方法的信号。JSON和XML解析器具有流式接口。


    2
    buffer 定义在哪里? - Ashok Koyi
    1
    @AshokKoyi 我之前混淆了两个变量(已修复) - Tires
    2
    你是否实际测量了整体内存占用?即使释放了数据缓冲区,除非消耗了最终流,否则内存仍会堆积,因为你在使用减少操作直到接收到最后一个字节。因此,我不确定使用这种方法是否会有任何优势。 - Ashok Koyi
    选项(1)存在问题,因为在将字节转换为字符串之前,不能保证已接收到所有数据。在映射操作期间,可能只读取了4字节UTF-8字符的1个字节。 - Ashok Koyi
    1
    这个想法是所有答案中最好的一个。唯一我不喜欢的是示例代码中对DataBufferUtils.release(buffer)的调用。这似乎像是手动内存管理,而这与 Java 的通常做法相左。 - Krzysztof Tomaszewski
    显示剩余3条评论

    7

    这对我有效

    val exchangeStrategies = ExchangeStrategies.builder()
                    .codecs { configurer: ClientCodecConfigurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024) }.build()
            return WebClient.builder().exchangeStrategies(exchangeStrategies).build()
    

    3

    另一个选择是创建自定义的CodecCustomizer,它将同时应用于WebFluxWebClient:

    @Configuration
    class MyAppConfiguration {
    
        companion object {
            private const val MAX_MEMORY_SIZE = 50 * 1024 * 1024 // 50 MB
        }
    
        @Bean
        fun codecCustomizer(): CodecCustomizer {
            return CodecCustomizer {
                it.defaultCodecs()
                    .maxInMemorySize(MAX_MEMORY_SIZE)
            }
        }
    }
    

    2

    从Spring Boot 2.3.0开始,现在有一个专门的配置属性用于Reactive Elasticsearch REST客户端。

    您可以使用以下配置属性来为客户端设置特定的内存限制。

    spring.data.elasticsearch.client.reactive.max-in-memory-size= 已经存在的spring.codec.max-in-memory-size属性是独立的,并且仅影响应用程序中的其他WebClient实例。


    1
    这个可以替换为spring.elasticsearch.webclient.max-in-memory-size=512MB 参见 - rajadilipkolli

    0
    请注意,如果您正在使用自定义解码器,则需要在其中设置最大内存大小,例如:
    Integer CODEC_20_MB_SIZE = 20 * 1024 * 1024;
    WebClient.builder()
        .codecs(clientCodecConfigurer -> {
            var codec = new Jackson2JsonDecoder();
            codec.setMaxInMemorySize(CODEC_20_MB_SIZE);
            clientCodecConfigurer.customCodecs().register(codec);
            clientCodecConfigurer.customCodecs().register(new Jackson2JsonEncoder());
        });
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接