HTTP-запросы, выполняемые в параллельных рабочих потоках, иногда задерживаются и прекращаются - PullRequest
0 голосов
/ 24 сентября 2019

Я пытаюсь отладить приложение Flask / Gunicorn, работающее с Python 3.6.3 на RHEL 7.7, часть которого отправляет запросы (запросы GET) бэкэнд-API, работающему на нескольких различных серверах / URL-адресах.Поскольку мы ограничены IO, каждый из этих запросов выполняется параллельно в своем собственном потоке.Проблема, с которой я сталкиваюсь, заключается в том, что эти запросы иногда занимают слишком много времени (на несколько порядков) для выполнения и даже неожиданно завершаются.Вот значительно упрощенная версия соответствующего кода:

from concurrent.futures import ThreadPoolExecutor, as_completed

import requests # Version 2.22.0, OpenSSL 1.0.2k-fips  26 Jan 2017

def query(url):
    response = requests.get(url)
    return response.json()

url_list = [url1, url2, ...]

executor = ThreadPoolExecutor(max_workers=len(url_list))
request_objects = list()

# Schedule queries to run in separate threads
for url in url_list:
    request_objects.append(executor.submit(query, url=url))

# Join all threads
list(as_completed(request_objects))

Обычно это работает, как ожидалось, и все в порядке.Но при запросе request.get время от времени возникают различные ошибки, все из которых связаны с внезапным и неожиданным разрывом соединения.Я не могу воспроизвести эти ошибки каким-либо детерминированным способом.Они включают в себя:

  • reports.exceptions.ConnectionError: ('Соединение прервано.', RemoteDisconnected ('Соединение с удаленным концом закрыто без ответа',)) *
  • request.exceptions.SSLError:EOF произошел с нарушением протокола (_ssl.c: 777)
  • reports.exceptions.ChunkedEncodingError: ('Соединение разорвано: IncompleteRead (чтение 0 байтов, ожидается еще 2)', IncompleteRead (чтение 0 байтов, еще 2ожидается))
  • запросы. исключения.SSLError: Соединение TLS / SSL было закрыто (EOF) (_ssl.c: 777)

После выполнения детективной работы в журналах ошибок, проблема стала более интересной.Эти ошибки завершения, как правило, происходят короткими пакетами, обычно все в одном рабочем процессе Gunicorn, когда несколько автоматических запросов к нашему API поступают (от Telegraf) примерно в одно и то же время.Но длительность запроса (длительность вызова блоков request.get) особенно интересна.Для одной группы ошибок длительность неудачных запросов была:

  • 205.52187418937683
  • 205.52265071868896
  • 205.6381540298462

и продолжительностиуспешных запросов было:

  • 2.622708320617676
  • 2.64787220954895
  • 2.939096212387085
  • 2.9614670276641846
  • 2.970625400543238 * 139 * 3 9706254003231* тысяча тридцать-девять *
  • 3,0918056964874268 * тысяча сорок одна * * 1 042 * +204,15788435935974
  • +204,15829873085022
  • +204,17250561714172
  • 204,1836473941803
  • +204,19148349761963
  • 204.70406007766724
  • 205.1262927055359
  • 205.12787008285522
  • 205.*
  • 205.57158827781677
  • 205.58390355110168
  • 205.78088784217834
  • 205.80147290229797
  • 205.8019723892212
  • 205.80248093605042
  • 207.37307357788086
  • 207.37479543685913
  • 207.3749725818634
  • 207.380 * 107 * 10 * * * * * * * * * * * * * * * 2071091 * Обратите внимание на кластеризацию неудачных длительностей около 205 секунд (то есть все три соединения были разорваны примерно в одно и то же время) и резкий скачок блокирования запросов GET на 3 секунды до 200+ секунд.Из журналов доступа внутреннего API я подтвердил, что для обработки этих запросов не требуется около 200 секунд - по крайней мере, не так много из них.Время запроса в несколько секунд приблизительно отражает, сколько времени на самом деле требуется бэкэнду для обработки запроса и возврата данных, но более длительное время (неудачное и успешное) - нет.Что-то задерживает получение ответа и, в некоторых случаях, разрывает соединение даже после того, как бэкэнд-API вернул действительные данные.

    Исключив возможность того, что бэкэнд-API перегружен и фактически требует много времени для ответа (что может привести к другой, известной ошибке), я подозреваю, что что-то вроде ОС, прокси, SSL или чего-то ещецепочка сети вызывает эти зависания.Но я не знаю достаточно, чтобы предложить какие-то конкретные предположения, а тем более подтвердить их.

...