Как настроить дедупликацию входящих DLR в SMPP-шлюзе

Как настроить дедупликацию входящих DLR в SMPP-шлюзе

Чтобы повторный DLR не запускал обработку заново, сохраняйте каждый отчёт в журнале и проверяйте его на дубликат до обновления статуса сообщения. Для этого нужны устойчивый идентификатор исходного SMS, нормализованный статус и правило обработки повторов. Ниже — схема, которая помогает принимать `deliver_sm`, отличать повторный отчёт от нового события и не терять итоговый статус при переподключении SMPP-сессии.

Почему шлюз присылает один DLR несколько раз?

DLR — отчёт о состоянии отправленного SMS. Шлюз передаёт его клиенту через `deliver_sm`. Если клиент не подтвердил получение или связь оборвалась до ответа, шлюз может повторить передачу. Приложение также может получить дубликат после перезапуска или повторной доставки данных из собственной очереди.

Поэтому факт получения `deliver_sm` ещё не означает, что отчёт обработан только один раз. Если каждый принятый DLR напрямую запускает действие, повтор может продублировать запись в CRM, уведомление оператору или обновление заказа. Сначала сохраняйте событие, затем решайте, меняет ли оно состояние SMS.

Для связи отчёта с исходным сообщением используйте `receipted_message_id`, если шлюз передаёт его в теле DLR или в дополнительном поле PDU. Формат идентификатора зависит от шлюза, поэтому при разборе нормализуйте его по правилам вашего подключения. `sequence_number` для дедупликации не подходит: он сопоставляет запрос и ответ в рамках SMPP-сессии, а после переподключения канал уже другой.

Как определить, что DLR уже обработан?

Создайте журнал входящих отчётов. Для каждого `deliver_sm` сохраните идентификатор исходного SMS, извлечённый статус, время приёма и исходные поля, нужные для проверки ошибок разбора. Отдельно храните состояние обработки: например, «получен», «применён» или «отклонён как повтор».

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

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

Как обработать `deliver_sm` и ответить шлюзу?

Разделите приём отчёта и бизнес-обработку. Сначала приложение разбирает PDU, извлекает идентификатор сообщения и статус, затем сохраняет событие с ключом дедупликации. После этого оно отвечает на `deliver_sm_resp` и передаёт событие внутреннему обработчику, который обновляет статус SMS.

  1. Прочитайте `deliver_sm` и проверьте, что в нём есть данные для идентификации отчёта.
  2. Преобразуйте идентификатор и статус к единому внутреннему формату.
  3. Попробуйте сохранить событие с уникальным ключом.
  4. При конфликте ключа отметьте отчёт как повторный и не запускайте действие повторно.
  5. При новой записи обновите состояние SMS и выполните связанные действия.

Ответ шлюзу не должен зависеть от того, успела ли завершиться вся бизнес-логика. Если обработка заказа занимает время, зафиксируйте DLR в очереди или журнале и подтвердите приём в соответствии с поведением вашего SMPP-клиента. Иначе длительная операция может привести к тайм-ауту и новой передаче того же отчёта.

Подробности разбора статусов и тайм-аутов удобно сверить с материалом о разборе DLR в SMPP. Для конкретной цепочки, например отправки статусов ремонта, пригодится пример настройки шлюза с DLR.

Как не испортить статус сообщения повтором или опоздавшим DLR?

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

Храните историю DLR отдельно от текущего состояния SMS. Тогда можно увидеть, что шлюз присылал несколько разных отчётов, и проверить порядок их поступления. Текущий статус вычисляйте по правилам переходов, а не по принципу «последняя запись всегда главная»: порядок доставки отчётов может отличаться от порядка событий.

При сбое сети очередь должна сохранять сообщение и внутренний идентификатор, а после восстановления соединения приложение продолжает обработку существующих записей. Не создавайте новую запись SMS только потому, что SMPP-сессия переподключилась: иначе DLR может относиться к старому сообщению, а система начнёт учитывать его как новое. Для обработки сбоев проверьте также правила очереди и повторной отправки в статье о статусах, тайм-аутах и повторах DLR.

Какие ошибки чаще всего приводят к дублям?

  • Использовать `sequence_number` как постоянный ключ. После переподключения сессии он не идентифицирует сообщение.
  • Считать каждую команду `deliver_sm` новым отчётом. Сначала проверьте ключ дедупликации.
  • Сравнивать идентификаторы без нормализации. Различия в представлении одного ID могут дать ложное впечатление, что это разные сообщения.
  • Обновлять статус до сохранения события. При сбое приложение потеряет возможность понять, применяло ли оно DLR.
  • Безусловно заменять статус последним принятым отчётом. Поздний отчёт может относиться к более раннему этапу доставки.

Начните с теста на одном SMS: сохраните первый DLR, отправьте тот же отчёт повторно и проверьте, что журнал отметил повтор, а действие выполнилось один раз. Затем протестируйте разные статусы для того же идентификатора и зафиксируйте допустимые переходы. Такой сценарий быстро покажет, разделяет ли ваша система приём отчёта, защиту от дублей и обновление состояния.