Организация очередей сообщений и гарантированной доставки в 1С (RabbitMQ / Native)
При прямых синхронных обменах между системами возникают проблемы:
- Потеря пакетов обмена при кратковременной недоступности сервиса-приемника.
- Зависание пользовательских сеансов при блокировках во время проведения документов, выполняющих синхронные HTTP-запросы.
- Дублирование документов при повторной отправке одного и того же пакета из-за сетевого таймаута (нарушение идемпотентности).
- Перегрузка базы-приемника пиковыми всплесками нагрузки (отсутствие Rate Limiting).
1. Архитектурный паттерн Transactional Outbox в 1С
Для обеспечения 100% гарантии доставки без распределенных транзакций используется паттерн Transactional Outbox:
- В рамках одной транзакции СУБД проводится бизнес-документ и делается запись в локальную таблицу очередей (РегистрСведений
ОчередьИсходящихСообщений). - Отдельное фоновое регламентное задание считывает записи из регистра и отправляет их в брокер сообщений (RabbitMQ / Apache Kafka).
- После подтверждения приема брокером (ACK) запись удаляется из очереди.
2. Публикация сообщений в RabbitMQ через HTTP REST API брокера
&НаСервере
Процедура ОтправитьВRabbitMQ(ИмяОчереди, ДанныеСообщенияJSON, ИдентификаторСообщения)
Хост = "rabbitmq.company.ru";
Порт = 15672; // Порт Management API
Пользователь = "app_1c";
Пароль = "RabbitSecretPass";
VHost = "%2F"; // Дефолтный vhost '/'
HTTPСоединение = Новый HTTPСоединение(
Хост, Порт, Пользователь, Пароль, , 15, Новый ЗащищенноеСоединениеOpenSSL()
);
Путь = СтрШаблон("/api/exchanges/%1/amq.default/publish", VHost);
HTTPЗапрос = Новый HTTPЗапрос(Путь);
HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json");
// Структура публикации RabbitMQ Management API
ПараметрыПубликации = Новый Структура;
ПараметрыПубликации.Вставить("properties", Новый Структура("delivery_mode, message_id", 2, ИдентификаторСообщения)); // delivery_mode=2 (Persistent)
ПараметрыПубликации.Вставить("routing_key", ИмяОчереди);
ПараметрыПубликации.Вставить("payload", ДанныеСообщенияJSON);
ПараметрыПубликации.Вставить("payload_encoding", "string");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, ПараметрыПубликации);
HTTPЗапрос.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть(), КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
HTTPОтвет = HTTPСоединение.ВызватьHTTPМетод("POST", HTTPЗапрос);
Если HTTPОтвет.КодСостояния <> 200 Тогда
ВызватьИсключение СтрШаблон("Ошибка отправки в очередь (%1): %2",
HTTPОтвет.КодСостояния, HTTPОтвет.ПолучитьТелоКакСтроку());
КонецЕсли;
КонецПроцедуры3. Обработка исходящей очереди регламентным заданием
&НаСервере
Процедура ОбработатьОчередьИсходящихСообщений()
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьИсходящихСообщений");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| Очередь.ИдентификаторСообщения КАК ID,
| Очередь.ОчередьНазначения КАК Очередь,
| Очередь.ТелоСообщения КАК Тело
|ИЗ
| РегистрСведений.ОчередьИсходящихСообщений КАК Очередь
|УПОРЯДОЧИТЬ ПО
| Очередь.МоментВремени ВОЗР";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОтправитьВRabbitMQ(Выборка.Очередь, Выборка.Тело, Строка(Выборка.ID));
// Удаление успешно отправленного сообщения
Набор = РегистрыСведений.ОчередьИсходящихСообщений.СоздатьНаборЗаписей();
Набор.Отбор.ИдентификаторСообщения.Установить(Выборка.ID);
Набор.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("ОчередиСообщений", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры Частые вопросы (FAQ)
Что такое delivery_mode = 2 в RabbitMQ?
Режим доставки persistent (2) указывает брокеру сохранять сообщение на физический диск, гарантируя его сохранность даже при внезапной перезагрузке ноды RabbitMQ.
Как обеспечить идемпотентность на стороне базы-приемника?
В каждом сообщении передается уникальный UUID (message_id). Приемник перед обработкой проверяет наличие этого UUID в регистре обработанных пакетов; если ID найден, повторная обработка пропускается.
Почему лучше использовать HTTP API RabbitMQ вместо внешних компонент Native AMQP?
HTTP API не требует установки C++ библиотек на серверах Linux/Windows, работает через стандартный объект HTTPСоединение 1С и стабилен в многопоточных фоновых заданиях rphost.
Как предотвратить взаимные блокировки при одновременной записи в очередь Outbox?
Используйте управляемые блокировки (РежимБлокировкиДанных.Управляемый) и записывайте события в очередь через независимый регистр сведений без избыточных измерений.