DataBufferLimitException:超过最大字节数限制以缓冲 webflux 错误

Posted

技术标签:

【中文标题】DataBufferLimitException:超过最大字节数限制以缓冲 webflux 错误【英文标题】:DataBufferLimitException: Exceeded limit on max bytes to buffer webflux error 【发布时间】:2020-05-01 07:22:38 【问题描述】:

发送文件时,我收到一个字节数组。我总是遇到 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 中解决这个问题?

【问题讨论】:

github.com/spring-projects/spring-framework/issues/23961 谢谢 【参考方案1】:

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

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

【讨论】:

我在我的 Spring Boot 应用程序配置中使用它,但是它没有帮助。 @mareck_ste 你好!也许您正在使用一些覆盖此选项的自定义配置。例如。你有 WebClient 配置,所以只需在那个 WebClientBuilder.exchangeStrategies() 中设置这个 'maxInMemorySize' 属性 @mareck_ste 确实,我对 spring-boot-starter-webflux 2.3.5-RELEASE 有同样的看法。看看这个excelent answer【参考方案2】:

Spring Boot application.properties 配置文件中设置最大 bytes(以 megabytes 为单位,如下所示:

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

【讨论】:

【参考方案3】:

我在一个简单的 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);
    

这出奇地难找

【讨论】:

为什么要显示 addResourceHandlers 方法?它是否与问题有关?【参考方案4】:

这对我有用:

    在您的配置类之一或主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);
        
    
    

【讨论】:

不知道为什么,这是唯一适用于 WebFlux 2.3.2.RELEASE 的解决方案 如果您声明了自定义 Web 配置(例如,通过实现 WebMvcConfigurer),则覆盖 properties.yaml 中的选项。因此,如果你设置maxInMemorySize属性,你直接配置你的webclient,绕过spring-boot的Web配置。【参考方案5】:

您可以流式传输,而不是立即检索数据:

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 解析器具有流式接口。

【讨论】:

buffer 定义在哪里? @AshokKoyi 我之前混合了两个变体(已修复) 你真的测量了整体内存占用吗?即即使在释放数据缓冲区之后,除非您使用最终流,否则内存仍然会堆积,因为您正在使用归约,直到您收到最后一个字节。所以,我不确定使用这种方法是否会有任何优势 选项 (1) 是有问题的,因为您不能保证在将字节转换为字符串之前收到所有数据。在映射操作期间,我们可能只读取了 4 字节 UTF-8 字符的 1 字节 如果可以利用部分数据并释放缓冲区,那是可以做的最好的事情。使用归约是一个坏主意,因为我们正在填充内存直到完全归约完成,这违背了在缓冲区到来时使用缓冲区的意义。选项(1)有效,但只能作为字节数组,不能作为字符串【参考方案6】:

另一种选择是创建自定义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)
        
    

【讨论】:

【参考方案7】:

这对我有用

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

【讨论】:

【参考方案8】:

为我工作

webTestClient.mutate().codecs(configurer -> configurer
  .defaultCodecs()
  .maxInMemorySize(16 * 1024 * 1024)).build().get()
  .uri("/u/r/l")
  .exchange()
  .expectStatus()
  .isOk()

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

以上是关于DataBufferLimitException:超过最大字节数限制以缓冲 webflux 错误的主要内容,如果未能解决你的问题,请参考以下文章