MAATRIX / Блог / Балансировка между тремя серверами в разных странах: рабочие схемы и типичные грабли

Балансировка между тремя серверами в разных странах: рабочие схемы и типичные грабли

MAATRIX

Три сервера в трёх странах — это не автоматически быстрый и отказоустойчивый сервис. Без продуманной схемы распределения трафика пользователь из Сингапура будет ходить в Амстердам, а при падении одного из узлов половина клиентов зависнет с открытыми, но бесполезными соединениями. Разберём три рабочие схемы балансировки между гео-распределёнными серверами — GeoDNS, anycast и балансировщик уровня приложения — и типичные грабли, которые всплывают уже после запуска, когда серверов стало больше одного и они физически разнесены.

Три схемы и когда какую выбирать

Прежде чем настраивать что-либо, стоит понять, что решается тремя принципиально разными по механике инструментами, и они не взаимозаменяемы:

СхемаЧто делаетСкорость failoverСложностьКогда достаточно
GeoDNSОтдаёт разным клиентам разные A-записи по их геолокацииМедленный (упирается в TTL и кэш резолверов)НизкаяЧитаемый трафик, некритичный к простою в единицы минут
AnycastОдин IP анонсируется из нескольких точек, маршрут выбирает сетьБыстрый (секунды, на уровне маршрутизации)Высокая (нужна своя автономная система или anycast-услуга провайдера)DNS, CDN, защита от DDoS, критичная доступность
L7-балансировщик + failoverОдин или несколько балансировщиков перед распределёнными бэкендами с health checkСредний (зависит от интервала проверок)СредняяПриложения, где важна логика поверх балансировки (сессии, маршрутизация по пути)

На практике эти схемы часто комбинируют: GeoDNS отдаёт ближайший к пользователю регион, а внутри региона уже работает обычный L7-балансировщик между несколькими бэкендами. Прежде чем выбирать схему, стоит определить, откуда вообще идёт основной трафик — если аудитория сконцентрирована в одном-двух регионах, третий сервер в третьей стране может решать не задачу скорости, а задачу отказоустойчивости, и это разные требования к схеме. Как оценить, где на самом деле сидит ваша аудитория, разобрано в статье про выбор локации по замерам аудитории — без этого шага балансировка между тремя странами рискует оптимизировать не тот трафик.

Схема 1: GeoDNS — маршрутизация по геолокации клиента

GeoDNS — это функция авторитативного DNS-сервера, которая отвечает на запрос разными IP-адресами в зависимости от того, откуда пришёл запрос. Технически резолвер смотрит на IP DNS-сервера, который спрашивает (не самого клиента — это важное отличие), сверяет его с базой геолокации (GeoIP) и отдаёт A-запись ближайшего по настроенной карте региона сервера.

Пример конфигурации на PowerDNS с бэкендом geoip:

domains:
  - domain: example.com
    ttl: 60
    records:
      www:
        - subnets: ["RU", "KZ", "BY"]
          ip: 203.0.113.10   # сервер в Москве
        - subnets: ["DE", "NL", "FR"]
          ip: 198.51.100.20  # сервер во Франкфурте
        - subnets: ["default"]
          ip: 192.0.2.30     # сервер в США — фолбэк

Плюсы понятны: настраивается на уровне DNS без вмешательства в архитектуру приложения, работает с любым бэкендом, не требует своей автономной системы или специального договора с провайдером — большинство managed DNS с geo-функцией доступны как обычная услуга. Для сайтов и API, где задача — просто дать пользователю сервер поближе, этого часто достаточно.

Минусы тоже конкретные, и оба системные, а не «настроили неправильно»:

  • Кэширование ломает быстрый failover. DNS-ответ живёт в кэше резолвера (и часто в кэше приложения или ОС клиента) до истечения TTL — а на практике многие резолверы и корпоративные DNS-серверы TTL занижают неохотно или вовсе игнорируют, держа старую запись дольше заявленного. Даже с TTL 60 секунд часть пользователей продолжит стучаться в упавший сервер минутами, а не секундами, а изменения будут доходить до разных пользователей неравномерными волнами — механика этого разобрана в статье про TTL DNS-записи и почему изменения доходят до людей разными волнами.
  • Geo-определение по IP неточное. База GeoIP знает страну и часто регион с хорошей точностью, но мобильные операторы, корпоративные VPN и часть провайдеров агрегируют трафик через один аплинк в другой стране — пользователь из Новосибирска может определиться как «Москва» или вовсе как страна транзитного узла. Для грубой балансировки по континентам это не критично, для точной маршрутизации внутри одной страны — уже проблема.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Схема 2: Anycast — один IP, маршрут выбирает сеть

Anycast — принципиально другой механизм: один и тот же IP-адрес анонсируется по протоколу BGP одновременно из нескольких точек присутствия, и какая именно точка обслужит конкретного клиента, решает не ваш сервер и не DNS, а сама маршрутизация в интернете — пакет уходит туда, куда ведёт кратчайший по метрикам BGP путь.

Здесь важна оговорка, которую часто упускают: «ближайший» в терминах BGP — это не «географически ближайший», а тот, у кого меньше AS-переходов и выгоднее анонс с точки зрения пиринговых соглашений между операторами. Сервер в Стокгольме иногда обслуживает петербуржца быстрее, чем формально более близкий сервер в другом городе — просто потому что маршрут короче по сетевым связям, а не по карте. Та же логика объясняет, почему маршрут не совпадает с географией и при обычной unicast-маршрутизации, без всякого anycast.

Главный практический плюс anycast — естественный и быстрый failover: если точка присутствия перестаёт анонсировать маршрут (упал сервер, отключили аплинк), трафик через BGP-конвергенцию перетекает на следующую ближайшую точку без участия DNS и без ожидания TTL. Это на порядок быстрее, чем GeoDNS-failover, и именно поэтому anycast — стандарт для публичных DNS-резолверов и CDN.

Минусы столь же весомые:

  • Настроить anycast «просто на своих трёх VPS» нельзя — нужна собственная автономная система (AS), блок IP-адресов с собственным LIR, договорённости о пиринге и BGP-сессии в каждой точке присутствия. Это заметная организационная и техническая нагрузка, а не разовая настройка.
  • Альтернатива — anycast-услуга у провайдера, который уже держит свою anycast-сеть (обычно это CDN- или DDoS-защита-провайдеры; публичный резолвер 1.1.1.1 — известный пример такой архитектуры). Вариантов на рынке несколько, и выбор зависит от того, что именно нужно проксировать — не стоит держать в голове единственного «правильного» поставщика.
  • Anycast плохо совместим с состоянием: если у клиента долгоживущее TCP-соединение (WebSocket, стрим), а маршрут BGP переключился в середине сессии, соединение рвётся — anycast не «мигрирует» сессию, а просто меняет, к какой точке присоединится следующий пакет.

Схема 3: L7-балансировщик перед распределёнными бэкендами

Третий вариант — не заменять DNS или маршрутизацию, а поставить прикладной балансировщик (HAProxy, nginx, Envoy) перед пулом бэкендов, часть из которых физически стоит в других странах. Обычно это работает так: балансировщик стоит в одной точке (или в каждом регионе — свой), знает адреса всех бэкендов независимо от их локации и распределяет запросы с учётом health check.

Пример апстрима HAProxy с бэкендами в трёх странах и активным health check:

backend app_servers
    balance roundrobin
    option httpchk GET /healthz
    http-check expect status 200
    default-server inter 3s fall 2 rise 3

    server ru1 203.0.113.10:443 check ssl verify none
    server de1 198.51.100.20:443 check ssl verify none
    server us1 192.0.2.30:443 check ssl verify none backup

Здесь us1 помечен как backup — получит трафик, только если оба основных узла не пройдут health check. Такая схема даёт контроль, которого нет ни у GeoDNS, ни у чистого anycast: можно завязать балансировку на реальную готовность приложения (/healthz, а не просто «порт слушает»), управлять весами, настраивать sticky-сессии там, где они нужны. Важно проверять именно готовность приложения, а не факт, что TCP-порт отвечает, — процесс может зависнуть, продолжая держать порт открытым.

Минус очевиден: балансировщик в одной точке — сам по себе единая точка отказа и источник дополнительной задержки для пользователей, далёких от неё географически. Запрос из Сингапура сначала долетает до балансировщика в Европе, и только потом — до ближайшего к нему же бэкенда, если тот вообще в Азии; при этом «ближайший географически» и «ближайший по факту задержки» — не всегда одно и то же, как показано на примере сервера во Франкфурте, который оказался быстрее московского для клиентов из Москвы. Поэтому эта схема обычно комбинируется с первыми двумя: GeoDNS или anycast приводит пользователя в ближайший регион, а внутри региона уже L7-балансировщик распределяет нагрузку между несколькими бэкендами и делает failover по health check.

Грабля: рассинхронизация данных между регионами

Самая частая ошибка при проектировании гео-распределённой схемы — решить вопрос балансировки трафика и не решить вопрос данных. Если в каждом регионе бэкенд пишет в свою локальную базу «для скорости», очень быстро выясняется, что это три разные базы с разной картиной мира: пользователь оформил заказ на сервере во Франкфурте, а через минуту его запрос ушёл на сервер в Москве — и там этого заказа просто нет.

Рабочих вариантов, по сути, два:

  • Единый источник истины (single source of truth). Один регион держит основную, «пишущую» базу, остальные — реплики только для чтения или прокси-запросы к мастеру. Это просто и предсказуемо, но добавляет задержку записи для регионов, далёких от мастера, и требует продуманного плана на случай, если мастер-регион недоступен.
  • Мультимастер-репликация с разрешением конфликтов. Каждый регион пишет локально, изменения реплицируются в обе стороны, а конфликты (два региона одновременно изменили одну запись) разрешаются по правилам — last-write-wins, векторные часы, ручная логика на уровне приложения. Это снимает задержку записи, но добавляет сложность и требует, чтобы модель данных вообще допускала конфликты без потери смысла (для счётчика остатков на складе это может быть фатально, для профиля пользователя — приемлемо).

Выбор между ними — не техническая деталь, а архитектурное решение, которое стоит принимать до того, как появится третий сервер, а не после первого расхождения данных.

Грабля: sticky sessions при переключении между регионами

Если балансировка происходит через GeoDNS или anycast, а не через L7-балансировщик с явным управлением сессиями, легко упустить, что смена региона для пользователя означает потерю всего, что хранилось в памяти конкретного сервера. Классический сценарий: пользователь начал оформлять заказ, в его сессии (в памяти процесса или в локальном Redis без репликации) лежит корзина — а следующий запрос, из-за смены сети (переключился с Wi-Fi на мобильный интернет, VPN поменял выходную точку) или из-за TTL DNS-записи, ушёл уже на сервер в другом регионе. Корзина «потерялась», хотя формально ничего не сломалось — просто запрос обслужил другой сервер, который об этой сессии ничего не знает.

Практические способы не наступать на эту грабли:

  • Не хранить состояние сессии локально на сервере — выносить в общее хранилище (Redis, база), доступное из всех регионов, либо шифровать состояние в самом токене (JWT с нужными данными) так, чтобы любой бэкенд мог его прочитать без обращения к конкретному узлу.
  • Если общее хранилище физически в одном регионе, честно закладывать задержку обращения к нему из других регионов в бюджет ответа — и не удивляться, если запрос из Азии к сессии в европейском Redis не будет мгновенным.
  • Для сценариев, где действительно нужна привязка к конкретному серверу (WebSocket, долгоживущие соединения), использовать sticky sessions на уровне L7-балансировщика (cookie-based affinity), а не полагаться на то, что DNS или anycast «сами не переключат» пользователя посреди сессии — переключат, и это нормальное поведение схемы, а не баг.

Грабля: общая база в одном регионе, бэкенды — в нескольких

Отдельный, менее очевидный случай: данные решили не размазывать по регионам (осознанно выбрали единый источник истины), но забыли посчитать, во что это выливается по задержке. Если база данных стоит, скажем, в Германии, а бэкенды — в Германии, США и Сингапуре, то каждый запрос из сингапурского бэкенда к базе идёт через полмира и обратно, и это может съедать больше времени, чем вся остальная обработка запроса вместе взятая.

Что стоит сделать до того, как это станет проблемой продакшена:

  • Замерить реальную сетевую задержку между каждым регионом бэкенда и регионом базы данных (ping, mtr, время выполнения простого запроса) — не полагаться на предположения о географии.
  • Развести операции по чувствительности к задержке: то, что можно закэшировать локально (справочники, редко меняющиеся данные), кэшировать; то, что требует свежей записи (баланс, остатки, платежи), — оставить идти напрямую в мастер-базу и заложить задержку в SLA осознанно.
  • Если задержка до базы становится узким местом для региона, рассмотреть локальную read-реплику для операций чтения — это не устраняет проблему записи, но снимает основную часть нагрузки для типового трафика.

Асимметрия задержки между регионами — не исключение, а норма для любой гео-распределённой схемы, и её стоит закладывать в план на этапе выбора локаций, а не выяснять постфактум.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

С чего начать, если серверов пока три и они в разных странах?

В большинстве случаев — с GeoDNS поверх L7-балансировщика в каждом регионе: самая простая в настройке комбинация, не требующая своей автономной системы. Anycast стоит рассматривать отдельно, когда критична скорость failover в секундах, а не минутах, и есть ресурс на его организационную сложность или оплату готовой anycast-услуги.

Можно ли обойтись без единой базы и держать каждый регион независимым?

Да, если данные регионов действительно не пересекаются (например, три отдельных рынка без общих пользователей). Если пересекаются — понадобится либо единый источник истины, либо мультимастер-репликация с разрешением конфликтов; без этого расхождение данных — вопрос времени, а не вероятности.

GeoDNS ошибочно определяет регион части пользователей — это нормально?

Да, это системное ограничение технологии. GeoIP-базы точны для основной массы трафика, но заведомо ошибаются на мобильных операторах, VPN и агрегированных корпоративных сетях. Если точность критична, одного GeoDNS недостаточно — нужна дополнительная проверка на уровне приложения.

Нужен ли anycast, если у нас всего три сервера и нет своей автономной системы?

Обычно нет — организационные затраты (AS, блок IP, пиринг) несоразмерны выгоде для такого масштаба. Разумная альтернатива — GeoDNS с коротким TTL плюс L7-балансировщик с health check внутри каждого региона.

Что делать, если после переключения региона пользователи теряют корзину или сессию?

Проверить, где хранится состояние сессии — если локально на сервере, перенести его в общее хранилище, доступное из всех регионов, или закодировать нужные данные в самом токене. Sticky sessions на балансировщике — временный костыль, а не решение на уровне GeoDNS или anycast, где переключение происходит вне контроля балансировщика.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →