后端服务oom
问题描述
服务内部调用过程中会打印调用日志,日志内容过大导致oom,内存参数调大后还是不起作用,监控发现只用了4G
Caused by: java.lang.OutOfMemoryError: null
2025/8/1 21:03:17 at com.alibaba.fastjson2.JSONWriterUTF16.ensureCapacity(JSONWriterUTF16.java:1562)
2025/8/1 21:03:17 at com.alibaba.fastjson2.JSONWriterUTF16.writeStringEscape(JSONWriterUTF16.java:364)
2025/8/1 21:03:17 at com.alibaba.fastjson2.JSONWriterUTF16JDK8UF.writeString(JSONWriterUTF16JDK8UF.java:56)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.ObjectWriterImplMap.write(ObjectWriterImplMap.java:481)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.ObjectWriterImplList.write(ObjectWriterImplList.java:371)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.ObjectWriterImplMap.write(ObjectWriterImplMap.java:550)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.FieldWriterList.writeList(FieldWriterList.java:253)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.OWG_6_3_PageResponse.write(Unknown Source)
2025/8/1 21:03:17 at com.alibaba.fastjson2.writer.OWG_1_4_R.write(Unknown Source)
2025/8/1 21:03:17 at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:2860)
2025/8/1 21:03:17 at com.sensetime.framework.feign.core.FeignResponseLogger.decode(FeignResponseLogger.java:47)
2025/8/1 21:03:17 at feign.InvocationContext.proceed(InvocationContext.java:36)
2025/8/1 21:03:17 at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:116)
2025/8/1 21:03:17 at feign.AsyncResponseHandler.handleResponse(AsyncResponseHandler.java:89)
2025/8/1 21:03:17 at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:141)
2025/8/1 21:03:17 at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:91)
2025/8/1 21:03:17 at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
2025/8/1 21:03:17 at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory$1.proceed(FeignCachingInvocationHandlerFactory.java:66)
2025/8/1 21:03:17 at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:54)
2025/8/1 21:03:17 at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:351)
2025/8/1 21:03:17 at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64)
2025/8/1 21:03:17 at org.springframework.cloud.openfeign.FeignCachingInvocationHandlerFactory.lambda$create$1(FeignCachingInvocationHandlerFactory.java:53)
2025/8/1 21:03:17 at com.sun.proxy.$Proxy140.queryConversationById(Unknown Source)
问题定位
由于调用别的服务返回了太大的数据,然后在用 Fastjson2 做序列化时内存爆了。
增加了黑名单接口,只有这个接口error才会打印日志
问题解决!!!
@Slf4j
public class FeignResponseLogger implements Decoder {
private final List<PropertyFilter> propertyFilters;
final Decoder delegate;
public FeignResponseLogger(List<PropertyFilter> propertyFilters, Decoder delegate) {
Objects.requireNonNull(delegate, "Decoder must not be null. ");
this.delegate = delegate;
this.propertyFilters = propertyFilters;
}
// 黑名单接口(只在非 200 时打印)
private static final List<String> URL_ONLY_ERROR_LOGGING = Arrays.asList(
"/xxx/xxx/xxx"
);
private boolean isOnlyErrorLogUrl(String url) {
return URL_ONLY_ERROR_LOGGING.stream().anyMatch(url::contains);
}
@Override
public Object decode(Response response, Type type) throws IOException, DecodeException, FeignException {
try {
Object decodedObject = delegate.decode(response, type);
String url = response.request().url();
int status = response.status();
boolean isOnlyLogOnError = isOnlyErrorLogUrl(url);
// 判断是否需要打印日志
if (!isOnlyLogOnError || status != 200) {
String decodedBody;
try {
if (decodedObject instanceof ResponseEntity) {
decodedBody = "Http response";
} else {
decodedBody = JSON.toJSONString(decodedObject, propertyFilters.toArray(new PropertyFilter[0]));
}
} catch (Exception e) {
log.warn("json serialize error|msg={}", e.getMessage(), e);
decodedBody = "parse error";
}
Long costTime = FeignRequestLogger.getCurrentRequestContext()
.map(FeignRequestLogger.Context::getStartTime)
.map(startTime -> System.currentTimeMillis() - startTime)
.orElse(null);
String logPrefix = (status == 200 ? "info" : "error");
logAtLevel(logPrefix, "feign request {}|cost={}|url={}|status={}|body={}",
logPrefix, costTime, url, status,
decodedBody.length() > 100 ? decodedBody.substring(0, 100) + "..." : decodedBody);
}
return decodedObject;
} finally {
FeignRequestLogger.clearCurrentRequestContext();
}
}
private void logAtLevel(String level, String format, Object... args) {
switch (level) {
case "info":
log.info(format, args);
break;
case "warn":
log.warn(format, args);
break;
case "error":
log.error(format, args);
break;
default:
log.debug(format, args);
break;
}
}
}