HAProxy 1.6: HTTP/2 для публичных API и минус 30% времени загрузки на мобильных клиентах
Обновили балансировщики до HAProxy 1.6 с нативным HTTP/2 и улучшенным SSL offloading. Мобильные клиенты почувствовали разницу - мультиплексирование убирает очереди запросов.
HAProxy 1.6 выпущен с нативной поддержкой HTTP/2 и улучшенным SSL offloading
HAProxy 1.6 вышел на прошлой неделе, и мы его уже раскатили на нескольких клиентских балансировщиках. Главное в релизе - нативная поддержка HTTP/2 на фронтенде и переработанный SSL offloading. Для нас это был повод закрыть задачу, которую откладывали месяца три: перевести публичные API одного из клиентов на HTTP/2 без дополнительного слоя терминации.
Зачем вообще HTTP/2 для API
У клиента - мобильное приложение, которое при запуске делает серию коротких запросов к API: профиль пользователя, список уведомлений, текущий баланс, настройки. Запросы независимые, идут последовательно - не потому что так задумано архитектурно, а потому что HTTP/1.1 создаёт очередь на одном соединении, и клиент так или иначе упирается в head-of-line blocking.
С HTTP/2 все эти запросы идут параллельно по одному TCP-соединению за счёт мультиплексирования. На мобильных сетях, где установка каждого нового TCP-соединения - это заметная задержка (handshake + TLS handshake), это ощутимо. По нашим замерам - время от запуска приложения до готового экрана сократилось примерно на 30%.
Оговорюсь сразу: цифра - это конкретный кейс, конкретное приложение, конкретная аудитория с определённым профилем сети. Бенчмарк «HTTP/2 быстрее HTTP/1.1 на X%» в вакууме - бессмысленный. Важно понять, есть ли у вас те самые условия: много мелких запросов, мобильная аудитория, задержки на установку соединений.
Почему HAProxy, а не nginx
Мы уже писали про nginx как TLS-терминатор - он там хорошо справляется. Но на этом клиенте HAProxy уже стоял как балансировщик перед nginx-бэкендами, и добавлять ещё один слой ради HTTP/2 не хотелось. HAProxy 1.6 решил вопрос: теперь он сам может терминировать HTTP/2 на фронтенде и проксировать дальше в HTTP/1.1.
Бэкенды при этом менять не нужно - они не знают про HTTP/2 и не должны. HAProxy транслирует. Это принципиально с точки зрения раскатки: обновили балансировщик, включили alpn h2, приложение не трогаем.
Что пришлось поменять в конфиге
Конфиг HAProxy 1.5 в целом совместим с 1.6, но для HTTP/2 нужно явно включить поддержку при сборке с USE_OPENSSL=1 и указать протоколы в bind. Вот что изменилось в нашем фронтенд-блоке:
frontend public_api
bind *:443 ssl crt /etc/haproxy/certs/api.pem alpn h2,http/1.1
mode http
option forwardfor
option http-server-close
default_backend api_servers
backend api_servers
balance roundrobin
option http-server-close
server app1 10.0.1.10:8080 check
server app2 10.0.1.11:8080 check
alpn h2,http/1.1 - это TLS ALPN-расширение, которое говорит клиенту «я умею HTTP/2 и HTTP/1.1, выбирай». Браузеры и современные HTTP-клиенты выбирают h2. Старые клиенты без ALPN получают HTTP/1.1 как и раньше. Обратная совместимость полная.
option http-server-close на бэкенде - важная деталь. HAProxy не держит постоянных соединений к бэкендам при этой опции, что при HTTP/2 на фронтенде означает: один клиент с мультиплексированием не монополизирует соединение к бэкенду.
SSL offloading: что улучшилось
В 1.6 переработали обработку SSL-сессий. Конкретно - появилась поддержка общего кеша SSL-сессий через tune.ssl.cachesize и tune.ssl.lifetime, что при нескольких процессах HAProxy (несколько CPU) позволяет не делать полный TLS handshake при переключении между процессами.
На нашей установке - 4 процесса, трафик балансируется между ними. До 1.6 часть клиентов при попадании на другой процесс делала повторный handshake. Сейчас сессия резьюмится. На нагруженных API с мобильными клиентами, которые переподключаются часто, это снижает CPU на балансировщике.
Также появилась директива ssl-dh-param-file для явного задания параметров DH под Diffie-Hellman key exchange - раньше HAProxy использовал встроенные параметры 1024 бит, что для 2015 года уже не очень. Сгенерировали 2048-битные, подключили.
Что осталось на nginx
Nginx никуда не делся - он стоит за HAProxy и отдаёт статику, проксирует на приложение. HTTP/2 с клиентом разговаривает HAProxy, nginx получает HTTP/1.1. Это нас устраивает: разделение ответственности понятное, отлаживать проще.
Если бы задача была проще (один инстанс, без L4-балансировки, только статика + проксирование), мы бы, наверное, обошлись nginx 1.9.5, у которого HTTP/2 тоже уже есть. Но там свои нюансы с конфигурацией сети на уровне ядра, и в связке HAProxy + nginx всё оказалось аккуратнее.
Текущее состояние
Раскатка прошла без даунтайма - HAProxy в 1.6 меняли через systemctl reload, конфиг проверяли через -c. На мобильных клиентах разница в скорости загрузки заметна, метрики собираем, через пару недель будет более полная картина по p95/p99 latency.
HAProxy 1.6 в целом выглядит как аккуратный релиз - API конфига не сломан, апгрейд без сюрпризов. Для managed-инфраструктуры это именно то, что нужно: новые возможности, которые можно включить поверх существующей конфигурации, без переписывания всего с нуля.