Какие алгоритмы балансировки существуют
Что проверяют
Знаете ли вы основные алгоритмы и понимаете ли, от каких условий зависит выбор. Для Go-разработчика вопрос часто уходит в практику: «напишите потокобезопасный Round Robin» или «почему gRPC-трафик в Kubernetes ложится на один под».
Как отвечать
Разделите на статические, которые не смотрят на состояние серверов, и динамические:
- Round Robin: по кругу, для одинаковых серверов и коротких запросов;
- Weighted Round Robin: серверу с весом 3 достаётся втрое больше запросов. В Nginx сглаженный вариант, который не шлёт три запроса подряд на один узел;
- Random: не требует общего счётчика, на большом потоке ровный;
- IP Hash и хеш по ключу: клиент или ключ всегда попадает на один сервер, это даёт sticky-сессии и локальность кеша. Для устойчивости к смене состава берут консистентное хеширование;
- Least Connections, в том числе взвешенный: туда, где меньше активных соединений. Подходит для запросов разной длительности и WebSocket;
- Least Response Time, EWMA: учитывают задержку ответа;
- Power of Two Choices: из двух случайных серверов берётся менее загруженный.
Любой алгоритм работает только вместе с проверками здоровья: упавший узел должен выпасть из пула.
Теперь про Go. Потокобезопасный Round Robin пишется без мьютекса: atomic.Uint64, Add(1) и остаток от деления на len(backends). Для HTTP есть httputil.ReverseProxy, выбор бэкенда делается в Rewrite. Про gRPC: он держит одно долгое HTTP/2-соединение и мультиплексирует в нём запросы. L4-балансировщик вроде обычного Kubernetes Service распределяет соединения, а не запросы, поэтому нужна L7-балансировка: Envoy или клиентская политика round_robin в gRPC-Go с headless-сервисом.
Где спотыкаются
Не различают L4 и L7 и не могут объяснить, почему при долгих соединениях Round Robin на уровне TCP даёт перекос.
Пишут счётчик Round Robin как обычный int под конкурентной нагрузкой и получают гонку, которую покажет go test -race.
Берут IP Hash для sticky-сессий и не думают, что пользователи за одним NAT сваливаются на один сервер, а при добавлении узла без консистентного хеширования переезжают почти все.