awesome-everything EN
↑ Обратно к восхождению

Сети и протоколы

Session affinity, consistent hashing и правильное решение

Суть Sticky sessions вызывают дисбаланс нагрузки в 1,5–2,5 раза и потерю сессии при отказе; consistent hashing минимизирует переотображение при изменении состава; правильное решение — Redis.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 12 min

Пользователь входит в систему. Его сессия живёт на backend B2. Следующий запрос маршрутизируется на B3. Сессия не найдена. Пользователь выброшен из системы в середине работы. Это происходит, когда приложение хранит состояние на одном backend и полагается на session affinity, чтобы это скрыть — пока affinity не сломается.

Зачем нужны sticky sessions

Некоторые приложения хранят состояние сессии на backend — в памяти или в локальном файле. Если следующий запрос маршрутизируется на другой backend, состояния нет: пользователь выброшен из системы, корзина пуста, контекст загрузки утерян.

Sticky sessions (session affinity) заставляют LB маршрутизировать все запросы от одного клиента на один и тот же backend. Запросы клиента «прикреплены» к одному серверу.

LB устанавливает cookie при первом ответе:

Set-Cookie: LB_ROUTE=backend_2; Path=/

При каждом последующем запросе браузер отправляет этот cookie. LB читает его и маршрутизирует на backend_2.

Преимущества:

  • Переживает смену IP клиента (handover мобильной сети, NAT failover).
  • Допускает graceful failover: если backend_2 нездоров, LB может выбрать новый backend и обновить cookie.

Недостаток: Cookie-based affinity требует HTTP. Не-HTTP-протоколы использовать его не могут.

IP-hash affinity

Маршрутизация по Hash(client_IP) % num_backends. Cookie не нужен — работает для любого протокола.

Недостатки:

  • Ломается при смене IP клиента (handover мобильной сети, переподключение VPN).
  • Плохо кластеризует: 50 клиентов за одним корпоративным NAT все хешируются на один backend — этот backend получает нагрузку в 50 раз больше остальных.
Стоимость и варианты session affinity
Дисбаланс нагрузки при sticky sessions
в 1,5–2,5 раза хуже
Потеря сессии при падении прикреплённого backend
100% для этой сессии
Cookie-based: переживает смену IP
да
IP-hash: переживает смену IP
нет
IP-hash при 50 клиентах за NAT
все 50 попадают на один backend
Правильное решение: TTL сессии в Redis
любой backend может продолжить

Правильное решение: вынесенное состояние сессии

Храните данные сессии в распределённом кэше (Redis, Memcached, DynamoDB) по ключу session ID. Каждый backend читает из и пишет в этот хранилище.

Теперь LB может маршрутизировать запросы свободно с помощью power-of-two-choices. Без affinity. Без привязки. Данные сессии переживают отказы backends, потому что они в Redis, а не в памяти одного backend.

Этот паттерн используется в Netflix, Airbnb, Shopify и любом сервисе, масштабирующемся горизонтально.

Consistent hashing: для локальности кэша, не состояния сессии

Session affinity — о состоянии пользователя. Consistent hashing — о локальности кэша: маршрутизация ключа кэша на один и тот же backend для получения cache hit вместо miss.

Проблема round-robin для кэширования:

  • Запрос user:42 → B1 (кэшировано там).
  • Следующий запрос user:42 → B2 (cache miss, запрос к БД).
  • Никакой локальности, никакой пользы от кэширования.

Consistent hashing отображает каждый ключ на точку кольца хешей. Backends тоже отображаются на точки (с множеством виртуальных узлов на backend для равномерного распределения, ~150–300). Ключ маршрутизируется к ближайшему backend по часовой стрелке на кольце.

Ключевое свойство: Когда backend входит или выходит, только ~1/N ключей переотображаются. Все остальные ключи остаются на том же backend. Это минимизирует нарушение кэша при изменениях топологии.

Стоимость поиска: O(log N) с отсортированным деревом (бинарный поиск по позициям виртуальных узлов).

Используйте consistent hashing для:

  • Распределённых кэшей (Memcached shards, Redis cluster).
  • Шардирования баз данных (маршрутизация по shard key).
  • Sticky-маршрутизации запросов для локальности кэша (задачи кодирования медиа, аналитические агрегации).

Не используйте consistent hashing для общей балансировки запросов — power-of-two-choices адаптируется к реальной нагрузке в реальном времени, consistent hashing — нет.

Rendezvous hashing (highest-random-weight)

Альтернатива кольцевому consistent hashing:

  1. Для каждого backend вычислите Hash(key, backend_id).
  2. Маршрутизируйте на backend с наибольшим значением хеша.

Нет кольца, нет виртуальных узлов — проще реализовать. O(N) на поиск, но для малого N (<100 backends) разница незначительна. Распределение хеша зачастую более равномерно, чем у кольцевого consistent hashing на практике. Некоторые CDN и Facebook TAO используют rendezvous hashing для шардирования.

Почему это работает

Зачем важны виртуальные узлы. Без виртуальных узлов каждый backend занимает одну дугу на кольце. При 4 backends (A, B, C, D), размещённых в 0, 90, 180, 270 градусах, дуги идеально равномерны — но это идеальный случай. На практике hash(backend_id) кластеризует backends неравномерно. Виртуальные узлы отображают каждый backend на 150–300 позиций кольца, разбивая его на 150–300 малых дуг. Это усредняет распределение, чтобы ни один backend не занимал непропорционально большую дугу.

Расставь шаги по порядку

Расставьте шаги failover session affinity (cookie-based), показывающие, почему это не работает без Redis:

  1. 1 Клиент отправляет запрос с cookie LB_ROUTE=backend_2.
  2. 2 LB читает cookie и маршрутизирует на backend_2.
  3. 3 Backend_2 падает или становится нездоровым.
  4. 4 LB прекращает маршрутизировать новые запросы на backend_2, но не может перенести существующую сессию.
  5. 5 Сессия клиента теряется, потому что только backend_2 хранил её в памяти.
  6. 6 Клиент делает retry; LB выбирает новый backend, но данные сессии утеряны.
  7. 7 Правильное решение: хранить сессию в Redis, доступном с любого backend, чтобы failover был прозрачным.
Викторина

В чём главный недостаток IP-hash session affinity по сравнению с cookie-based?

Викторина

Когда backend удаляется из consistent-hash кольца, какая доля ключей должна переотображаться на новый backend?

Вспомните перед уходом
  1. 01
    Почему session affinity считается антипаттерном и каково правильное решение?
  2. 02
    Какую проблему решает consistent hashing, которую не может решить round-robin?
  3. 03
    Что такое виртуальные узлы в consistent hashing и зачем они нужны?
Итог

Session affinity маршрутизирует каждого клиента на один backend через cookie (LB_ROUTE=backend_2) или IP-хеш. Cookie-based affinity переживает смену IP; IP-hash ломается при смене IP и плохо кластеризует за NAT. Оба вызывают дисбаланс нагрузки в 1,5–2,5 раза и теряют сессии при отказе backend — это обходные решения, а не настоящие. Правильная архитектура: вынести состояние сессии в Redis, чтобы любой backend мог продолжить любую сессию, а LB маршрутизировал свободно. Consistent hashing — другой инструмент для локальности кэша: он отображает ключ на один backend с переотображением ~1/N при изменении состава, используя виртуальные узлы (150–300 на backend) для выравнивания распределения по кольцу. Используйте consistent hashing для кэшей и шардирования; используйте power-of-two-choices для балансировки живых запросов.

Связанные уроки
встречается в152
Продолжить восхождение ↑Retry-бури, circuit breakers и load shedding
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.