VPN·HUB·CRACK SMART-балансировщик 2.0 · VPN HUB CRACK
Все статьи
Remnawave PRO Remnawave

SMART-балансировщик 2.0

Обновлено 11 августа 2026 Проверено

SMART-балансировщик 2.0

У типового сервиса два класса серверов. Обычные: заграничные, трафик дешёвый, работают, пока их не срежут блокировкой. И ноды обхода: вход в России с «белым» IP, дальше каскадом за границу; они держатся там, где обычные уже вырезаны, но трафик на них дорогой.

Хочется, чтобы клиент сам сидел на дешёвых, а на дорогие уходил автоматически и только в аварию. И это ещё не всё: одна и та же нода обхода работает у МТС и молчит у Мегафона, значит выбирать нужно ещё и между самими нодами обхода.

Обычный «Автовыбор» (leastPing) так не умеет: он берёт узел с лучшим пингом и не отличает «дешёвый» от «дорогого». Здесь разберём, как собрать одну плитку, которая решает всё это сама. Всё замерено на боевом сервисе, а не переписано из документации; неудачные ходы показаны наравне с рабочими: их-то в документации и нет.

Это продолжение статей «СУПЕР-АВТОВЫБОР» и «Балансировка и автовыбор». Базовые вещи про балансеры смотрите там, здесь только про приоритет и дорогой резерв.

🛠️ Конструктор: впиши свои теги и копируй

Впиши сверху страницы теги, которыми помечены твои хосты в поле Tag, они сами подставятся в конфиг ниже. Веса, интервал и остальное уже проставлены по замерам из статьи; хочешь тоньше, правь прямо в конфиге, всё разобрано по разделам.

1. Где вообще живёт логика

Первое, что стоит уложить в голове: подписка отдаёт клиенту не список серверов, а конфиг Xray с правилами. Внутри приложения (Happ, v2rayNG, Streisand) крутится то же ядро Xray, что и на сервере, и оно исполняет эти правила само. Решение, на какой узел пойти, принимается на устройстве абонента, поэтому оно учитывает именно его оператора и его сеть. Панель к этому моменту уже ни при чём: она собрала конфиг и отдала.

Практическое следствие честно: схема работает только у клиентов на ядре Xray. Проверено: Happ (iOS/Windows), v2rayNG, Streisand получают полноценный балансировщик; Clash, sing-box, Shadowrocket видят обычный список серверов без автопереключения. Это не «наполовину», это «у части клиентов не заработает вовсе», держите в уме, когда объявляете фичу.

Чтобы клиент получал JSON-конфиг, а не плоский список, в панели включается один тумблер под названием «Использовать JSON в базовой подписке»:

Настройки подписки: JSON в базовой подписке

2. Стратегий балансировки ровно четыре

random (он же по умолчанию), roundRobin, leastPing, leastLoad. Всё. Никаких priority, weighted или failover в Xray не существует, говорю прямо, потому что их постоянно ищут и пытаются вписать в конфиг. Приоритет собирается не стратегией, а двумя другими механизмами: весами (costs) и аварийным выходом (fallbackTag).

3. Механизм 1: costs у leastLoad

costs это веса по регулярному выражению на тег узла. Работает только со стратегией leastLoad. Метрика узла умножается на квадратный корень из веса (value * math.Pow(cost, 0.5)), дальше сортировка по возрастанию: кто меньше, тот и выигрывает. То есть cost: 100 даёт множитель ×10, cost: 12 даёт примерно ×3.5.

Разложим на живой ситуации. Пусть реальные пинги такие:

Узел Реальный пинг Вес Итоговая метрика
обычный TCP 200 мс ×1 200 ← выигрывает
gRPC / xHTTP 250 мс ×3.5 875
нода обхода 300 мс ×10 3000

Пока обычный TCP жив, он всегда впереди: даже с не самым лучшим пингом его метрика 200 против 875 и 3000. Дорогая нода обхода математически задвинута в самый конец.

А теперь важное свойство: мёртвые узлы вообще выпадают из кандидатов. Балансировщик проверяет каждый по трём условиям: живой (Alive), пинг меньше maxRTT, доля ошибок меньше tolerance. Не прошёл, его нет в выборке, множители к нему уже не применяются. Поэтому дорогая группа поднимается сама, когда дешёвая умерла: не нужно писать никаких «если TCP не отвечает, переключись». Всё, что осталось живого, участвует; costs лишь расставляет приоритет между живыми.

И ещё один параметр, без которого схема течёт: expected: 1. Он говорит «бери строго один лучший узел». Если его не задать, leastLoad наберёт несколько узлов и будет ходить по ним вперемешку, а это затянет в выборку и дорогие ноды, даже когда дешёвые в порядке. Один узел: один выбор.

4. Механизм 2: fallbackTag, и три его подводных камня

fallbackTag срабатывает в один момент: когда стратегия не вернула ни одного живого узла. Пул пуст, трафик уходит на запасной тег. Звучит просто, но каждый из трёх камней ниже стоил реальной отладки.

Камень 1: fallbackTag не может указывать на балансировщик, только на outbound. Балансировщик отдаёт строку-тег, которую диспетчер ищет в реестре outbound-хендлеров. А сами балансировщики лежат в другом реестре. Укажете фолбэк на тег балансера, в логе получите цепочку:

fallback to [POOL] → no qualified outbound → non existing outTag: POOL

...и трафик просто оборвётся. Фолбэк ведёт только на конкретный outbound (в нашем случае: на тег одной из нод обхода).

Камень 2: регистр имеет значение. Инжект хостов в Remnawave по правилу ^BYPASS$ создаёт outbound'ы с префиксом из шаблона: маленькими буквами (ihc...). Если в fallbackTag написать заглавными, узел не найдётся, и вы снова в цепочке non existing outTag. Реальный баг из практики: плитка годами имела нерабочий фолбэк, и никто не замечал, просто потому, что пул ни разу до конца не опустел. Пока есть хоть один живой узел, фолбэк не трогается, и опечатка спит.

Камень 3: фолбэк на узел внутри самого пула бесполезен. Соблазнительно поставить fallbackTag на основную группу: «пусть будет универсальная страховка». Но фолбэк срабатывает именно тогда, когда пул пуст, то есть когда все узлы пула мертвы. Страховка приведёт вас в такой же мёртвый узел. Смысл есть только в теге вне пула, в отдельной ноде обхода, которая в подборе по costs не участвует, но в реестре outbound живёт.

5. Ловушка, стоившая отдельной отладки: два observatory

В Xray два механизма проверки здоровья узлов: burstObservatory и обычный observatory со своим probeInterval. Возникает красивая идея: дешёвые ноды пинговать часто, дорогие редко, разнеся их по двум механизмам. Экономия же.

Идея не работает. Если заданы оба, leastLoad берёт данные только из обычного observatory. Burst-пробы при этом физически идут (в debug-логе честно видно burst: checking main), но балансировщик их не видит. И садится на дорогой резерв даже когда с дешёвыми всё исправно.

Отдельно стоит методическая ловушка, из-за которой в эту яму легко поверить. Если проверять схему на конфиге, где дешёвые ноды заглушены (для «чистоты эксперимента»), выбор дорогого узла выглядит правильным и создаёт ложный вывод, что балансер читает оба источника. Тест подтвердил неверную гипотезу. Проверять надо при всех живых узлах, и тогда видно, что дорогой узел выбирается ошибочно.

Вывод простой: все узлы держим в одном burstObservatory. Разделить частоту проб по группам нельзя: это ограничение, а не недоработка конфига.

6. Цена вопроса: пробы стоят трафика

Мониторинг не бесплатный. Каждая проба burstObservatory это новый Reality/TLS-хендшейк к узлу, соединение не переиспользуется. Замер делался счётчиками iptables на IP конкретных нод (правило без target, вставленное первым в цепочку, чтобы считать байты, но не менять маршрут).

Одна проба ≈ 8.8 КБ. Параметр sampling на объём трафика не влияет: это окно усреднения результатов, а не количество проб. Регулировать можно только интервал:

Интервал На клиента в месяц 34 клиента 100 клиентов
10 с 4.4 ГБ 150 ГБ 429 ГБ
30 с 1.5 ГБ 50 ГБ 143 ГБ
60 с 732 МБ 24 ГБ 71 ГБ
120 с 366 МБ 12 ГБ 36 ГБ

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

7. Тайминги: тоже замеренные

Хорошая новость: холодный старт не страдает. Первая проба выполняется сразу при запуске ядра, поэтому интернет появляется через ~4 секунды, и когда обычные ноды живы, и когда они уже мертвы (тогда сразу через резерв). Интервал проб на холодный старт не влияет вообще.

Ждать приходится только в одном сценарии (отказ посреди сессии): человек уже сидит в интернете, и тут его ноды срезают блокировкой. Вот тут интервал и sampling решают, сколько секунд клиент просидит без сети:

Настройка Простой клиента
interval 60 с, sampling 2 85 с
interval 60 с, sampling 1 72 с
interval 30 с, sampling 1 43 с
interval 15 с, sampling 1 30 с

Обратите внимание на sampling. sampling: 2 требует двух неудачных проб подряд и фактически удваивает простой, при этом трафик он не экономит (см. прошлый раздел). Переход на sampling: 1 это бесплатное ускорение вдвое, берите его не задумываясь.

И неприятная деталь для честности: пока узел не признан мёртвым, балансировщик продолжает слать в него трафик и перебирать таких же мёртвых соседей. Всё это время клиент без интернета. Мгновенного переключения не существует; вопрос только в том, 30 это секунд или 85.

8. Готовый конфиг с разбором

Три «этажа» плюс аварийный выход. Конфиг приводится целиком, это и есть та самая одна плитка:

{
  "remnawave": {
    "injectHosts": [
      { "selector": { "type": "tagRegex", "pattern": "^TAG_TCP$"   }, "tagPrefix": "main", "selectFrom": "ALL" },
      { "selector": { "type": "tagRegex", "pattern": "^TAG_GRPC$"  }, "tagPrefix": "grp",  "selectFrom": "ALL" },
      { "selector": { "type": "tagRegex", "pattern": "^TAG_XHTTP$" }, "tagPrefix": "xht",  "selectFrom": "ALL" },
      { "selector": { "type": "tagRegex", "pattern": "^TAG_BYPASS$" }, "tagPrefix": "ihc",  "selectFrom": "ALL" }
    ]
  },
  "burstObservatory": {
    "subjectSelector": ["main", "grp", "xht", "ihc"],
    "pingConfig": {
      "destination": "http://www.gstatic.com/generate_204",
      "interval": "30s",
      "timeout": "3s",
      "sampling": 1
    }
  },
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "balancers": [
      {
        "tag": "SMART",
        "selector": ["main", "grp", "xht", "ihc"],
        "strategy": {
          "type": "leastLoad",
          "settings": {
            "costs": [
              { "regexp": true, "match": "^(grp|xht)", "value": 12  },
              { "regexp": true, "match": "^ihc",       "value": 100 }
            ],
            "expected": 1,
            "maxRTT": "6s",
            "tolerance": 0.2
          }
        },
        "fallbackTag": "ihc"
      }
    ],
    "rules": [
      { "type": "field", "network": "tcp,udp", "balancerTag": "SMART" }
    ]
  }
}

Смысл трёх уровней:

  • main: обычные ноды по TCP, основной режим. Вес ×1, всегда в приоритете, пока живы.
  • grp / xht: те же ноды, но по gRPC и xHTTP. Другой транспорт часто проходит там, где TCP уже режут. Вес 12 (×3.5): включаются, когда TCP заметно просел или умер, но раньше дорогого обхода.
  • ihc: ноды обхода, вес 100 (×10), да ещё и роль последнего fallbackTag. Последняя линия и по costs, и по аварийному выходу.

Отдельно поговорим про префиксы: это ловушка на ровном месте. selector матчит по началу тега, поэтому префиксы не должны вкладываться друг в друга. Пара alt и alt2 сломается: alt захватит оба. Нужны непересекающиеся: main, grp, xht, ihc. Ни один не является началом другого.

9. Где это живёт в панели

Инжект собирает узлы из хостов по их тегам-инбаундам. Вот как хосты выглядят в панели, у каждого свой тег (VLESS-REALITY, STEAL-SHOP и т.д.), именно по нему injectHosts их и подхватывает:

Хосты и их теги-инбаунды в Remnawave

Теги задаются в Config Profile, наборе инбаундов, на которые ссылаются хосты. Профиль показывает, сколько в нём тегов и нод:

Config Profile: теги и ноды

Сам Xray-конфиг правится в JSON-редакторе прямо в панели, с проверкой валидности:

JSON-редактор Xray в панели

10. Как всё это проверять без риска для боевого сервиса

Три приёма, которыми схема отлаживалась, ни разу не тронув живых клиентов.

1. Проверка маршрутизации без подключения к нодам. Берём реальный рендер подписки и заменяем все outbound'ы на заглушки (freedom / blackhole), сохранив теги. Поднимаем Xray с socks-инбаундом и loglevel: info, ходим через socks обычным curl и смотрим в access-логе строки вида:

accepted tcp:site:443 [socks-in -> main]

Видно решение роутера (на какой тег он отправил), а боевые узлы не тронуты, чужие учётки не используются. Идеально, чтобы убедиться, что costs расставляет приоритет так, как задумано.

2. Имитация блокировки у оператора. Молчаливый дроп, как у реальных систем фильтрации:

iptables -I OUTPUT 1 -d <IP ноды> -j DROP

Так проверяется, что балансировщик действительно замечает мёртвый узел и уходит на соседний, а не висит на нём.

3. Замер трафика. Правило iptables без target, вставленное первым, считает байты и пропускает трафик дальше:

iptables -I OUTPUT 1 -d <IP ноды>

Именно -I ... 1 (в начало), а не -A (в конец): правило в конце может не сработать; до него не дойдёт очередь, если выше есть ACCEPT.

И практическая деталь: чтобы вообще увидеть настоящий рендер подписки, нужен User-Agent клиента и реальный x-hwid уже привязанного устройства. Без заголовка панель отдаёт заглушку «Приложение не поддерживает HWID», с чужим hwid: «Превышено количество устройств».

11. Грабли Remnawave

Собраны на практике, каждая стоила времени.

  • POST /api/subscription-templates игнорирует templateJson: создаётся шаблон-заглушка на ~700 байт. Содержимое заливается вторым шагом через PATCH. Симптом, если забыть: плитка рендерится как обычный сервер, без всякого балансировщика. (Проверено на практике: POST действительно кладёт пустышку, конфиг встал только через PATCH.)
  • Хост-ресивер (тот, что несёт шаблон) не инжектится в свой же пул: его теги надо оставлять пустыми, а узлы пула заводить отдельными скрытыми хостами.
  • serverDescription: максимум 30 символов. Больше не примет.
  • Видимость плитки ограничивается через excludedInternalSquads: перечисляются все сквады, кроме нужных (логика «от обратного»).
  • Плитку нельзя давать тарифу, где нет инбаундов обхода: fallbackTag укажет в пустоту, и трафик оборвётся вместо аварийного переключения. То есть ровно в тот момент, когда защита нужнее всего, её не будет.

12. Итог: что получилось и чего добиться нельзя

Получилось: одна плитка вместо десяти в подписке. Клиент сидит на дешёвых нодах, при их падении сам уходит на другой транспорт, а в крайнем случае, на дорогой обход с белым IP, и всё это выбирается на его устройстве под его оператора. Дорогие ноды поднимаются автоматически и только в аварию: costs держит их в самом конце очереди, пока живо хоть что-то дешевле.

Чего добиться нельзя, и с этим придётся жить:

  • Разделить частоту проб по группам нельзя: все узлы в одном burstObservatory, дорогие пингуются так же часто, как дешёвые. Пробы стоят трафика, и на сотне клиентов это сотни гигабайт.
  • Мгновенного переключения нет: при обрыве посреди сессии клиент простаивает от 30 до 85 секунд, смотря по интервалу.
  • Работает только на ядре Xray: Happ, v2rayNG, Streisand. У клиентов на Clash/sing-box/Shadowrocket это обычный список серверов.
  • fallbackTag ведёт ровно на один узел: не на группу и не на балансер.

Это честный потолок механизма. В его пределах SMART-балансировщик делает именно то, ради чего затевался: экономит трафик на дорогих нодах и включает обход сам, ровно тогда, когда обычные серверы перестали работать.

📖 Незнакомые слова? Все термины простыми словами смотрите в Словаре терминов.