现象

线上spring cloud gateway时,偶尔出现几DataBufferLimitException异常,堆栈信息如下:

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)

使用max-in-memory-size配置无效

spring:
  codec:
    max-in-memory-size: 1048576

异常原因

  • 在使用ReadBodyPredicateFactory(org.springframework.cloud.gateway.handler.predicate)获取body后重新创建ServerRequest时,org.springframework.core.io.buffer.LimitedDataBufferList中判断接收数据大小超过制,org.springframework.core.codec.AbstractDataBufferDecoder中的默认262144。
package org.springframework.cloud.gateway.handler.predicate;
...
public class ReadBodyPredicateFactory
		extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
...
    /**
     * 使用webFlux创建了默认配置的Reader
     */
    private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies
            .withDefaults().messageReaders();
	@Override
	@SuppressWarnings("unchecked")
	public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
		return new AsyncPredicate<ServerWebExchange>() {
			@Override
			public Publisher<Boolean> apply(ServerWebExchange exchange) {
...
					return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
							(serverHttpRequest) -> ServerRequest
									.create(exchange.mutate().request(serverHttpRequest)
											.build(), MESSAGE_READERS)
									.bodyToMono(inClass)
									.doOnNext(objectValue -> exchange.getAttributes().put(
											CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
									.map(objectValue -> config.getPredicate()
											.test(objectValue)));
...

}

 

package org.springframework.core.io.buffer;
...
public class LimitedDataBufferList extends ArrayList<DataBuffer> {
    private final int maxByteCount; //最大数据容量
    private int byteCount; //当前接受的数据量

    //设置maxByteCount
    public LimitedDataBufferList(int maxByteCount) {
        this.maxByteCount = maxByteCount;
    }
    ...
    private void updateCount(int bytesToAdd) {
        if (this.maxByteCount >= 0) {
            //接受数据超过2147483647个byte
            if (bytesToAdd > 2147483647 - this.byteCount) {
                this.raiseLimitException(); //异常
            } else {
                this.byteCount += bytesToAdd;
                //接受数据超过maxByteCount个byte
                if (this.byteCount > this.maxByteCount) {
                    this.raiseLimitException(); //异常
                }
            }

        }
    }
...
}

 

 

解决办法

方案一

gateway-2.2.3以上版本修复了该bug,在GatewayAutoConfiguration中加入了配置写入,但只限ReadBodyPredicateFactory类,如自定义类型需要使用方案二。

package org.springframework.cloud.gateway.handler.predicate;
...
public class ReadBodyPredicateFactory
		extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
...
	private final List<HttpMessageReader<?>> messageReaders;

	public ReadBodyPredicateFactory() {
		super(Config.class);
		this.messageReaders = HandlerStrategies.withDefaults().messageReaders();
	}
    /**
     * GatewayAutoConfiguration初始化配置写入相关配置
     */
	public ReadBodyPredicateFactory(List<HttpMessageReader<?>> messageReaders) {
		super(Config.class);
		this.messageReaders = messageReaders;
	}
...

}

 

方案二

     重写ReadBodyPredicateFactory,注入ServerCodecConfigurer,使用ServerCodecConfigurer.getReaders()获取相关配置。

...
/**
 * @description: 自定义ReadBodyPredicateFactory,copy之ReadBodyPredicateFactory
 * @author: lizz
 * @date: 2020/6/8 14:22
 */
@Component
public class GwReadBodyPredicateFactory extends AbstractRoutePredicateFactory<GwReadBodyPredicateFactory.Config> {
    /**
     * 获取Spring配置,解决最大body问题
     */
    @Autowired
    ServerCodecConfigurer codecConfigurer;

    @Override
    @SuppressWarnings("unchecked")
    public AsyncPredicate<ServerWebExchange> applyAsync(GwReadBodyPredicateFactory.Config config) {
...
        return new AsyncPredicate<ServerWebExchange>() {
            @Override
            public Publisher<Boolean> apply(ServerWebExchange exchange) {
                    return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
                            (serverHttpRequest) -> ServerRequest
                                    .create(exchange.mutate().request(serverHttpRequest)
                                            .build(), codecConfigurer.getReaders())
                                    .bodyToMono(inClass)
									.doOnNext(objectValue -> exchange.getAttributes().put(
											CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
									.map(objectValue -> config.getPredicate()
											.test(objectValue)));
...
}

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐