Компания использует Spring Gateway, как бизнес-единицу, всегда имела неизлечимую проблему утечки дополнительной памяти. Переняв это у коллеги, я наконец решил эту проблему.
В целом,nettyвне кучи Память Утечки можно добавить, добавив-Dio.netty.leakDetection.level=PARANOID
параметр,Затем перейдите на опрессовку, чтобы проверить, нет ли утечек напечатанной Память.,к сожалению,Мы попробовали, но это не сработало,Никакой печати.
Затем, поскольку версия jdk очень старая, я попытался обновить версию jdk, но проблема все еще сохраняется.
В производственной линии имеется множество кластеров k8s. Шлюз в составе кластера развертывается независимо по определенным бизнес-причинам. По наблюдениям, некоторые шлюзовые сервисы не будут перезапускать OOM в течение месяца, а некоторые даже будут происходить раз в несколько часов (благодаря возможностям k8s это в принципе безразлично для бизнеса).
Это немного интересно. Наблюдая за шлюзом, который часто глючит, я обнаружил, что данные ответа на запросы здесь обычно относительно велики. В это время я начал задаваться вопросом, есть ли проблема утечки памяти в коде, который обрабатывает ответ. шлюз и нижестоящие службы.
Возьмите это и смоделируйте эту ситуацию в своей локальной среде.
Будет добавлено
Наконец, было установлено, что следующий код вызвал утечку памяти вне кучи.
public class Oom extends ServerHttpResponseDecorator {
public Oom(ServerHttpResponse delegate) {
super(delegate);
}
@Override
@NonNull
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
Flux<? extends DataBuffer> flux = Flux.from(body);
return super.writeWith(flux.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = getDelegate().bufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
// Игнорировать другую обработку
return dataBufferFactory.wrap(content);
}));
}
}
На первый взгляд,Кажется, нет никаких проблем,Вновь созданный буфер также перерабатывается. В чем проблема?,flux.buffer()
Функция оператора буфера — считывать все буферы данных Flux и сохранять их в список. Исходный код ключа выглядит следующим образом.
reactor.core.publisher.FluxBuffer.BufferExactSubscriber#onNext
@Override
public void onNext(T t) {
if (done) {
Operators.onNextDropped(t, actual.currentContext());
return;
}
C b = buffer;
if (b == null) {
try {
b = Objects.requireNonNull(bufferSupplier.get(),
"The bufferSupplier returned a null buffer");
}
catch (Throwable e) {
Context ctx = actual.currentContext();
onError(Operators.onOperatorError(s, e, t, ctx));
Operators.onDiscard(t, ctx); //this is in no buffer
return;
}
buffer = b;
}
// b — это список, и потребленный t будет добавлен к b
b.add(t);
if (b.size() == size) {
buffer = null;
actual.onNext(b);
}
}
Обычно,databufferбудет побитdataBufferFactory.join
переработка,но,В случае отмены запроса или ошибки,не будет выполненоmap
В этом методе,В результате добавление в список
Буфер данных не может быть переработан.
решить тоже очень просто, продолжайте смотреть исходник
reactor.core.publisher.FluxBuffer.BufferExactSubscriber#onError
@Override
public void onError(Throwable t) {
if (done) {
Operators.onErrorDropped(t, actual.currentContext());
return;
}
done = true;
actual.onError(t);
Operators.onDiscardMultiple(buffer, actual.currentContext());
}
@Override
public void cancel() {
s.cancel();
Operators.onDiscardMultiple(buffer, this.ctx);
}
В случае отмены или ошибки,воляDatabufferобратный вызов наonDiscard
событие,Поэтому измените код на следующий, и все будет решено
@Override
@NonNull
public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
Flux<? extends DataBuffer> flux = Flux.from(body);
return super.writeWith(flux.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = getDelegate().bufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
DataBufferUtils.release(join);
// Игнорировать другую обработку
return dataBufferFactory.wrap(content);
})
// Отслеживайте события сброса и перезапускайте буфер данных.
.doOnDiscard(DataBuffer.class, DataBufferUtils::release));
}
Приведенный выше код на самом деле является проблемой oom.,но Гораздо важнее понять, почему всеbodyзачитать,Если вы ведете журналирование,Должно быть необходимо усечь содержимое тела,Потому что размер тела неконтролируем.
Это необходимо оптимизировать с точки зрения бизнеса.