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

Введение: что происходит за кулисами маршрутизации SMS с сопоставлением операторов

Большинство SMS-провайдеров описывают маршрутизацию как чёрный ящик:

«Мы выберем лучший маршрут на основе качества и цены.»

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

  • Какой путь прошло сообщение.
  • Какой отправитель был использован.
  • Почему система выбрала именно эту комбинацию.
  • Как система поведёт себя при сбоях и пиковых нагрузках.

Маршрутизация с сопоставлением операторов — одна из ключевых причин, по которым одни шлюзы стабильно достигают доставляемости 99,4%+ в сложных вертикалях, тогда как другие застревают на уровне низких и средних 90-х. В этой статье мы детально разберём архитектуру:

  • Слой интеллекта (определение оператора и типа линии).
  • Движок принятия решений о маршрутизации.
  • Выбор пула/грида и логика ротации.
  • Стратегии резервных маршрутов.
  • Наблюдаемость и отладка.

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


Раздел 1: Что на самом деле означает «сопоставление операторов»

На высоком уровне сопоставление операторов — это:

Для каждого номера назначения выбрать отправителя и маршрут, которые наилучшим образом соответствуют оператору и контексту получателя.

Вместо того чтобы:

  • Отправлять всё через самые дешёвые универсальные маршруты.
  • Смешивать всех операторов и сценарии использования на одних и тех же отправителях.

Цель сопоставления операторов:

  • Использовать проверенных отправителей Verizon для абонентов Verizon.
  • Использовать проверенных отправителей AT&T для абонентов AT&T.
  • Сохранять репутацию на уровне каждого оператора изолированной и предсказуемой.

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

  • Прирост доставляемости на 3–12 процентных пунктов у конкретных операторов по сравнению с универсальной маршрутизацией.
  • Меньший разброс показателей со временем.
  • Более чистый анализ первопричин при возникновении проблем (один оператор, один грид).

Раздел 2: Высокоуровневая архитектура

SMS-шлюз с сопоставлением операторов, как правило, включает следующие компоненты:

  1. Ingress API

    • Принимает запросы на отправку сообщений (/messages).
    • Валидирует полезную нагрузку, авторизацию и базовую схему данных.
  2. Нормализация и обогащение

    • Нормализует номера телефонов (E.164).
    • Обогащает данными:
      • Информация об операторе.
      • Страна/регион.
      • Тип линии (мобильная, VoIP, стационарная, если доступно).
      • Сигналы риска.
  3. Движок принятия решений о маршрутизации

    • На основе обогащённого контекста и метаданных приложения:
      • Выбирает профиль маршрута (например, OTP_US, Promo_EU).
      • Выбирает пул/грид.
      • Определяет отправителя внутри этого грида.
    • Применяет правила на уровне оператора и грида.
  4. Очередь и диспетчеризация

    • Помещает сообщения в очереди для каждого маршрута.
    • Применяет:
      • Ограничение скорости.
      • Контроль всплесков.
      • Стратегии повторных попыток.
  5. Квитанции о доставке и обратная связь

    • Принимает DLR (квитанции о доставке).
    • Обновляет:
      • Состояние здоровья пула/грида.
      • Метрики репутации отправителя.
    • Передаёт данные обратно в движок маршрутизации.
  6. Плоскость наблюдаемости

    • Метрики, логи, трассировки.
    • Поддерживает запросы по:
      • Оператору.
      • Пулу/гриду.
      • Отправителю.
      • Кампании.

Раздел 3: Слой интеллекта об операторах

Прежде чем сопоставлять операторов, нужно знать их.

Входные данные

  • Номер телефона в формате E.164.
  • Опционально:
    • Код страны из контекста приложения.
    • Известные метаданные пользователя (например, ранее определённый оператор).

Источники

  • Провайдеры HLR / поиска оператора.
  • API-интеллект по номерам телефонов.
  • Внутренние кэши (недавно определённые номера).

Выходные данные

Для заданного получателя:

  • carrier_id: например, verizon_us, att_us, tmobile_us, o2_uk и т.д.
  • country_code: US, GB, DE и т.д.
  • line_type: mobile, fixed, voip (если доступно).
  • risk_flags: недавний перенос номера, подозрительные диапазоны и т.д. (опционально).

Стратегия кэширования

  • Прогрев кэша для:
    • Часто используемых направлений.
    • Известных активных отправителей (например, пользователей с интенсивной отправкой OTP).
  • Соблюдение:
    • Ограничений скорости провайдера поиска.
    • Требований к актуальности данных.

Пример (псевдокод):

type CarrierInfo = {
  carrierId: string;
  country: string;
  lineType?: string;
  lastUpdated: number;
};

async function resolveCarrier(msisdn: string): Promise<CarrierInfo> {
  const cached = await carrierCache.get(msisdn);
  if (cached && Date.now() - cached.lastUpdated < CACHE_TTL_MS) {
    return cached;
  }

  const lookup = await externalLookup(msisdn);

  const info: CarrierInfo = {
    carrierId: lookup.carrierId,
    country: lookup.countryCode,
    lineType: lookup.lineType,
    lastUpdated: Date.now(),
  };

  carrierCache.set(msisdn, info);
  return info;
}

Раздел 4: Проектирование движка принятия решений о маршрутизации

Исходные данные:

  • Обогащённый контекст сообщения (CarrierInfo, страна, метаданные приложения).
  • Тип сообщения (OTP, транзакционное, маркетинговое).
  • Конфигурация клиента/аккаунта.

Движок маршрутизации должен выбрать:

  1. Профиль маршрута

    • Например, OTP_US, PROMO_US, ALERT_EU и т.д.
    • Инкапсулирует:
      • Предпочтительные операторы/маршруты.
      • Ограничения пропускной способности.
      • Допустимые типы отправителей.
  2. Пул / грид

    • Например, US_OTP_VERIZON_GRID_A, US_PROMO_ATT_GRID_B.
    • Каждый грид:
      • Представляет набор SIM-карт/номеров.
      • Имеет метрики ёмкости и состояния здоровья на уровне оператора.
  3. Отправитель внутри грида

    • Выбирается на основе:
      • Стратегии ротации.
      • Состояния здоровья.
      • Локальных ограничений.

Процесс принятия решений (упрощённо)

function routeMessage(msg: Message, carrier: CarrierInfo): RouteDecision {
  const profile = selectProfile(msg, carrier);

  const candidateGrids = findEligibleGrids(profile, carrier);

  const grid = selectBestGrid(candidateGrids);

  const sender = pickSenderFromGrid(grid, msg);

  return { profileId: profile.id, gridId: grid.id, senderId: sender.id };
}

Где:

  • selectProfile использует:

    • Тип сообщения (OTP против промо).
    • Страну/регион.
    • Риск/вертикаль (например, crypto/adult).
  • findEligibleGrids фильтрует по:

    • Стране.
    • Совместимости с оператором.
    • Порогам состояния здоровья.
  • selectBestGrid может:

    • Отдавать предпочтение гридам с:
      • Нормальными показателями ошибок и жалоб.
      • Доступной ёмкостью.
    • Избегать:
      • Гридов, приближающихся к пороговым значениям.
  • pickSenderFromGrid:

    • Реализует ротацию:
      • Round-robin.
      • Взвешенную.
      • С учётом состояния здоровья (избегать проблемных отправителей).

Раздел 5: Пулы/гриды и логика ротации

Грид как основная единица изоляции

Грид может определяться по:

  • Региону: US.
  • Составу операторов: только Verizon, только AT&T, мультиоператорный.
  • Сценарию использования: OTP, PROMO, ALERT.
  • Уровню приоритета.

Каждый грид отслеживает:

  • Общее количество отправок.
  • Разбивку по доставленным/недоставленным.
  • Коды жёстких ошибок.
  • Показатели жалоб/отписок.

Стратегии ротации

Простейшая:

  • Round-robin по активным отправителям.

Лучше:

  • Ротация с учётом состояния здоровья:
    • Пропускать отправителей с:
      • Высоким уровнем ошибок за последнее время.
      • Высокой долей жалоб.
    • Отдавать вес в пользу:
      • Новых, здоровых отправителей.

Пример:

function pickSenderFromGrid(grid: GridState): Sender {
  const healthy = grid.senders.filter((s) => s.healthScore > MIN_HEALTH);
  const weighted = buildWeightedList(healthy, (s) => s.weight);
  return randomChoice(weighted);
}

Где:

  • healthScore основывается на:
    • Недавнем уровне доставки.
    • Уровне жёстких ошибок.
    • Уровне жалоб.
    • Времени с последней верификации/прогрева.

Вывод из ротации и период охлаждения

Внедрите правила вида:

  • Вывести или охладить отправителя, когда:
    • Жёсткие ошибки > 1–2% за последние N сообщений.
    • Жалобы > 0,3–0,5% за период.
    • Специфические для оператора коды ошибок резко возрастают.

Выведенные из ротации отправители:

  • Исключаются из активной ротации.
  • Могут быть протестированы позднее на небольших, безопасных объёмах трафика.

Раздел 6: Резервные маршруты, повторные попытки и режимы сбоев

Даже при грамотной маршрутизации случаются сбои:

  • Операторы испытывают аутажи.
  • Конкретные маршруты деградируют.
  • Грид временно «сгорает».

Принципы резервных маршрутов

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

    • Переход с Grid A → Grid B в рамках одного профиля/страны.
    • Оставлять OTP на OTP-гридах, промо — на промо-гридах.
  2. Избегать немедленных повторных попыток по тому же сломанному пути

    • Применять агрессивный backoff:
      • Экспоненциальный или линейный.
    • Помечать неработающие маршруты/гриды как деградированные.
  3. Плавная деградация

    • Для OTP:
      • Попробовать альтернативного отправителя в рамках той же семьи операторов.
      • Рассмотреть более медленный, но надёжный резервный маршрут.
    • Для промо:
      • Снизить скорость отправки.
      • Отложить отправки, если операторы явно нестабильны.

Пример логики повторных попыток (упрощённо)

async function dispatchMessage(decision: RouteDecision, msg: Message) {
  try {
    const result = await sendToCarrier(decision, msg);

    updateMetrics(decision, result);
    return result;
  } catch (err) {
    markRouteAsDegraded(decision, err);

    const fallbackDecision = findFallback(decision, msg);
    if (!fallbackDecision) throw err;

    const fallbackResult = await sendToCarrier(fallbackDecision, msg);
    updateMetrics(fallbackDecision, fallbackResult);
    return fallbackResult;
  }
}

Раздел 7: Наблюдаемость, логирование и отладка

Маршрутизация с сопоставлением операторов хороша ровно настолько, насколько хороша её наблюдаемость.

Вы должны иметь возможность задать вопросы вроде:

  • «Покажи все сообщения в Verizon за последние 24 часа, маршрутизированные через Grid A против Grid B.»
  • «У каких отправителей в Grid C самый высокий уровень жёстких ошибок?»
  • «Что изменилось в момент, когда упала доставляемость?»

Минимальный набор полей в логах

Для каждого сообщения:

  • message_id
  • timestamp
  • customer_id (или ID проекта/приложения)
  • destination_msisdn (хэшированный/псевдонимизированный при необходимости)
  • carrier_id
  • country_code
  • profile_id
  • grid_id
  • sender_id
  • route_id / upstream ID
  • status (queued, sent, delivered, failed, unknown)
  • error_code (если есть)
  • dlr_timestamp
  • latency_ms
  • campaign_id или flow_id (если применимо)

Дашборды

  • Тепловые карты оператор × грид:
    • Уровень доставки.
    • Уровень жёстких ошибок.
  • Рейтинги отправителей:
    • Отсортированные по состоянию здоровья и пропускной способности.
  • Обнаружение аномалий:
    • Алерты при:
      • Падении уровня доставки оператора X, грида Y ниже порога.
      • Всплеске кодов ошибок.

Пример рабочего процесса при инциденте

  1. Алерт: «Доставляемость Verizon упала более чем на 3 пункта в гриде US_PROMO_A.»
  2. Используем логи:
    • Проверяем коды ошибок и объёмы.
    • Сравниваем с другими гридами.
  3. Митигация:
    • Временно переводим промо-трафик Verizon на Grid US_PROMO_B.
    • Снижаем скорость отправки.
  4. Расследование:
    • Недавние изменения контента/шаблонов.
    • Изменения в конфигурации маршрутизации.

FAQ: Маршрутизация с сопоставлением операторов для разработчиков

1. Нужен ли нам HLR/поиск для каждого сообщения?

Не обязательно.

Варианты:

  • Кэшировать результаты с разумным TTL.
  • Выполнять разрешение заранее для высокочастотных пользователей.
  • Выполнять пакетный поиск при заполнении гридов.

2. Как обрабатывать перенос номеров?

Перенесённые номера могут сменить оператора. Лучшие практики:

  • Периодически обновлять информацию об операторе для:
    • Часто используемых направлений.
    • Номеров с повторяющимися сбоями.

3. Актуально ли сопоставление операторов только для США?

Нет. Это особенно полезно:

  • Везде, где несколько операторов ведут себя по-разному.
  • Там, где sender ID и шаблоны специфичны для каждого оператора (многие рынки ЕС/APAC).

4. Как это взаимодействует с A2P 10DLC и зарегистрированными кампаниями?

Сопоставление операторов:

  • Корректно использует зарегистрированные кампании и отправителей для каждого оператора.
  • Помогает оставаться в рамках ожидаемых пропускной способности и содержания для каждой кампании.

5. А как насчёт приватности и персональных данных?

Реализация с приоритетом конфиденциальности:

  • Хэширует MSISDN в логах.
  • Хранит минимум данных.
  • Сохраняет метаданные оператора и маршрутизации, но не исходное содержимое.

6. Можно ли наложить сопоставление операторов поверх существующего CPaaS?

Иногда:

  • Если CPaaS предоставляет:
    • Управление на уровне каждого оператора.
    • Статистику на уровне каждого отправителя.
  • Можно построить мета-слой маршрутизации поверх.

Но наиболее мощные реализации требуют собственной инфраструктуры (SIM-карты, частные гриды).


Заключение: от маршрутизации «как получится» к инженерной маршрутизации

Большинство SMS-программ живут на маршрутизации «как получится»:

  • Провайдер выбирает дешёвые/доступные маршруты.
  • Вы получаете 1–2 метрики.
  • И надеетесь на лучшее.

Маршрутизация с сопоставлением операторов превращает SMS в инженерную систему:

  • Детерминированный выбор пути на уровне каждого оператора.
  • Изолированные гриды и пулы.
  • Ротация и резервные маршруты с учётом состояния здоровья.
  • Богатая наблюдаемость для разбора инцидентов.

Если вам важно:

  • Достигать и удерживать доставляемость 99,4%+.
  • Переживать промо-всплески и высокорисковые сценарии использования.
  • Давать вашей команде SRE/инфра рычаги управления, которые они могут понять и которым можно доверять.

…то внедрение или выбор шлюза с серьёзной архитектурой сопоставления операторов — это не опция «приятно иметь», а единственная разумная долгосрочная стратегия.

Dach SMS Lab

Dach SMS Lab