Проблема с производительностью: асинхронный gRPC с Gunicorn + Tornado

Фон:

Мы пытаемся перенести наш API-шлюз с REST на gRPC. API-шлюз будет использоваться серверной командой с помощью REST, а обмен данными между API-шлюзом и микросервисом будет осуществляться с помощью gRPC. Наш API-шлюз построен с использованием Tornado Python Framework, Gunicorn и использует tornado.curl_httpclient.CurlAsyncHTTPClient для включения Async / Future для каждой конечной точки. Каждая конечная точка будет вызывать микросервисы с использованием Unary RPC, а заглушка gRPC вернет Future.

Поэтому, прежде чем полностью перейти на gRPC, мы пытаемся сравнить производительность gRPC и REST. Вот что вам может понадобиться:

  1. У нас есть 3 конечные точки для тестирования. /0, /1 и /2 с однострочными данными. Размер полезной нагрузки составляет 100 КБ, 1 МБ и 4 МБ. Эти сообщения уже созданы при запуске экземпляра, поэтому конечной точке нужно только его получить.
  2. Параллелизм = 1, 4, 10 для каждой конечной точки.
  3. Максимальное количество работников пула потоков gRPC = 1, а работник Gunicorn = 16.
  4. Мы используем APIB для нагрузочного тестирования.
  5. Все нагрузочные тесты выполняются с использованием экземпляра виртуальной машины GCP. Технические характеристики машины: Intel Broadwell, n1-standard-1 (1 виртуальный ЦП, 3,75 ГБ памяти), ОС: Debian 9
  6. Код имеет схожую структуру и бизнес-логику.

Вот результат:  введите описание изображения здесь

Вывод: чем выше Concurrency и размер полезной нагрузки, тем медленнее становится gRPC и, в конечном итоге, медленнее, чем REST.

Вопрос:

  1. Может ли gRPC обрабатывать большой размер полезной нагрузки и большой параллелизм с помощью Unary Call по сравнению с REST?
  2. Есть ли способ сделать так, чтобы gRPC стал быстрее, чем REST?
  3. Есть ли какая-то фундаментальная причина, которую я упустил?

Вот несколько способов, которые я пробовал:

  1. GZIP Сжатие из grpcio. В результате он стал медленнее, чем раньше.
  2. Использование параметров GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS и GRPC_ARG_KEEPALIVE_TIMEOUT_MS в конфигурации заглушки и сервера. По производительности изменений нет.
  3. Измените максимальное количество рабочих серверов gRPC на 10000. Результат: без изменений производительности.
  4. Измените Gunicorn Worker на 1. Результат: без изменений производительности.

Как я не пробовал:

  1. Использование Stream RPC

Любая помощь объявляется. Спасибо.


person Kaslie    schedule 26.06.2020    source источник


Ответы (1)


Может ли gRPC обрабатывать большой размер полезной нагрузки и большой параллелизм с помощью Unary Call по сравнению с REST?

Да, привязки gRPC Python используются в производственной среде для обслуживания запросов размером до нескольких гигабайт на скорости.

Есть ли способ сделать так, чтобы gRPC стал быстрее, чем REST?

Я считаю, что ваша проблема, скорее всего, следующая:

Каждая конечная точка будет вызывать микросервисы с использованием Unary RPC, а заглушка gRPC вернет Future.

Каждый раз, когда вы используете будущий API в привязках Python, создается новый поток для обслуживания этого запроса. Как вы знаете, Python имеет глобальную блокировку интерпретатора, поэтому, хотя процесс может иметь много потоков, только один из них может обращаться к объектам Python одновременно. Кроме того, чем больше потоков соревнуются в GIL, тем больше будет замедлений из-за синхронизации.

Чтобы избежать этого, вы можете использовать только синхронные части gRPC Python API или переключиться на привязки AsyncIO, которые были разработаны для решения именно этой проблемы.

person Richard Belleville    schedule 11.01.2021
comment
Я немного запутался. Я думаю, что future API в Tornado должна быть сопрограммой, а не потоком. Я упустил ключевой момент? - person Bingoabs; 09.04.2021
comment
Вопрос касается gRPC Python, чей будущий API не использует фьючерсы Tornado. - person Richard Belleville; 10.04.2021