← Назад к вопросам

Какие плюсы и минусы DNS в качестве балансировщика нагрузки?

2.4 Senior🔥 71 комментариев
#DevOps и инфраструктура

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

DNS как балансировщик нагрузки

DNS балансирование — использование Domain Name System для распределения трафика между несколькими серверами.

Как работает DNS балансирование

1. Клиент запрашивает: example.com
2. DNS server возвращает несколько IP адресов:
   203.0.113.1
   203.0.113.2
   203.0.113.3
3. Клиент выбирает один и подключается

Плюсы DNS балансирования

1. Простота и дешевизна

Без DNS балансирования:
┌─────────────────┐
│  Load Balancer  │  -- Требует отдельного оборудования
│  (Hardware/SW)  │  -- Дорого (или облако решение)
└────────┬────────┘
         │
    ┌────┼────┬──────┐
    │    │    │      │
┌───▼──┐ │ ┌──▼──┐ ┌─▼──┐
│Server1│ │ │Server2 │Server3│
└───────┘ │ └──────┘ └──────┘

С DNS балансированием:
┌──────────────────────┐
│  DNS Server          │  -- Встроено везде
│  (example.com)       │  -- Никаких дополнительных затрат
└──────────────────────┘
    │
    ├──> 203.0.113.1
    ├──> 203.0.113.2
    └──> 203.0.113.3

2. Отсутствие единой точки отказа

# Нет центрального bottleneck
# Если один сервер упадёт, другие остаются доступны

# DNS возвращает все живые записи:
# A record: 203.0.113.1 (Server 1)
# A record: 203.0.113.2 (Server 2)  
# A record: 203.0.113.3 (Server 3)

# Клиент выбирает случайно или по порядку

3. Географическое распределение

# GeoDNS — возвращает разные IP на основе геолокации

# Запрос из России:
# answer: 91.108.123.1 (Moscow datacenter)

# Запрос из США:
# answer: 34.117.45.2 (US East datacenter)

# Использование:
# from geoip2 import geolite2

def geo_dns_response(client_ip: str):
    response = geolite2.reader().get(client_ip)
    location = response['location']
    
    if location['latitude'] > 40:  # Northern hemisphere
        return '91.108.123.1'  # Europe
    else:
        return '34.117.45.2'  # US

4. Поддержка SRV записей

_http._tcp.example.com SRV records:

_http._tcp.example.com 10 60 80 server1.example.com
_http._tcp.example.com 10 40 80 server2.example.com
_http._tcp.example.com 20 60 80 server3.example.com

Priority: 10 (выше) → 20 (ниже)
Weight: распределение среди приоритета
Port: 80
Host: server1.example.com

5. Масштабируемость

Миллионы клиентов одновременно → DNS легко справляется
DNS кэшируется на всех уровнях:
├─ OS кэш
├─ ISP кэш
├─ Router кэш
└─ Application кэш

Не требует центрального load balancer-а

Минусы DNS балансирования

1. Задержка обновления (TTL)

# Если сервер упадёт, клиенты узнают об этом не сразу

# TTL = 300 (5 минут)
Example:
- 12:00:00 → Сервер упадёт
- 12:00:05 → Клиент получает ошибку
- 12:00:10 → Клиент переподключается (но кэш ещё действует!)
- 12:05:00 → TTL истёк, DNS запрашивается заново
- 12:05:05 → Клиент попадает на живой сервер

# Проблема: 5+ минут downtime для некоторых клиентов

import dns.resolver
import time

def check_dns_ttl(domain: str):
    answers = dns.resolver.resolve(domain, 'A')
    for rdata in answers:
        print(f'IP: {rdata}, TTL: {answers.rrset.ttl}')

check_dns_ttl('example.com')
# IP: 203.0.113.1, TTL: 300

2. Отсутствие health checks

# DNS не проверяет живы ли серверы

# Сценарий:
# Server 1: CRASHED ❌
# Server 2: OK ✓
# Server 3: OK ✓

# DNS всё ещё возвращает:
# 203.0.113.1 ← МЁРТВЫЙ СЕРВЕР!
# 203.0.113.2
# 203.0.113.3

# Клиент может попасть на упавший сервер
# и получить Connection refused после 30+ секунд таймаута

# Решение: добавить health checks
def health_check_server(ip: str, port: int = 80, timeout: int = 5):
    import socket
    try:
        sock = socket.create_connection((ip, port), timeout=timeout)
        sock.close()
        return True
    except (socket.timeout, socket.error):
        return False

# Но это требует запуска отдельного сервиса!

3. Неравномерное распределение нагрузки

# DNS не учитывает текущую нагрузку на серверах

# Сценарий:
# Server 1: 10k RPS (98% CPU) ← ПЕРЕГРУЖЕН
# Server 2: 100 RPS (1% CPU)  ← НЕДОГРУЖЕН

# DNS всё ещё распределяет 33%-33%-33%
# или по round-robin без учёта текущей нагрузки

# Load Balancer может умнее:
from collections import defaultdict

class SmartLB:
    def __init__(self, servers):
        self.servers = servers
        self.current_load = defaultdict(int)
    
    def get_best_server(self):
        # Выбирает сервер с минимальной нагрузкой
        return min(self.servers, key=lambda s: self.current_load[s])

lb = SmartLB(['server1', 'server2', 'server3'])
# Выберет сервер с наименьшей нагрузкой

4. Различное поведение клиентов

# Разные клиенты обрабатывают DNS по-разному

# Chrome: кэширует DNS 1 минуту
# Firefox: кэширует DNS 60 секунд
# curl: не кэширует (или использует системный DNS)
# Custom app: может кэшировать на часы!

# Результат: очень неравномерное распределение

# Example:
# Клиент A: запросил example.com → получил 203.0.113.1
#          кэширует на 1 час → будет использовать 1 час

# Клиент B: запросил example.com → получил 203.0.113.3
#          кэширует на 5 минут → будет переопрашивать

# Клиент C: не кэширует → переопрашивает каждый запрос
#          может менять IP каждый раз!

5. Проблемы с сессиями

# Если используется session affinity (sticky sessions)

# Сценарий:
# 1. Клиент подключился к Server 1 (получил session cookie)
# 2. Server 1 упал
# 3. DNS возвращает только Server 2 и 3
# 4. Клиент переподключается ко Server 2
# 5. Server 2 не знает о сессии → нужна переаутентификация

# Решение: использовать sticky load balancer с session replication
# Но DNS этого не может!

6. Сложность отладки

Проблема: клиент говорит "ваш сервис медленный"
Вы проверяете: все серверы работают нормально

Причина: у клиента старый кэшированный DNS
         который указывает на перегруженный сервер
         который был перезагружен 2 часа назад

Отладка:
$ nslookup example.com
$ dig example.com
$ cat /etc/resolv.conf
$ ipconfig /flushdns  (Windows)
$ sudo dscacheutil -flushcache  (Mac)

7. Отсутствие SSL/TLS termination

# DNS просто возвращает IP адреса
# Криптография должна быть на серверах

# Load Balancer может делать HTTPS offloading:
Client → HTTPS → LB (decrypts) → HTTP → Server
                     ↓
            Экономим CPU на серверах!

# DNS не может этого сделать

Когда использовать DNS балансирование

✅ Хорошо для:

  • Микросервисов внутри сети (Kubernetes DNS)
  • GeoDNS для географического распределения
  • Статических сервисов без dynamic scaling
  • Дешёвых решений для стартапов

❌ Плохо для:

  • Production приложений требующих reliability
  • Систем с частыми изменениями (auto-scaling)
  • Sessions-зависимых приложений
  • Требующих real-time health checks

Гибридный подход

┌────────────────────────────┐
│  GeoDNS                    │
│  example.com.eu → EU LB    │
│  example.com.us → US LB    │
└────────────────────────────┘
         │
    ┌────┴────┐
    │          │
┌───▼──────┐ ┌─▼─────────┐
│EU LB     │ │ US LB     │  -- Настоящие load balancers
└──┬─┬─┬──┘ └─┬─┬─┬──────┘
   │ │ │      │ │ │
┌──▼▼▼──┐  ┌──▼▼▼──┐
│Servers│  │Servers│  -- Реальные серверы
└───────┘  └───────┘

DNS распределяет между регионами,
Load Balancer внутри региона распределяет между серверами

Практическая реализация

import dns.resolver
import socket
from typing import List

class DNSLoadBalancer:
    def __init__(self, domain: str, port: int = 80):
        self.domain = domain
        self.port = port
        self.addresses = []
        self.refresh_dns()
    
    def refresh_dns(self):
        try:
            answers = dns.resolver.resolve(self.domain, 'A')
            self.addresses = [str(rdata) for rdata in answers]
        except Exception as e:
            print(f"DNS error: {e}")
    
    def get_address(self, index: int = None) -> str:
        if not self.addresses:
            self.refresh_dns()
        
        if index is None:
            import random
            index = random.randint(0, len(self.addresses) - 1)
        
        return self.addresses[index % len(self.addresses)]
    
    def check_health(self) -> List[str]:
        healthy = []
        for addr in self.addresses:
            try:
                sock = socket.create_connection((addr, self.port), timeout=2)
                sock.close()
                healthy.append(addr)
            except (socket.timeout, socket.error):
                pass
        return healthy

# Использование
lb = DNSLoadBalancer('example.com')
healthy_servers = lb.check_health()
server_to_use = healthy_servers[0]

Вывод: DNS балансирование — бесплатный и простой способ базового распределения нагрузки, но не заменяет полноценный Load Balancer для production систем.