Sticky-сессии и хеширование: ip_hash и hash
Иногда клиента нужно «приклеить» к одному и тому же бэкенду — например, если там лежит его сессия в памяти. Для этого есть ip_hash и hash.
«Липкая сессия лечит симптом, а не болезнь. Настоящее лекарство — хранить состояние не в памяти процесса, а в общем хранилище.»
Round-robin и least_conn распределяют запросы свободно, и один клиент в разных запросах попадает на разные серверы. Обычно это хорошо. Но если приложение хранит сессию в памяти конкретного процесса, клиента нужно стабильно направлять туда же. Это sticky-сессии (липкие сессии).
Метод ip_hash
upstream backend {
ip_hash;
server 10.0.0.1;
server 10.0.0.2;
server 10.0.0.3;
}
ip_hash вычисляет хеш от IP-адреса клиента и по нему всегда выбирает один и тот же сервер. Один IP — один бэкенд (пока тот жив).
client 203.0.113.7 -- hash --> всегда сервер B client 198.51.100.4 -- hash --> всегда сервер A client 192.0.2.50 -- hash --> всегда сервер C
Гибкий hash по ключу
upstream backend {
hash $request_uri consistent; # привязка по URI, а не по IP
server 10.0.0.1;
server 10.0.0.2;
}
hash позволяет выбрать ключ привязки самому: $request_uri, $arg_userid и т.п. Параметр consistent включает консистентное хеширование: при добавлении/удалении сервера переедет лишь малая доля ключей, а не все.
Смоделируем хеш-привязку на Python
servers = ["A", "B", "C"]
def pick(client_ip):
# упрощённый аналог ip_hash: стабильный выбор по хешу
h = sum(int(p) for p in client_ip.split("."))
return servers[h % len(servers)]
clients = ["203.0.113.7", "198.51.100.4", "192.0.2.50", "203.0.113.7"]
for ip in clients:
print(f"{ip:16} -> сервер {pick(ip)}")
print("Заметь: один и тот же IP всегда даёт один и тот же сервер")
Попробуй сам ▶ Один IP детерминированно отображается в один сервер — это и есть суть липкой сессии. Если убрать сервер из списка, привязки массово поедут (поэтому в Nginx и придумали consistent).
Как работает под капотом
ip_hash берёт первые октеты IPv4 (или полный IPv6) и считает хеш, отображая его на список серверов. При обычном хешировании удаление одного сервера меняет результат почти для всех ключей — сессии массово теряются. consistent (алгоритм ketama) минимизирует это: при изменении состава апстрима переезжает примерно 1/N ключей. Если клиенты сидят за общим NAT (один внешний IP на офис), ip_hash свалит их всех на один сервер — балансировка перекосится.
Частые ошибки
- ip_hash при клиентах за NAT/CDN. Сотни пользователей под одним IP уедут на один бэкенд — перекос нагрузки.
- Лечить sticky тем, что должно жить в общем хранилище. Сессии в Redis/БД убирают саму потребность в липкости.
- hash без consistent. Любое изменение апстрима обнулит почти все привязки разом.
Best practices
- Сначала вынеси состояние из памяти процесса (Redis, БД, JWT) — тогда липкость не нужна и масштабирование проще.
- Если sticky неизбежна, предпочитай
hash ... consistentради плавных переездов. - Помни про NAT: ip_hash честен только при уникальных клиентских IP.
Почему индустрия уходит от липких сессий
Sticky-сессии — наследие эпохи, когда состояние пользователя (его корзина, авторизация, временные данные) хранилось прямо в оперативной памяти конкретного процесса приложения. Тогда привязать клиента к «его» серверу было необходимостью. Но у этого подхода есть фундаментальный изъян: как только этот сервер падает или его выводят на деплой, все привязанные к нему пользователи теряют сессию — их разлогинивает, корзина пустеет. Масштабировать и обновлять такую систему больно, потому что каждый сервер несёт уникальное незаменимое состояние.
Современный подход — stateless-архитектура: вынести состояние из памяти процесса в общее хранилище. Сессии кладут в Redis или базу данных, к которым имеют доступ все бэкенды одинаково; или вообще уходят от серверных сессий к самодостаточным токенам (JWT), где всё нужное зашито в самом токене у клиента. Тогда любой запрос можно отдать любому серверу, балансировка снова свободная (round-robin или least_conn), а падение или деплой одного бэкенда никто не замечает. Именно поэтому в новых проектах sticky-сессии стараются не закладывать вовсе. Если же ты унаследовал систему, где они нужны, относись к этому как к временному компромиссу и постепенно выноси состояние наружу — это окупится при первом же масштабировании или обновлении без простоя.
Итоги
Sticky-сессии (ip_hash, hash) привязывают клиента к одному бэкенду — нужны, когда состояние лежит в памяти процесса. Но это костыль: лучше хранить сессии в общем хранилище и балансировать свободно. consistent смягчает переезды при изменении состава. Дальше — проверки здоровья и отказоустойчивость апстрима.