Глубокое погружение для разработчиков: архитектура и алгоритмы маршрутизации SMS с сопоставлением операторов
Введение: что происходит за кулисами маршрутизации SMS с сопоставлением операторов
Большинство SMS-провайдеров описывают маршрутизацию как чёрный ящик:
«Мы выберем лучший маршрут на основе качества и цены.»
Если вы инженер или архитектор, ответственный за бесперебойность работы, этого недостаточно. Вам нужно знать:
- Какой путь прошло сообщение.
- Какой отправитель был использован.
- Почему система выбрала именно эту комбинацию.
- Как система поведёт себя при сбоях и пиковых нагрузках.
Маршрутизация с сопоставлением операторов — одна из ключевых причин, по которым одни шлюзы стабильно достигают доставляемости 99,4%+ в сложных вертикалях, тогда как другие застревают на уровне низких и средних 90-х. В этой статье мы детально разберём архитектуру:
- Слой интеллекта (определение оператора и типа линии).
- Движок принятия решений о маршрутизации.
- Выбор пула/грида и логика ротации.
- Стратегии резервных маршрутов.
- Наблюдаемость и отладка.
Это не маркетинг вендора. Это практическая архитектура, которая, по нашему опыту, работает при обработке миллионов сообщений в день.
Раздел 1: Что на самом деле означает «сопоставление операторов»
На высоком уровне сопоставление операторов — это:
Для каждого номера назначения выбрать отправителя и маршрут, которые наилучшим образом соответствуют оператору и контексту получателя.
Вместо того чтобы:
- Отправлять всё через самые дешёвые универсальные маршруты.
- Смешивать всех операторов и сценарии использования на одних и тех же отправителях.
Цель сопоставления операторов:
- Использовать проверенных отправителей Verizon для абонентов Verizon.
- Использовать проверенных отправителей AT&T для абонентов AT&T.
- Сохранять репутацию на уровне каждого оператора изолированной и предсказуемой.
Преимущества на практике:
- Прирост доставляемости на 3–12 процентных пунктов у конкретных операторов по сравнению с универсальной маршрутизацией.
- Меньший разброс показателей со временем.
- Более чистый анализ первопричин при возникновении проблем (один оператор, один грид).
Раздел 2: Высокоуровневая архитектура
SMS-шлюз с сопоставлением операторов, как правило, включает следующие компоненты:
-
Ingress API
- Принимает запросы на отправку сообщений (
/messages). - Валидирует полезную нагрузку, авторизацию и базовую схему данных.
- Принимает запросы на отправку сообщений (
-
Нормализация и обогащение
- Нормализует номера телефонов (E.164).
- Обогащает данными:
- Информация об операторе.
- Страна/регион.
- Тип линии (мобильная, VoIP, стационарная, если доступно).
- Сигналы риска.
-
Движок принятия решений о маршрутизации
- На основе обогащённого контекста и метаданных приложения:
- Выбирает профиль маршрута (например, OTP_US, Promo_EU).
- Выбирает пул/грид.
- Определяет отправителя внутри этого грида.
- Применяет правила на уровне оператора и грида.
- На основе обогащённого контекста и метаданных приложения:
-
Очередь и диспетчеризация
- Помещает сообщения в очереди для каждого маршрута.
- Применяет:
- Ограничение скорости.
- Контроль всплесков.
- Стратегии повторных попыток.
-
Квитанции о доставке и обратная связь
- Принимает DLR (квитанции о доставке).
- Обновляет:
- Состояние здоровья пула/грида.
- Метрики репутации отправителя.
- Передаёт данные обратно в движок маршрутизации.
-
Плоскость наблюдаемости
- Метрики, логи, трассировки.
- Поддерживает запросы по:
- Оператору.
- Пулу/гриду.
- Отправителю.
- Кампании.
Раздел 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, транзакционное, маркетинговое).
- Конфигурация клиента/аккаунта.
Движок маршрутизации должен выбрать:
-
Профиль маршрута
- Например,
OTP_US,PROMO_US,ALERT_EUи т.д. - Инкапсулирует:
- Предпочтительные операторы/маршруты.
- Ограничения пропускной способности.
- Допустимые типы отправителей.
- Например,
-
Пул / грид
- Например,
US_OTP_VERIZON_GRID_A,US_PROMO_ATT_GRID_B. - Каждый грид:
- Представляет набор SIM-карт/номеров.
- Имеет метрики ёмкости и состояния здоровья на уровне оператора.
- Например,
-
Отправитель внутри грида
- Выбирается на основе:
- Стратегии ротации.
- Состояния здоровья.
- Локальных ограничений.
- Выбирается на основе:
Процесс принятия решений (упрощённо)
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: Резервные маршруты, повторные попытки и режимы сбоев
Даже при грамотной маршрутизации случаются сбои:
- Операторы испытывают аутажи.
- Конкретные маршруты деградируют.
- Грид временно «сгорает».
Принципы резервных маршрутов
-
Приоритет родственным резервам
- Переход с Grid A → Grid B в рамках одного профиля/страны.
- Оставлять OTP на OTP-гридах, промо — на промо-гридах.
-
Избегать немедленных повторных попыток по тому же сломанному пути
- Применять агрессивный backoff:
- Экспоненциальный или линейный.
- Помечать неработающие маршруты/гриды как деградированные.
- Применять агрессивный backoff:
-
Плавная деградация
- Для OTP:
- Попробовать альтернативного отправителя в рамках той же семьи операторов.
- Рассмотреть более медленный, но надёжный резервный маршрут.
- Для промо:
- Снизить скорость отправки.
- Отложить отправки, если операторы явно нестабильны.
- Для 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_idtimestampcustomer_id(или ID проекта/приложения)destination_msisdn(хэшированный/псевдонимизированный при необходимости)carrier_idcountry_codeprofile_idgrid_idsender_idroute_id/ upstream IDstatus(queued, sent, delivered, failed, unknown)error_code(если есть)dlr_timestamplatency_mscampaign_idилиflow_id(если применимо)
Дашборды
- Тепловые карты оператор × грид:
- Уровень доставки.
- Уровень жёстких ошибок.
- Рейтинги отправителей:
- Отсортированные по состоянию здоровья и пропускной способности.
- Обнаружение аномалий:
- Алерты при:
- Падении уровня доставки оператора X, грида Y ниже порога.
- Всплеске кодов ошибок.
- Алерты при:
Пример рабочего процесса при инциденте
- Алерт: «Доставляемость Verizon упала более чем на 3 пункта в гриде US_PROMO_A.»
- Используем логи:
- Проверяем коды ошибок и объёмы.
- Сравниваем с другими гридами.
- Митигация:
- Временно переводим промо-трафик Verizon на Grid US_PROMO_B.
- Снижаем скорость отправки.
- Расследование:
- Недавние изменения контента/шаблонов.
- Изменения в конфигурации маршрутизации.
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