Утечка памяти в TCP-клиенте Camel Netty при использовании строк с разрывами строк Windows (CR LF) - PullRequest
4 голосов
/ 13 мая 2019

Мои текстовые строки, использующие клиента Camel netty tcp, имеют утечку памяти, но только в том случае, если строки тестовых данных заканчиваются разрывом строки в Windows (CR LF).У меня не возникло проблем с разрывами строк в Unix (LF).

Я провел короткий тест, чтобы продемонстрировать проблему, имитирующую tcp-сервер, непрерывно посылающий строки тестовых данных.

С разрывами строк в Unix (LF)в тестовых данных я вижу пропускную способность около 3500 сообщений в секунду и стабильное использование оперативной памяти 180 МБ.Никаких проблем.

При разрыве строки в Windows (CR LF) в тестовых данных я вижу пропускную способность, начинающуюся с 380 000 (вау!) Сообщений в секунду, пока не достигнет моего предела кучи -Xmx4G примерно через 30 секунд и, вероятно, значительно замедлитсяиз-за чрезмерного GC;если получить больше кучи, она будет стабильно расти, пока не достигнет этого предела (пробовал с -Xmx20G).

Единственное отличие - это действительно разрыв строки в моих тестовых данных ... Я что-то здесь упускаю?

Использование Camel 2.24.0 (который использует netty 4.1.32-Final) в Linux с OpenJDK 1.8.0_192.Проблема также возникает с последним netty 4.1.36.Final.Также происходит с OpenJ9 JVM, поэтому, похоже, не зависит от JVM.

public abstract class MyRouteBuilderTestBase extends CamelTestSupport {
    private final int nettyPort = AvailablePortFinder.getNextAvailable();

    private ServerSocket serverSocket;
    private Socket clientSocket;
    private PrintWriter out;

    @Override
    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
                from("netty4:tcp://localhost:" + nettyPort + "?clientMode=true&textline=true&sync=false")
                    .to("log:throughput?level=INFO&groupInterval=10000&groupActiveOnly=false");
            }
        };
    }

    protected void startServerStub(String testdata) throws Exception {
        serverSocket = new ServerSocket(nettyPort);
        clientSocket = serverSocket.accept();
        out = new PrintWriter(clientSocket.getOutputStream(), true);
        for (;;) {
            out.print(testdata);
        }
    }

    @After
    public void after() throws Exception {
        if (out != null) out.close();
        if (clientSocket != null) clientSocket.close();
        if (serverSocket != null) serverSocket.close();
    }
}

public class MyRouteBuilderTestUnixLineBreaks extends MyRouteBuilderTestBase {
    @Test
    public void testUnixLineBreaks() throws Exception {
        startServerStub("my test data\n");  // Unix LF
    }
}

public class MyRouteBuilderTestWindowsLineBreaks extends MyRouteBuilderTestBase {
    @Test
    public void testWindowsLineBreaks() throws Exception {
        startServerStub("my test data\r\n");  // Windows CR LF
    }
}

1 Ответ

4 голосов
/ 14 мая 2019

Анализ дампа кучи показал, что память выделяется одним экземпляром io.netty.util.concurrent.DefaultEventExecutor , который использует LinkedBlockingQueue с внутренним неограниченным размером.Эта очередь растет бесконечно под нагрузкой, вызывая проблему.

DefaultEventExecutor создается Camel из-за параметра usingExecutorService , который по умолчанию имеет значение true (возможно, не является хорошим выбором).Установка usingExecutorService = false заставляет Netty использовать свой цикл обработки событий вместо исполнителя, который работает намного лучше.

Теперь я получаю 600.000 сообщений в секунду с пропускной способностью с данными, используя линию Windowsперерывы (CR NL) с устойчивым использованием оперативной памяти около 200 МБ (-Xmx500M).Хорошо.

Хотя с данными, использующими разрывы строк Unix (NL), пропускная способность составляет всего около 6,500 сообщений в секунду, на два порядка медленнее, что все еще вызывает недоумение.

Причина в том, что Camel создает свой собственный org.apache.camel.component.netty4.codec.DelimiterBasedFrameDecoder класс путем подкласса Netty io.netty.handler.codec.DelimiterBasedFrameDecoder - Я не знаю почему, так как класс Кэмел не добавляет никакой функциональности.Но путем создания подклассов Camel предотвращает определенную оптимизацию внутри DelimiterBasedFrameDecoder , который переключается на io.netty.handler.codec.LineBasedFrameDecoder внутренне, но только если не является подклассом.

Чтобы преодолеть это, мне нужно было явно объявить декодер и кодеры, используя вместо этого классы Netty, в дополнение к настройке usingExecutorService = false.

Теперь я получаю пропускную способность 600 000 сообщений в секунду с данными, также используя разрывы строк Unix (NL) иувидеть устойчивое использование оперативной памяти около 200 МБ.Это выглядит намного лучше.

public abstract class MyRouteBuilderTestBase extends CamelTestSupport {
    private final int nettyPort = AvailablePortFinder.getNextAvailable();

    private ServerSocket serverSocket;
    private Socket clientSocket;
    private PrintWriter out;

    @Override
    protected JndiRegistry createRegistry() throws Exception {
        JndiRegistry registry = super.createRegistry();

        List<ChannelHandler> decoders = new ArrayList<>();
        DefaultChannelHandlerFactory decoderTextLine = new DefaultChannelHandlerFactory() {
            @Override
            public ChannelHandler newChannelHandler() {
                return new io.netty.handler.codec.DelimiterBasedFrameDecoder(1024, true, Delimiters.lineDelimiter());
                // Works too:
                // return new LineBasedFrameDecoder(1024, true, true);
            }
        };
        decoders.add(decoderTextLine);
        ShareableChannelHandlerFactory decoderStr = new ShareableChannelHandlerFactory(new StringDecoder(CharsetUtil.US_ASCII));
        decoders.add(decoderStr);
        registry.bind("decoders", decoders);

        List<ChannelHandler> encoders = new ArrayList<>();
        ShareableChannelHandlerFactory encoderStr = new ShareableChannelHandlerFactory(new StringEncoder(CharsetUtil.US_ASCII));
        encoders.add(encoderStr);
        registry.bind("encoders", encoders);

        return registry;
    }

    @Override
    protected RouteBuilder createRouteBuilder() {
        return new RouteBuilder() {
            public void configure() {
                from("netty4:tcp://localhost:" + nettyPort + "?clientMode=true&textline=true&sync=false&usingExecutorService=false&encoders=#encoders&decoders=#decoders")
                .to("log:throughput?level=INFO&groupInterval=10000&groupActiveOnly=false");
            }
        };
    }

    protected void startServerStub(String testdata) throws Exception {
        serverSocket = new ServerSocket(nettyPort);
        clientSocket = serverSocket.accept();
        out = new PrintWriter(clientSocket.getOutputStream(), true);
        for (;;) {
            out.print(testdata);
        }
    }

    @After
    public void after() throws Exception {
        if (out != null) out.close();
        if (clientSocket != null) clientSocket.close();
        if (serverSocket != null) serverSocket.close();
    }
}

Обновление : проблема использования памяти не в утечке памяти (и я сожалею, что сформулировал свой вопрос таким образом), а в буферизации.Пожалуйста, ознакомьтесь с комментариями к этому ответу пользователей Bedla и Claus Ibsen, чтобы получить хорошее представление о последствиях решения, изложенного выше.Пожалуйста, обратитесь к CAMEL-13527

...