Я пытаюсь отладить приложение 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 или чего-то ещецепочка сети вызывает эти зависания.Но я не знаю достаточно, чтобы предложить какие-то конкретные предположения, а тем более подтвердить их.