现象描述
- 网关Hoxton.RELEASE版本,接入Apollo。
- 在项目开启限流、压测一段时间后,netty_data_buffer的内存逐渐变大,最终导致内存溢出。
- 项目在运行过程中开启了ReadBodyPredicateFactory断言。
问题定位
? ? ? ? 1、在ReadBodyPredicateFactory中进行请求体缓存。
public class ReadBodyPredicateFactory
extends AbstractRoutePredicateFactory<ReadBodyPredicateFactory.Config> {
...
@Override
@SuppressWarnings("unchecked")
public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
return exchange -> {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
Mono<?> modifiedBody;
// We can only read the body from the request once, once that happens if we
// try to read the body again an exception will be thrown. The below if/else
// caches the body object as a request attribute in the ServerWebExchange
// so if this filter is run more than once (due to more than one route
// using it) we do not try to read the request body multiple times
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put(TEST_ATTRIBUTE, test);
return Mono.just(test);
}
catch (ClassCastException e) {
if (log.isDebugEnabled()) {
log.debug("Predicate test failed because class in predicate "
+ "does not match the cached body object", e);
}
}
return Mono.just(false);
}
else {
return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
(serverHttpRequest) -> ServerRequest
.create(exchange.mutate().request(serverHttpRequest)
.build(), messageReaders)
.bodyToMono(inClass)
.doOnNext(objectValue -> exchange.getAttributes()
.put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
.map(objectValue -> config.getPredicate()
.test(objectValue)));
}
};
}
...
}
? ? ? ? 2、ServerWebExchangeUtils和上下文属性
ServerWebExchangeUtils ?里面存放了很多静态公有的字符串KEY值(?这些字符串KEY的实际值是?org.springframework.cloud.gateway.support.ServerWebExchangeUtils. ?+ 下面任意的静态公有KEY?),这些字符串KEY值一般是用于?ServerWebExchange ?的属性(?Attribute ?,见上文的?ServerWebExchange#getAttributes() ?方法)的KEY,这些属性值都是有特殊的含义,在使用过滤器的时候如果时机适当可以直接取出来使用,下面逐个分析。
PRESERVE_HOST_HEADER_ATTRIBUTE ?:是否保存Host属性,值是布尔值类型,写入位置是?PreserveHostHeaderGatewayFilterFactory ?,使用的位置是?NettyRoutingFilter ?,作用是如果设置为true,HTTP请求头中的Host属性会写到底层Reactor-Netty的请求Header属性中。CLIENT_RESPONSE_ATTR ?:保存底层Reactor-Netty的响应对象,类型是?reactor.netty.http.client.HttpClientResponse ?。CLIENT_RESPONSE_CONN_ATTR ?:保存底层Reactor-Netty的连接对象,类型是?reactor.netty.Connection ?。URI_TEMPLATE_VARIABLES_ATTRIBUTE ?:?PathRoutePredicateFactory ?解析路径参数完成之后,把解析完成后的占位符KEY-路径Path映射存放在?ServerWebExchange ?的属性中,KEY就是?URI_TEMPLATE_VARIABLES_ATTRIBUTE ?。CLIENT_RESPONSE_HEADER_NAMES ?:保存底层Reactor-Netty的响应Header的名称集合。GATEWAY_ROUTE_ATTR ?:用于存放?RoutePredicateHandlerMapping ?中匹配出来的具体的路由(?org.springframework.cloud.gateway.route.Route ?)实例,通过这个路由实例可以得知当前请求会路由到下游哪个服务。GATEWAY_REQUEST_URL_ATTR ?:?java.net.URI ?类型的实例,这个实例代表直接请求或者负载均衡处理之后需要请求到下游服务的真实URI。GATEWAY_ORIGINAL_REQUEST_URL_ATTR ?:?java.net.URI ?类型的实例,需要重写请求URI的时候,保存原始的请求URI。GATEWAY_HANDLER_MAPPER_ATTR ?:保存当前使用的?HandlerMapping ?具体实例的类型简称(一般是字符串"RoutePredicateHandlerMapping")。GATEWAY_SCHEME_PREFIX_ATTR ?:确定目标路由URI中如果存在schemeSpecificPart属性,则保存该URI的scheme在此属性中,路由URI会被重新构造,见?RouteToRequestUrlFilter ?。GATEWAY_PREDICATE_ROUTE_ATTR ?:用于存放?RoutePredicateHandlerMapping ?中匹配出来的具体的路由(?org.springframework.cloud.gateway.route.Route ?)实例的ID。WEIGHT_ATTR ?:实验性功能(此版本还不建议在正式版本使用)存放分组权重相关属性,见?WeightCalculatorWebFilter ?。ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR ?:存放响应Header中的ContentType的值。HYSTRIX_EXECUTION_EXCEPTION_ATTR ?:?Throwable ?的实例,存放的是Hystrix执行异常时候的异常实例,见?HystrixGatewayFilterFactory ?。GATEWAY_ALREADY_ROUTED_ATTR ?:布尔值,用于判断是否已经进行了路由,见?NettyRoutingFilter ?。GATEWAY_ALREADY_PREFIXED_ATTR ?:布尔值,用于判断请求路径是否被添加了前置部分,见?PrefixPathGatewayFilterFactory ?。
解决办法
? ? ? ? 1、在网关全局异常处理逻辑中,对databuffer的内存进行释放
Object object = exchange.getAttribute("cacheRequestBody");
if(object != null){
DataBufferUtils.release((DataBuffer) object);
}
|