Рекомендации по унарным вызовам с высокой пропускной способностью и малой задержкой в ​​gRPC

Мне нужны рекомендации по увеличению пропускной способности и минимизации задержки для унарных вызовов gRPC. Мне нужно добиться около 20 000 QPS, ‹50 мс каждое. На умеренном оборудовании (4-ядерный ЦП) я смог достичь только около 15 000 запросов в секунду при средней задержке 200 мс. Я использую клиент и сервер Java. Сервер ничего не делает, кроме как возвращает ответ. Клиент отправляет несколько одновременных запросов, используя асинхронную заглушку. Количество одновременных запросов ограничено. ЦП остается в диапазоне ~ 80%. Для сравнения, используя Apache Kafka, я могу достичь гораздо более высокой пропускной способности (100 тысяч запросов в секунду), а также задержки в диапазоне 10 мс.


person Daniel Nitzan    schedule 17.10.2019    source источник
comment
набор тестов gRPC показывает задержку 195 мкс (для одиночного RPC) и 245 тыс. запросов в секунду для 8-ядерного GCE. ВМ с использованием TLS. Есть много факторов, которые могут повлиять на результаты, но самые основные - это тип теста, количество разминки и сеть. Ваши цифры настолько далеки от ожидаемых, что я обычно предполагаю, что это связано с тем, что не было периода прогрева, чтобы дать время JIT оптимизировать код, но это стреляет в темноте.   -  person Eric Anderson    schedule 17.10.2019
comment
Спасибо @EricAnderson. Теперь я использую клиент / сервер на экземплярах AWS r4.2xlarge. Я получаю намного лучшую пропускную способность - 50 КБ (после прогрева) с низкими задержками, как и ожидалось. Клиент однопоточный. Оба экземпляра клиент / сервер имеют ~ 200% ЦП (используются два потока). Как я могу выжать больше производительности?   -  person Daniel Nitzan    schedule 17.10.2019


Ответы (1)


Если вы используете grpc-java 1.21 или новее и grpc-netty-shaded, вы уже должны использовать транспорт Netty Epoll. Если вы используете grpc-netty, добавьте зависимость времени выполнения от io.netty:netty-transport-native-epoll (правильную версию можно найти, просмотрев файл pom.xml grpc-netty или таблица версий в SECURITY.md).

Исполнителем по умолчанию для обратных вызовов является «кэшированный пул потоков». Если вы не блокируете (или знаете пределы блокировки), указание пула потоков фиксированного размера может повысить производительность. Вы можете попробовать как Executors.newFixedThreadPool, так и ForkJoinPool; мы видели, что «оптимальный» выбор варьируется в зависимости от рабочей нагрузки. Вы указываете собственного исполнителя через ServerBuilder.executor() и ManagedChannelBuilder.executor().

Если у вас высокая пропускная способность (~ Гбит / с + на клиента с TLS; выше, если используется открытый текст), использование нескольких каналов может повысить производительность за счет использования нескольких соединений TCP. Каждое TCP-соединение закреплено за потоком, поэтому наличие большего количества TCP-соединений позволяет использовать больше потоков. Вы можете создать несколько каналов, а затем выполнять циклический переход по ним; выбор другого для каждого RPC. Обратите внимание, что вы можете легко реализовать интерфейс Channel, чтобы «скрыть» эту сложность от остальной части вашего приложения. Похоже, что это даст вам именно большой выигрыш, но я ставлю это последним, потому что обычно в этом нет необходимости.

person Eric Anderson    schedule 24.10.2019
comment
Вы можете рассмотреть этот ChannelPool в качестве примера. Обратите внимание, что он длиннее, чем это необходимо, потому что он реализует ManagedChannel вместо Channel. - person Eric Anderson; 25.10.2019