Перейти к содержанию

Потоки обмена

Статья описывает, какими путями данные движутся через платформу при основных сценариях обмена: отправка сообщения, получение, подтверждение, повторная обработка, файловый обмен. Поток описывается на уровне «какой компонент с каким взаимодействует и в каком порядке», без углубления в код. Конкретные API-эндпоинты — в Транспортном API; внутреннее устройство — в Архитектуре сервиса и Работе с очередями.

Базовые участники

В каждом потоке участвуют (но не все одновременно):

  • ИС-отправитель — клиентская система, шлющая сообщение.
  • ИС-получатель — клиентская система, забирающая сообщение.
  • nginx — единая точка входа извне, TLS-терминация.
  • backend (Exchange-контур) — Сервер DataHUB, обрабатывающий обменные запросы.
  • PostgreSQL — конфигурация обмена (системы, пакеты, маршруты, права).
  • RabbitMQ — очереди сообщений.
  • S3-хранилище — для бинарных вложений (см. Файловое хранилище).

Поток 1. Отправка сообщения

Базовый, самый частый поток: ИС-отправитель публикует сообщение в шину.

sequenceDiagram
    participant S as ИС-отправитель
    participant N as nginx
    participant B as backend
    participant C as Caffeine
    participant P as PostgreSQL
    participant R as RabbitMQ

    S->>N: ① POST /api/.../send · Bearer token
    N->>B: TLS terminate
    B->>B: ② JWT-валидация
    B->>C: ③ маршруты из кеша
    alt кеш miss
        C->>P: SELECT маршруты
        P-->>C: маршруты
    end
    B->>R: ④ публикация копий в очереди получателей
    B-->>N: ⑤ 200 OK + accepted id
    N-->>S: 200 OK + accepted id

Ключевые моменты:

  • Шаг ②. Валидация JWT отправителя — синхронная, не зависит от состояния других компонентов. Подробнее — Безопасность.
  • Шаг ③. Маршруты читаются из кеша (Caffeine) с TTL и инвалидацией. Holdup на PostgreSQL — только если в кеше нет.
  • Шаг ④. Для каждого подписанного получателя — отдельная копия в его очереди. Если подписчиков N, в RabbitMQ кладётся N копий с одинаковым id. Это реализация модели «очередь на получателя» из Очередей сообщений.
  • Шаг ⑤. Ответ отправителю — после успешной публикации в RMQ. Если шаг ④ упал — отправитель получит ошибку и должен повторить.
  • В БД шины при отправке ничего не пишется — централизованного «лога доставки сообщений» нет. Видимость прохождения реализована на стороне клиентского модуля (для 1С — отдельная страница статусов). Централизованный трекинг на стороне шины — в roadmap.

Поток 2. Получение сообщения

ИС-получатель забирает сообщения из своей очереди.

sequenceDiagram
    participant R as ИС-получатель
    participant N as nginx
    participant B as backend
    participant MQ as RabbitMQ

    R->>N: ① GET /api/.../receive · Bearer token
    N->>B: TLS terminate
    B->>B: ② JWT-валидация
    B->>MQ: ③ read из очереди получателя
    MQ-->>B: сообщения
    Note over MQ: ④ сообщения in-flight — не удалены, ждут ACK
    B-->>N: ⑤ 200 OK + список сообщений
    N-->>R: список сообщений

Ключевые моменты:

  • Шаг ③. Backend читает сообщения из той RabbitMQ-очереди, которая закреплена за этим получателем. Имена очередей в RMQ детерминированы — см. Работа с очередями.
  • Шаг ④. Сообщение остаётся в очереди в состоянии «in-flight» до явного подтверждения. Если получатель упадёт до подтверждения — сообщение вернётся в очередь и будет выдано заново.
  • Получатель может работать в режиме short-polling (опрос с интервалом) или long-polling (соединение держится открытым, пока сообщение не появится).

Поток 3. Подтверждение приёма

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

sequenceDiagram
    participant R as ИС-получатель
    participant N as nginx
    participant B as backend
    participant MQ as RabbitMQ

    Note over R: сохранила сообщение в локальный пул,<br/>закоммитила транзакцию
    R->>N: ① POST /api/.../ack · {messageId}
    N->>B: TLS terminate
    B->>MQ: ② basic.ack
    MQ-->>MQ: удалить сообщение из очереди
    B-->>N: ③ 200 OK
    N-->>R: 200 OK

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

После basic.ack сообщение окончательно удаляется из очереди RabbitMQ. В БД шины никакой записи об этом не появляется — централизованного лога доставки нет (см. пометку в Потоке 1).

Ключевые моменты:

  • Шаг ①. Подтверждение отправляется после коммита транзакции сохранения сообщения в локальный пул входящих — это инвариант модели at-least-once. Прикладная обработка ещё не выполнена и шину не интересует.
  • Если ① не пришёл (получатель упал, сеть оборвалась до отправки ACK) — сообщение в RabbitMQ остаётся в состоянии in-flight; через таймаут брокера возвращается в очередь; будет выдано заново (см. поток 5).
  • Если шаг ② упал между «отдали ACK получателю» и «сделали basic.ack в RMQ» — сообщение тоже вернётся, и получатель увидит его второй раз. Защита — идемпотентность вставки в локальный пул (уникальный индекс по id сообщения), см. Идемпотентность.
  • Если ошибка возникнет позже, при прикладной обработке из пула — шина уже ни при чём, сообщение из очереди удалено. Ретраи обработки идут внутри модуля по его настройкам.

Поток 4. Веерная рассылка

Один отправитель → N получателей. Поток отправки тот же, что и в Потоке 1, но шаг ④ кладёт N физических копий сообщения — по одной в каждую очередь подписчиков.

flowchart LR
    S[ИС-отправитель] --> B[backend]
    B --> MQ[RabbitMQ]
    MQ --> A["очередь получателя A  (id = 42)"]
    MQ --> Bb["очередь получателя B  (id = 42)"]
    MQ --> C["очередь получателя C  (id = 42)"]

Каждая копия живёт независимо: получатель A может уже подтвердить, B — ещё в очереди, C — в in-flight у медленного консьюмера. У всех трёх — одинаковый id (идентификатор исходного сообщения), что нужно для дедупликации на стороне получателей и для согласования действий между ними, если бизнес-логика этого требует.

Поток 5. Повторная доставка после сбоя

Сценарий: сообщение было выдано, ACK не пришёл.

sequenceDiagram
    participant MQ as RabbitMQ
    participant R as ИС-получатель

    Note over MQ: ⏱ timeout in-flight
    MQ->>MQ: возврат в исходную очередь
    MQ->>R: следующий receive — то же сообщение (тот же id)
    Note over R: идемпотентный обработчик распознаёт дубликат
    R->>MQ: ACK

Никакой отдельной retry-очереди или DLQ в типовой схеме не используется — повтор это та же самая запись в той же самой очереди. См. Концепции / Жизненный цикл сообщения.

Поток 6. Файловый обмен

Файл передаётся отдельно от сообщения, через S3-хранилище. В payload сообщения — только ссылка.

sequenceDiagram
    participant S as ИС-отправитель
    participant S3 as S3-хранилище
    participant B as backend
    participant MQ as RabbitMQ
    participant R as ИС-получатель

    S->>S3: ① загрузка файла
    S3-->>S: URL + метаданные (размер, md5)
    S->>B: ② POST /send · ссылка и метаданные в payload
    B->>MQ: публикация (Поток 1)
    MQ->>R: ③ сообщение со ссылкой
    R->>S3: ④ скачивание файла по ссылке
    S3-->>R: файл

Шина не участвует в шагах ① и ④ — она только перевозит ссылку. Файл может быть выдан получателю много раз (например, при повторных обработках или просмотрах), не дублируясь в трафике шины. Подробнее — Файловое хранилище и Концепции / Файловый обмен.

Поток 7. Обмен с офлайн-получателем

sequenceDiagram
    participant S as ИС-отправитель
    participant B as backend
    participant MQ as RabbitMQ
    participant R as "ИС-получатель X"

    S->>B: сообщение
    B->>MQ: публикация в очередь X
    Note over MQ,R: получатель офлайн — сообщения накапливаются ⏳
    R->>B: вернулась онлайн, receive
    B->>MQ: read пачками
    MQ-->>B: накопленные сообщения
    B-->>R: сообщения пачками
    R->>B: ACK (Поток 3)
    Note over MQ: очередь рассасывается до нуля

Для отправителя и backend поток не отличается от обычного — это нормальный режим работы.

Что дальше