Инженерный контур 1С. Часть 6А - Буферизация входящей нагрузки, транзакционный Outbox, идемпотентные потребители и обеспечение устойчивости 1C:ERP 2.5 | infolimp.ru

Инженерный контур 1С. Часть 6А - Буферизация входящей нагрузки, транзакционный Outbox, идемпотентные потребители и обеспечение устойчивости 1C:ERP 2.5

5 сентября 2026 · infolimp.ru

Реализация высоконадежного интеграционного контура для высоконагруженной 1C:ERP 2.5 с выносом внешнего трафика (маркетплейсы, логистические провайдеры 3PL, складские ТСД) в брокер сообщений RabbitMQ. Реализация архитектурного паттерна Transactional Outbox на уровне прикладного кода и СУБД, гарантия доставки at-least-once, параллельная идемпотентная обработка и ликвидация каскадных транзакционных блокировок ядра.

Об иллюстративном кейсе. Сквозной пример «Торговый контур» в этой статье - обобщённый собирательный сценарий, а не описание конкретной компании или проекта. Цифры и симптомы типичны для нагруженных инсталляций 1С:ERP/КА с СУБД PostgreSQL на Linux и приведены для наглядности инженерных решений, а не как отчёт о конкретном внедрении.

Контекст и аудитория


В чём проблема

1. Ловушка синхронного взаимодействия по протоколу HTTP/REST

Синхронный вызов HTTP-сервиса связывает жизненный цикл внешнего сетевого клиента с жизненным циклом серверного потока 1С и транзакцией базы данных.

Синхронный HTTP-вызов (Антипаттерн прямого сопряжения):
[Маркетплейс / Внешний клиент] 
        | (HTTP POST /orders)
        ▼
[Пул соединений rphost] --(Захват рабочего потока)--+
        |                                           | Поток заблокирован на 4.8 с
        ▼                                           | (ожидание I/O, блокировок)
[Транзакция PostgreSQL] --(Блокировка ТоварыНаСкладах)-+
        |
        ▼ (HTTP 200 OK)
[Клиент получает ответ через 4.8 с]

При синхронной схеме возникают четыре системных противоречия:

  1. Прямое воздействие внешних сетевых задержек на ресурсы сервера приложений: Сервер 1С вынужден удерживать выделенный поток операционной системы и сеансовую память на всем протяжении сетевого взаимодействия с клиентом. Если у внешнего клиента наблюдается медленное TCP-соединение (высокий RTT) или клиент задерживает отправку чанков тела запроса, рабочий процесс rphost простаивает в состоянии ожидания сетевого ввода-вывода (Network I/O Wait), удерживая лицензию и оперативную память.

  2. Быстрое исчерпание пула рабочих процессов rphost: В соответствии с законом Литтла (Little's Law), среднее число одновременно занятых рабочих потоков $L$ в стационарной системе выражается произведением интенсивности входящего потока запросов $\lambda$ на среднее время обработки запроса $W$:

$$L = \lambda \cdot W$$

Если в штатном режиме при $\lambda = 50 \text{ запр/сек}$ и времени проведения документа $W = 0.2 \text{ с}$ системе требуется $L = 50 \cdot 0.2 = 10 \text{ потоков}$, то при возникновении задержки блокировок на регистрах накопления до $W = 4.0 \text{ с}$ требуемое число потоков возрастает лавинообразно:

$$L_{\text{peak}} = 50 \cdot 4.0 = 200 \text{ потоков}$$

Поскольку максимальный пул доступных сеансов в кластере 1С физически ограничен аппаратными ресурсами, наступает состояние полного голодания пула потоков (Thread Starvation). В этот момент интерактивные пользователи получают системную ошибку «Превышено максимальное число соединений с сервером».

  1. Отсутствие буферизации входящей нагрузки (Backpressure): При синхронной интеграции интенсивность нагрузки на реляционную СУБД жестко продиктована внешними генераторами трафика. Всплеск заказов (например, старт распродажи) мгновенно транслируется в поток параллельных транзакций INSERT и UPDATE в таблицы остатков PostgreSQL. Возникает каскадный рост очередей блокировок СУБД, деградация процессорного кэша и рост задержек дисковой подсистемы.

  2. Проблема двойной записи (Dual Write Problem) при попытке синхронной нотификации: Если в процессе проведения документа учетной системе требуется уведомить внешнюю службу (например, отправить статус агрегации маркировки в шлюз «Честного Знака»), синхронный вызов из тела транзакции приводит к невозможности гарантировать консистентность:

text Попытка синхронной нотификации из транзакции: НачатьТранзакцию(); Документ.Записать(Проведение); Ответ = ВызватьВнешнийHTTPСервис(); // <--- Точка отказа! ЗафиксироватьТранзакцию();


Архитектура решения

1. Архитектурная модель асинхронного взаимодействия

Для устранения взаимного влияния интеграционного трафика и интерактивного транзакционного ядра развертывается асинхронный интеграционный контур на базе промежуточного программного брокера сообщений (RabbitMQ 3.13).

2. Шаблон «Транзакционный Outbox» (Transactional Outbox)

Шаблон гарантирует, что любое событие, порожденное бизнес-логикой 1С (например, регистрация отгрузки), будет доставлено во внешний брокер тогда и только тогда, когда локальная транзакция СУБД завершилась успешной фиксацией (Commit).

  1. Атомарная запись: В рамках одной транзакции СУБД выполняется проведение документа и создание записи в служебном регистре сведений РегистрСведений.ОчередьИсходящихСобытий.
  2. Гарантия согласованности: Если транзакция проведения откатывается, запись в регистре Outbox также откатывается СУБД. Появление «фантомных» сообщений в брокере математически исключено.
  3. Асинхронная доставка: Отдельное регламентное фоновое задание считывает накопленные события из регистра Outbox с установкой управляемой блокировки, публикует их в RabbitMQ и при получении подтверждения удаляет обработанные записи.
  4. Гарантия доставки (At-Least-Once): Сообщение сохраняется в регистре до тех пор, пока брокер сообщений явно не подтвердит сохранение на диск. При сетевом сбое сообщение будет отправлено повторно.

3. Шаблон «Идемпотентный потребитель» (Idempotent Consumer)

Поскольку гарантия доставки at-least-once допускает повторную передачу сообщений при сетевых сбоях, принимающая сторона обязана обладать свойством идемпотентности:

$$f(f(x)) = f(x)$$

Повторная обработка того же сообщения с идентификатором $\text{MessageID}$ не должна приводить к повторному проведению документа или возникновению дублирующих финансовых проводок. - Проверка осуществляется через служебный регистр РегистрСведений.ИсторияОбработанныхСообщенийИнтеграции. - При поступлении дубликата транзакционное ядро не выполняет проведение документа повторно, а мгновенно подтверждает получение брокеру (basic.ack), исключая застревание сообщения в очереди.

4. Изоляция дефектов через Dead Letter Exchange (DLX)

Сообщения, содержащие невалидную структуру данных (синтаксические дефекты JSON, отсутствие обязательных полей), не должны блокировать общий конвейер обработки и не должны повторно запрашиваться из очереди, провоцируя циклические отказы (Poison Message loop). - Очередь trade.orders.incoming конфигурируется с параметрами: - x-dead-letter-exchange: "trade.dlx" - x-dead-letter-routing-key: "orders.poison" - При возникновении фатального дефекта валидации потребитель 1С отправляет команду basic.nack с флагом requeue = false. Брокер мгновенно и прозрачно перемещает дефектный пакет в очередь trade.orders.poison_dlq для последующего ручного аудита инженерами эксплуатации.


Пошаговая реализация

Шаг 1. Проектирование и развертывание топологии шины в RabbitMQ

Для исключения ручной настройки через веб-интерфейс топология шины формализована в виде декларативного манифеста definitions.json и развертывается в изолированном Docker-контейнере через docker-compose.rabbitmq.yml.

Манифест развертывания брокера:

version: '3.8'

services:
  rabbitmq:
    image: rabbitmq:3.13-management-alpine
    container_name: rabbitmq-trade-node
    hostname: rabbitmq-trade
    restart: unless-stopped
    ports:
      - "5672:5672"     # AMQP порт обмена данными
      - "15672:15672"   # Веб-интерфейс управления и REST API
      - "15692:15692"   # Эндпоинт экспортера метрик Prometheus
    environment:
      RABBITMQ_DEFAULT_USER: trade_admin
      RABBITMQ_DEFAULT_PASS: TradeSecurePass2026!
      RABBITMQ_DEFAULT_VHOST: /trade
      RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: "+S 4:4 +P 1048576"
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
      - rabbitmq_logs:/var/log/rabbitmq
      - ../rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
      - ../rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
      - ../rabbitmq/definitions.json:/etc/rabbitmq/definitions.json:ro
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4096M

Конфигурация пределов ресурсов в rabbitmq.conf:

# Блокировка входящей публикации при достижении 70% доступной RAM контейнера
vm_memory_high_watermark.relative = 0.7

# Аварийный порог свободного дискового пространства
disk_free_limit.absolute = 2GB

# Автоматическая загрузка декларативной топологии при старте
load_definitions = /etc/rabbitmq/definitions.json
prometheus.return_per_object_metrics = true

Архитектура декларативной топологии definitions.json:

  1. Виртуальный хост: /trade (обеспечивает полную изоляцию очередей торгового контура от других корпоративных систем);
  2. Точки обмена (Exchanges):
  3. trade.events.direct (тип direct, durable: true) - целевая маршрутизация входящих заказов по схемам интеграции (orders.fbo, orders.fbs, order.create);
  4. trade.events.topic (тип topic, durable: true) - маршрутизация событий маркировки (marking.#);
  5. trade.dlx (тип direct, durable: true) - специализированная точка обмена для сброса недоставленных и поврежденных пакетов.
  6. Очереди сообщений (Queues):
  7. trade.orders.incoming (durable: true) - основной буфер входящих заказов. Оснащен аргументами x-dead-letter-exchange: "trade.dlx" и x-dead-letter-routing-key: "orders.poison";
  8. trade.marking.status_update (durable: true) - очередь обработки статусов маркировки;
  9. trade.orders.poison_dlq (durable: true) - очередь-накопитель бракованных сообщений.

Шаг 2. Реализация шаблона Transactional Outbox на встроенном языке 1С

В модуле TransactionalOutbox_Service.bsl реализованы функции атомарной регистрации событий и их регламентной диспетчеризации.

1. Атомарная фиксация события в транзакции:

Функция ЗарегистрироватьСобытиеВТранзакции(Знач ИмяСобытия, Знач ДанныеСобытия, 
    Знач ТочкаОбмена = "trade.events.direct", Знач КлючМаршрутизации = "order.create", 
    Знач ИдентификаторСобытия = Неопределено) Экспорт

    // Контроль транзакционного контекста: метод обязан выполняться внутри транзакции СУБД
    Если Не ВТранзакции() Тогда
        ВызватьИсключение НСтр("ru = 'Вызов регистрации события Outbox вне транзакции СУБД недопустим!'");
    КонецЕсли;

    ИдентификаторСтрокой = ?(ЗначениеЗаполнено(ИдентификаторСобытия), 
        Строка(ИдентификаторСобытия), Строка(Новый УникальныйИдентификатор));

    ТелоJSON = СериализоватьОбъектВJSON(ДанныеСобытия);

    МенеджерЗаписи = РегистрыСведений.ОчередьИсходящихСобытий.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.ИдентификаторСобытия  = ИдентификаторСтрокой;
    МенеджерЗаписи.ИмяСобытия            = ИмяСобытия;
    МенеджерЗаписи.ТочкаОбмена           = ТочкаОбмена;
    МенеджерЗаписи.КлючМаршрутизации     = КлючМаршрутизации;
    МенеджерЗаписи.ТелоСообщения         = ТелоJSON;
    МенеджерЗаписи.Статус                = "НОВОЕ";
    МенеджерЗаписи.КоличествоПопыток     = 0;
    МенеджерЗаписи.ДатаСоздания          = ТекущаяДатаСеанса();
    МенеджерЗаписи.ДатаСледующейПопытки  = ТекущаяДатаСеанса();

    // Запись фиксируется локальной транзакцией PostgreSQL вместе с бизнес-документом
    МенеджерЗаписи.Записать(Истина);

    Возврат ИдентификаторСтрокой;
КонецФункции

2. Пакетная диспетчеризация с управляемой блокировкой:

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

НачатьТранзакцию(РежимБлокировкиТранзакций.Управляемый);
Попытка
    Блокировка = Новый БлокировкаДанных;
    Элемент = Блокировка.Добавить("РегистрСведений.ОчередьИсходящихСобытий");
    Элемент.Режим = РежимБлокировкиДанных.Исключительный;
    Блокировка.Заблокировать();

    Запрос = Новый Запрос(
        "ВЫБРАТЬ ПЕРВЫЕ 100
        |   Очередь.ИдентификаторСобытия, Очередь.ТочкаОбмена, 
        |   Очередь.КлючМаршрутизации,    Очередь.ТелоСообщения, 
        |   Очередь.КоличествоПопыток
        |ИЗ РегистрСведений.ОчередьИсходящихСобытий КАК Очередь
        |ГДЕ (Очередь.Статус = ""НОВОЕ"" ИЛИ (Очередь.Статус = ""ОШИБКА"" И Очередь.ДатаСледующейПопытки <= &Момент))
        |УПОРЯДОЧИТЬ ПО Очередь.ДатаСоздания ВОЗР");
    Запрос.УстановитьПараметр("Момент", ТекущаяДатаСеанса());
    Таблица = Запрос.Выполнить().Выгрузить();
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    Возврат;
КонецПопытки;

Шаг 3. Разработка пула асинхронных потребителей (Consumer)

Модуль RabbitMQ_Consumer.bsl организует пакетное извлечение сообщений из брокера и их обработку.

  1. Пакетная вычитка: За один сетевой запрос из очереди извлекается до 50 сообщений, что устраняет накладные расходы на сетевые рукопожатия и инициализацию сеансов платформы;
  2. Атомарность проведения: Проведение заказа и запись отметки в регистр идемпотентности выполняются в единой транзакции базы данных;
  3. Разделение типов сбоев:
  4. Транзиторные сбои (таймаут блокировки СУБД) сопровождаются возвратом сообщения в очередь либо повторной попыткой;
  5. Синтаксические дефекты JSON вызывают немедленный вызов basic.nack(requeue = false) для перемещения пакета в очередь недоставленных сообщений (DLQ).
Попытка
    Чтение = Новый ЧтениеJSON;
    Чтение.УстановитьСтроку(ТелоJSON);
    СтруктураЗаказа = ПрочитатьJSON(Чтение);
    ВалидироватьСхемуЗаказа(СтруктураЗаказа);
Исключение
    // Невалидная структура: отправляем в Dead Letter Queue без зацикливания
    ЗафиксироватьОтказИдемпотентности(MessageID, "СИНТАКСИЧЕСКИЙ_ДЕФЕКТ", ОписаниеОшибки());
    ОтклонитьСообщениеБезВозврата(HTTPСоединение, ВиртуальныйХост, ИмяОчереди, Сообщение, ОписаниеОшибки());
    Возврат;
КонецПопытки;

Шаг 4. Реализация протокола идемпотентности при приеме данных

Для предотвращения повторного проведения документов при повторной доставке сообщений перед началом бизнес-логики выполняется проверка наличия $\text{MessageID}$ в регистре ИсторияОбработанныхСообщенийИнтеграции:

Функция СообщениеУжеОбработано(Знач MessageID)
    Запрос = Новый Запрос(
        "ВЫБРАТЬ ПЕРВЫЕ 1 1 
        |ИЗ РегистрСведений.ИсторияОбработанныхСообщенийИнтеграции КАК История
        |ГДЕ История.ИдентификаторСообщения = &MessageID И История.Статус = ""УСПЕХ""");
    Запрос.УстановитьПараметр("MessageID", MessageID);
    Возврат Не Запрос.Выполнить().Пустой();
КонецФункции

Если запись найдена, потребитель фиксирует предупреждение в журнале регистрации, подтверждает сообщение брокеру (basic.ack) для его удаления из очереди и завершает работу без повторного создания прикладных документов.


Шаг 5. Экспорт метрик очередей в Prometheus и визуализация в Grafana

Плагин rabbitmq_prometheus, включенный в конфигурации, предоставляет метрики по порту 15692. В системе мониторинга настраиваются ключевые запросы на языке PromQL:

# 1. Глубина входящего буфера заказов (Backpressure buffer)
rabbitmq_queue_messages_ready{vhost="/trade", queue="trade.orders.incoming"}

# 2. Количество дефектных пакетов в Dead Letter Queue (требует внимания инженера)
rabbitmq_queue_messages_ready{vhost="/trade", queue="trade.orders.poison_dlq"}

# 3. Скорость обработки заказов потребителями 1С (сообщений в секунду)
rate(rabbitmq_queue_messages_delivered_total{vhost="/trade", queue="trade.orders.incoming"}[1m])

Проверка результата и метрики

Для верификации характеристик асинхронного контура и подтверждения эффективности буферизации был проведен нагрузочный стресс-тест с использованием скрипта benchmark-traffic.py. В систему генерировался залповый поток из 50 000 заказов с пиковой интенсивностью до 400 сообщений в секунду.

Сравнительные эксплуатационные показатели проекта «Торговый контур»

Эксплуатационный показатель Синхронная интеграция (HTTP-сервисы 1С) Асинхронный контур (RabbitMQ + Outbox) Результат модернизации
Время подтверждения внешнего API-вызова (HTTP Roundtrip) 4 800 мс (ожидание фиксации в PostgreSQL) 42 мс (быстрая публикация в буфер брокера) Ускорение внешнего отклика в 114 раз
Время отклика интерактивных сеансов при пике (350 польз.) 12–18 секунд (задержки блокировок, зависания) 0.85–1.1 секунды (полная стабильность) Ликвидация деградации пользовательского интерфейса
Число занятых рабочих процессов rphost интеграцией 18–24 процессов (исчерпание доступного пула) 2 фоновых процесса (строго лимитированный пул) Экономия до 85% серверной оперативной памяти
Уровень взаимных блокировок на регистре «ТоварыНаСкладах» До 42 блокировок/час в периоды акций 0 блокировок (упорядоченная пакетная обработка) Полное исключение конфликтов доступа к данным
Доля потерянных заказов при аварийном перезапуске 1С 3.8% (обрыв сетевых соединений при записи) 0.00% (персистентные очереди на диске брокера) 100% гарантия доставки (At-Least-Once)
Динамика времени отклика интерактивных сеансов при залповой нагрузке (50 000 заказов):

Время (мин):        00:00        00:05 (Старт пика)   00:10        00:15        00:20
Синхронная схема:   0.9 с ------- 14.5 с ------------- 18.2 с ----- 12.0 с ----- 1.1 с (Глубокая деградация)
Асинхронный контур: 0.9 с -------  1.0 с -------------- 1.1 с ------ 0.9 с ----- 0.9 с (Стабильный отклик)
                                   ▲
                                   | Заказы буферизуются в trade.orders.incoming,
                                   | фоновые обработчики забирают их с постоянной скоростью.

Риски и ограничения

Внедрение брокеров сообщений сопряжено со специфическими архитектурными рисками, требующими строгого соблюдения правил эксплуатации.

1. Опасность зацикливания дефектных сообщений (Poison Message Loop)

2. Риск неконтролируемого переполнения оперативной памяти брокера

3. Ограничения производительности платформы при одиночной вычитке сообщений


Итоги

Практические итоги внедрения асинхронного брокера сообщений

Внедрение асинхронного интеграционного контура на базе RabbitMQ в проекте «Торговый контур» позволило решить ключевые задачи масштабирования: 1. Изоляция пользовательского контура от пиковых нагрузок: Интерактивные пользователи больше не испытывают задержек при оформлении заказов и списании остатков в периоды маркетинговых распродаж на маркетплейсах; 2. Гарантированная устойчивость обмена (At-Least-Once): Устранена потеря данных при кратковременных сетевых сбоях и перезапусках серверов; 3. Ликвидация каскадных сбоев: Применение Dead Letter Queue изолирует некорректно сформированные пакеты данных, обеспечивая непрерывную обработку основного потока заказов; 4. Снижение совокупной стоимости владения (TCO): Отказ от избыточного наращивания процессорных ресурсов сервера 1С позволил высвободить аппаратные мощности под аналитические и регламентные задачи.

Логический переход к Части 6Б

Архитектура обмена изолирована, процессы поставки и телеметрии выстроены - переходим к Части 6Б: развертыванию слоя искусственного интеллекта на базе Model Context Protocol (MCP) для анализа метаданных и генерации прикладных тестов. В следующей статье мы разберем создание локального MCP-сервера, обеспечивающего контекстное понимание структуры объектов 1С, безопасную работу больших языковых моделей с метаданными прикладного решения и автоматизированную генерацию модульных тестов YAxUnit непосредственно по структуре конфигурации.

Расширение «НОПик» для 1С — встраиваемый коннектор к внешнему AI с интеллектуальным поиском по базе. Задавайте вопросы обычными словами - AI сам найдёт нужное. 45 дней бесплатно.

Попробовать НОПик →
Знаете ответ на такие вопросы не хуже автора статьи? Пройдите бесплатную анонимную проверку уровня на infolimp.ru - 3 практических задачи, 15 минут, публичный токен-профиль, который можно показать работодателю или заказчику. Без регистрации по почте.

Проверить свой уровень бесплатно →