Вопрос или проблема
Мне нужно передать изображения объемом 432 МБ между двумя компьютерами с Linux, которые подключены к одной быстрой сети. scp
передает каждое изображение менее чем за 1 секунду. Код ниже, использующий HTTP, занимает от 3 до 8 секунд на изображение.
На клиентских и серверных компьютерах нет другой загрузки, кроме этого процесса передачи. Использование ЦП и диска на обоих компьютерах низкое. HTTP-сервер не обрабатывает других запросов. Передача файлов по HTTP и scp
проходит успешно. Ошибок нет, и я не получал сообщений о тайм-ауте.
Я понимаю, что HTTP имеет больший оверхед, чем scp
, но я не ожидал, что это увеличит время передачи более чем в 3 раза.
Я потратил более часа безуспешно ища причины этой разницы в производительности. Чат-боты не помогают.
Можете ли вы сообщить, ожидаемо ли это и есть ли способы сделать передачу по HTTP более близкой к производительности scp
.
Код, использованный для процесса HTTP:
client.py
#!/usr/bin/python3
import argparse
import requests
import sys, os
# Предопределенные переменные
SERVER = 'ua02861p.abbvienet.com'
PORT = 8443
def upload_image(image_path, url, verify_ssl):
with open(image_path, 'rb') as img:
files = {'file': img}
response = requests.post(url, files=files, verify=verify_ssl)
try:
response.raise_for_status()
except requests.exceptions.HTTPError as e:
print(f"HTTP ошибка: {e}")
return response.json()
return response.json()
def main():
parser = argparse.ArgumentParser(description="Загрузка изображений на сервер FastAPI.")
parser.add_argument('-s', '--server', type=str, default=SERVER, help=f'Адрес сервера (имя хоста или IP) [по умолчанию: {SERVER}]')
parser.add_argument('-p', '--port', type=int, default=PORT, help=f'Номер порта [по умолчанию: {PORT}]')
parser.add_argument('--use-http', action='store_true', default=False, help='Использовать HTTPS вместо HTTP [по умолчанию: True]')
parser.add_argument('--verify-ssl', action='store_true', default=False, help='Включить проверку SSL-сертификата [по умолчанию: False]')
parser.add_argument('filenames', type=str, nargs="+", help='Один или несколько путей к файлам изображений')
args = parser.parse_args()
protocol = "http" if args.use_http else "https"
server = args.server
port = args.port
filenames = args.filenames
verify_ssl = args.verify_ssl
url = f"{protocol}://{server}:{port}/upload-image/"
for image_path in filenames:
print(f"Загрузка {image_path} на {url} ", end = '')
''' перенаправление stderr, чтобы убрать раздражающее предупреждение о проверке ssl '''
stderr = sys.stderr
sys.stderr = open(os.devnull,'w')
response = upload_image(image_path, url, verify_ssl)
sys.stderr = stderr
print(f"({response['info']})")
if __name__ == "__main__":
main()
server.py
#!/usr/bin/python3
from fastapi import FastAPI, File, UploadFile, Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from pathlib import Path
import uvicorn
import requests
import os
import sys
# По умолчанию
# SERVER = 'ua02861p.abbvienet.com' #
SERVER = '0.0.0.0' # Слушать все интерфейсы
PORT = 8443
def fpath(file_path):
if os.path.exists(file_path):
#print(f"Файл '{file_path}' существует.")
return file_path
else:
# Добавить директорию скрипта к пути файла
script_directory= os.path.dirname(sys.argv[0])
#print (f'script_directory = {type (script_directory)}')
modified_file_path = os.path.join(script_directory, file_path)
#print(f"Измененный путь файла '{modified_file_path}' существует. {type (modified_file_path)}")
if os.path.exists(modified_file_path):
return modified_file_path
else:
raise FileNotFoundError(f"{file_path} не существует.")
app = FastAPI()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)
@app.post("/upload-image/")
async def upload_image(file: UploadFile = File(...)):
file_location = f"uploaded_images/{file.filename}"
Path("uploaded_images").mkdir(parents=True, exist_ok=True)
with open(file_location, "wb") as buffer:
buffer.write(await file.read())
return {"info": "файл успешно загружен", "filename": file.filename}
def get_module_name():
return os.path.splitext(os.path.basename(sys.argv[0]))[0]
def start_uvicorn():
import argparse
parser = argparse.ArgumentParser(description="Запуск сервера FastAPI с uvicorn.")
parser.add_argument('--host', type=str, default=SERVER, help='Хост сервера [по умолчанию: {SERVER}')
parser.add_argument('--port', type=int, default=PORT, help='Порт сервера [по умолчанию: {PORT}]')
parser.add_argument('--log-level', type=str, default="debug", help='Уровень логирования [по умолчанию: debug]')
parser.add_argument('--ssl-keyfile', type=str, default="key.pem", help='Путь к файлу ключа SSL [по умолчанию: key.pem]')
parser.add_argument('--ssl-certfile', type=str, default="cert.pem", help='Путь к файлу сертификата SSL [по умолчанию: cert.pem]')
args = parser.parse_args()
keyfile=fpath(args.ssl_keyfile)
certfile=fpath(args.ssl_certfile)
# Печать отладочной информации о файлах SSL
if keyfile:
print(f"Использование файла ключа SSL: {keyfile}")
if not os.path.exists(keyfile):
raise FileNotFoundError(f"Файл ключа SSL не найден: {keyfile}")
else:
print("Файл ключа SSL не предоставлен")
if certfile:
print(f"Использование файла сертификата SSL: {certfile}")
if not os.path.exists(certfile):
raise FileNotFoundError(f"Файл сертификата SSL не найден: {certfile}")
else:
print("Файл сертификата SSL не предоставлен")
module_name = get_module_name()
uvicorn.run(
f"{module_name}:app",
host=args.host,
port=args.port,
log_level=args.log_level,
ssl_keyfile=keyfile,
ssl_certfile=certfile
)
if __name__ == "__main__":
start_uvicorn()
Ответ или решение
Передача больших файлов может демонстрировать различные показатели производительности в зависимости от использования протоколов. В вашем случае, передача файлов через HTTP происходит медленнее, чем через SCP, что может объясняться несколькими факторами.
Причины различий в производительности:
-
Проверка целостности данных: Протокол SCP часто использует механизм проверки целостности данных, который более оптимизирован по сравнению с HTTP. При передаче HTTP сервер может выполнять дополнительные проверки, что увеличивает задержку.
-
Структура протокола: HTTP работает на основе запросов и ответов, что подразумевает большее количество накладных расходов, таких как установка соединения, обработка заголовков и аутентификация. SCP, наоборот, использует SSH для создания единого канала передачи, что позволяет передавать данные более эффективно.
-
Параметры сервера: Ваша реализация сервера FastAPI, возможно, не оптимизирована для отправки больших файлов. Обратите внимание на настройки
uvicorn
, который вы используете для запуска FastAPI. -
Размеры буферов и потока: SCP может использовать более крупные буферы для передачи данных, что может значительно увеличить скорость передачи. При использовании HTTP для передачи файлов, данные могут обрабатываться в меньших порциях, что приводит к большему количеству операций ввода-вывода.
-
Настройки сети: Проверьте, есть ли какие-либо ограничения или настройки в вашей сетевой инфраструктуре, которые могут повлиять на скорость передачи по протоколу HTTP.
Рекомендации для повышения производительности HTTP:
-
Используйте
Streaming
: Вместо того чтобы загружать файл целиком в память перед отправкой, используйте потоковую передачу. FastAPI поддерживает потоковую передачу, что может помочь сократить время передачи.Измените
upload_image
вserver.py
следующим образом:@app.post("/upload-image/") async def upload_image(file: UploadFile = File(...)): file_location = f"uploaded_images/{file.filename}" Path("uploaded_images").mkdir(parents=True, exist_ok=True) with open(file_location, "wb") as buffer: while content := await file.read(1024 * 1024): # Чтение файла по частям buffer.write(content) return {"info": "file uploaded successfully", "filename": file.filename}
-
Настройка
uvicorn
: Если вы используете SSL, убедитесь, что это не создает дополнительной задержки. Также рассмотрите возможность использования более оптимальных значений для--workers
, чтобы использовать возможности многопоточности и многопроцессности (зависит от вашей архитектуры серверов). -
Изменения в клиенте: Как и в серверной части, попробуйте реализовать потоковую отправку на клиенте для более эффективной передачи данных.
-
Настройки HTTP-сервера: Если вы используете дополнительные модули или прокси-серверы (например, Nginx или Apache), убедитесь, что их конфигурация не ограничивает скорость передачи.
Заключение
Ваша наблюдаемая разница в скорости передачи данных через HTTP и SCP вполне объяснима. Учитывая рекомендации, вы сможете значительно повысить производительность передачи через HTTP и приблизиться к скорости, которую демонстрирует SCP. Если все предложенные шаги не приведут к ожидаемым результатам, возможно, стоит продолжить использовать SCP для задач, связанных с передачей больших файлов, до тех пор, пока не будет найдено более оптимальное решение.