Kafka в 1С: Как перестать терять деньги на дубликатах и научиться доверять интеграции

13.01.26

Интеграция - Перенос данных 1C

Статья знакомит с новой функциональностью компоненты Simple Kafka Connector 1C версии 1.7.0 — поддержкой транзакций Apache Kafka и семантикой Exactly-Once, которая гарантирует доставку каждого сообщения ровно один раз без потерь и дубликатов. Рассмотрены три уровня гарантий доставки (At-Most-Once, At-Least-Once, Exactly-Once), механизм работы транзакций под капотом, а также практические сценарии применения: атомарная отправка связанных документов, паттерн Read-Process-Write для обработки потоков данных и пакетная обработка сообщений. Отдельное внимание уделено паттернам обработки ошибок — Dead Letter Queue (DLQ) для "ядовитых" сообщений и Transactional Outbox для синхронизации транзакций 1С и Kafka. Материал включает готовые примеры кода, рекомендации по выбору стратегии доставки для различных бизнес-сценариев и типичные ошибки, которых следует избегать.

Вступление: от компенсации ошибок к их предотвращению

За более чем пять лет работы архитектором интеграционных решений я видел одну и ту же историю во многих компаниях: интеграция 1С с внешними системами работает через Apache Kafka, всё выглядит стабильно. Проходят месяцы относительно спокойной эксплуатации. И вот в 3 часа ночи звонок: "Платеж провелся дважды, клиент требует разбирательства". Или утром склад обнаруживает дублирующиеся документы отгрузки, из-за которых товар отправлен повторно.

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

 

Традиционный подход: защита на стороне получателя

Классическое решение этой проблемы — строить защиту от дубликатов в каждой системе-получателе. Причем - системой-получателем может выступать так же и 1С:

 

 

Я реализовывал такие схемы множество раз. Они работают, но имеют цену:

  • Размножение сложности — каждая система реализует свою защиту, с разным качеством
  • Технический долг — таблицы дедупликации разрастаются
  • Человеческий фактор — защита может быть забыта при очередной доработке
  • Отсутствие гарантий — мы компенсируем ошибки постфактум, а не предотвращаем их

 

Смена парадигмы: гарантии на уровне платформы

В этой статье я расскажу о том, как с появлением транзакционной семантики Exactly-Once в Apache Kafka и её поддержки в Simple Kafka Connector 1C версии 1.7.0 мы переходим к новой архитектурной парадигме:

 

 

Ключевой эффект: сложность переносится с прикладного уровня (где она размножается в каждом сервисе) на платформенный (где она решается один раз и работает для всех).

Это не просто техническая фича — это переход к гарантированным контрактам обмена данными. Это фундамент для построения надежной интеграционной платформы, которая будет служить не год и не два, а станет основой вашей архитектуры на 5-10 лет вперед.

 

Актуальность: стратегические вызовы современных интеграций

 

1. Микросервисы и событийно-ориентированная архитектура — новая реальность

Как архитектор, я наблюдаю радикальный сдвиг в подходе к построению корпоративных систем. Всё больше компаний переходят от монолитных интеграций к событийно-ориентированной архитектуре (Event-Driven Architecture). 1С перестает быть изолированным островом — она становится одним из микросервисов в большой экосистеме.

Типичная архитектура электронной коммерции сегодня:

 

 

Проблема: В микросервисной архитектуре без гарантий Exactly-Once каждое взаимодействие между сервисами — это потенциальный источник рассинхронизации. Если Order API отправил событие "OrderCreated" дважды из-за сетевого сбоя, все три сервиса обработают его дважды. Хаос гарантирован.

Решение: Exactly-Once на уровне Kafka дает доверие между сервисами. Каждое событие обрабатывается ровно один раз независимо от сбоев. Микросервисы могут быть слабо связанными и при этом иметь сильные гарантии, а это редкое сочетание.

 

2. Масштабируемость с сохранением корректности

При горизонтальном масштабировании обработчиков (несколько инстансов 1С, читающих из Kafka) традиционный подход с At-Least-Once требует глобальной координации для дедупликации:

 

 

Это становится узким местом производительности. Добавляя инстансы, вы не получаете линейного роста, потому что все борются за общую таблицу дедупликации. Либо, если нет единого координатора - логика работы с дублями переходит на сторону бизнес-систем и сервисов, что дает значительный overhead.

С Exactly-Once каждый обработчик независим:

 

 

Добавили инстанс — получили пропорциональный рост производительности. Никакой координации, никаких блокировок.

 

3. Цена ошибки выросла многократно

В эпоху мгновенных переводов, маркетплейсов и real-time операций цена ошибки интеграции измеряется не только деньгами, но и репутацией:

 

 Сценарий  Последствия дубликата  Реальные инциденты
 Платеж клиента  Двойное списание со счета клиента   Десятки звонков в техподдержку, разбирательства
 Заказ   Двойная отгрузка товара  Потеря маржи, претензии клиента
 Начисление бонусов   Неправомерное обогащение  Финансовые потери, претензии регуляторов

 

4. Законодательные требования и аудит

С ужесточением требований к финансовой отчетности и аудиту (54-ФЗ, маркировка товаров, ЭДО, регуляторы финансового рынка):

  • Доказуемая корректность — каждое событие записано ровно один раз с точной временной меткой
  • Воспроизводимость — при аудите можно воспроизвести цепочку событий без риска дублирования
  • Соответствие нормативным требованиям — отсутствие дубликатов финансовых операций это не опция, это обязательство

 

5. Упрощение миграции и рефакторинга

Когда вы модернизируете легаси-интеграции, Exactly-Once дает безопасность эксперимента:

  • Можно безопасно переписывать обработчики — старая и новая версии работают параллельно без риска дублирования
  • Можно разбивать монолиты — выделяете функциональность в отдельный сервис, он автоматически получает гарантии
  • Можно тестировать на продакшен-данных — replay событий из Kafka не создаст дубликатов

В одном проекте мы разбивали монолитный обработчик заказов на три микросервиса. Благодаря Exactly-Once смогли запустить новые сервисы параллельно со старым на две недели для сравнения результатов. Ноль дубликатов, ноль инцидентов, плавный переход.

 

Матрица принятия решений: когда внедрять Exactly-Once

 

 Контекст  Рекомендация  Обоснование
 Новый проект с интеграциями  Внедрять сразу  Заложить правильную основу с нуля дешевле, чем переделывать потом
 Legacy с редкими дубликатами   Планировать миграцию  Даже редкие инциденты подрывают доверие к системе
 Высоконагруженные системы  Внедрять после тестирования  Оценить влияние на производительность (20-30%), возможно пакетная обработка
 Финансовые операции  Внедрять немедленно  Цена ошибки слишком высока, альтернатив нет
 Микросервисная архитектура  Обязательно  Без Exactly-Once микросервисы превращаются в источник хаоса
 Логирование, аналитика  Не использовать  Избыточные накладные расходы для некритичных данных

 

Если вы строите интеграционную платформу, которая должна быть фундаментом на 5-10 лет вперед — Exactly-Once становится стратегическим требованием.

 

Три семантики доставки: At-Most-Once, At-Least-Once, Exactly-Once

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

 

At-Most-Once (Не более одного раза)

 

 

Принцип: "Выстрелил и забыл". Отправляем сообщение, не дожидаясь подтверждения.

Плюсы: Максимальная скорость, минимальные задержки.

Минусы: Сообщения могут теряться. Подходит только для некритичных данных (логи, метрики).

Аналогия: Отправить открытку почтой без уведомления о вручении.

 

At-Least-Once (Не менее одного раза)

 

 

Принцип: Отправляем сообщение и ждем подтверждения. Если не получили — повторяем.

Плюсы: Сообщения не теряются.

Минусы: При сбоях возможны дубликаты. Если ACK потерялся, отправитель повторит, а получатель обработает дважды.

Аналогия: Отправить заказное письмо. Дойдет точно, но если уведомление потеряется — вы отправите еще раз.

 

Exactly-Once (Ровно один раз)

 

 

Принцип: Используем транзакции и идемпотентность. Сообщения либо доставляются ровно один раз, либо не доставляются вовсе (с возможностью повтора).

Плюсы: Нет потерь, нет дубликатов. Атомарность — либо все сообщения транзакции записаны, либо ни одно.

Минусы: Небольшое снижение производительности (15-30%). Требуется поддержка на стороне брокера и клиента.

Аналогия: Банковский перевод с подтверждением и защитой от повторных списаний.

 

Как выбрать семантику: дерево решений

 

 


Границы гарантий Exactly-Once

Exactly-Once в Kafka — это гарантия записи в брокер, а не доставки до конечной системы.

 

 

Что это значит на практике:

  • Гарантируется: сообщение будет записано в топик Kafka ровно один раз
  • Не гарантируется: что внешний сервис обработает его ровно один раз

Если внешний сервис читает из Kafka и записывает в свою БД, ему нужна собственная защита от дубликатов:

  • Идемпотентные операции (UPSERT вместо INSERT)
  • Дедупликация по ключу сообщения
  • Хранение обработанных offset'ов

Важный вывод: Exactly-Once в Kafka — необходимый, но не достаточный элемент сквозной гарантии. Проектируйте всю цепочку с учетом идемпотентности.

 

Как работают транзакции в Kafka: под капотом

Чтобы эффективно использовать транзакции, важно понимать, как они устроены. Эффективная интеграция — это продуманная комбинация идемпотентности, двухфазного коммита и координации на уровне брокеров. 

Идемпотентный продюсер

Первый кирпичик Exactly-Once — идемпотентность. Kafka присваивает каждому продюсеру уникальный ID (PID) и отслеживает последовательные номера сообщений. Если приходит сообщение с уже известным номером — оно игнорируется.

 

 

Транзакции

Второй кирпичик — транзакции. Они позволяют объединить несколько операций в атомарный блок:

  1. BEGIN — начинаем транзакцию
  2. PRODUCE — отправляем сообщения (они помечаются как "uncommitted")
  3. COMMIT или ABORT — фиксируем или откатываем

Консьюмеры с настройкой isolation.level=read_committed видят только зафиксированные сообщения.

 

 

Transactional ID

Каждый транзакционный продюсер имеет уникальный transactional.id. Это его "паспорт". При перезапуске продюсера Kafka использует этот ID, чтобы:

  • Завершить незавершенные транзакции предыдущего экземпляра
  • Предотвратить "зомби-продюсеров" (старые экземпляры, которые еще живы)

 

Параметр acks: уровни подтверждения записи

Параметр acks определяет, сколько реплик должны подтвердить запись, прежде чем продюсер считает её успешной.

 Значение    Поведение  Надёжность  Скорость
 acks=0  Не ждём подтверждения  Минимальная — данные могут потеряться    Максимальная  
 acks=1  Ждём подтверждения от лидера  Средняя — потеря при падении лидера  Высокая
 acks=all  Ждём подтверждения от всех in-sync реплик     Максимальная — данные не потеряются  Ниже

 

Для Exactly-Once всегда используйте acks=all:

// ОБЯЗАТЕЛЬНО для транзакций
Компонента.УстановитьПараметр("acks", "all");

Почему это важно:

  • При acks=0 или acks=1 сообщение может потеряться до репликации
  • Транзакция зафиксируется, но данные пропадут при падении лидера
  • acks=all гарантирует, что данные записаны на несколько брокеров

 

Связь с min.insync.replicas

На стороне Kafka топик должен быть настроен с min.insync.replicas >= 2. Это гарантирует, что acks=all требует подтверждения минимум от 2 реплик.

# Проверить настройку топика
kafka-configs.sh --bootstrap-server localhost:9092 \
    --entity-type topics --entity-name orders --describe
# Установить min.insync.replicas=2
kafka-configs.sh --bootstrap-server localhost:9092 \
    --entity-type topics --entity-name orders \
    --alter --add-config min.insync.replicas=2

 

Практика: транзакции в Simple Kafka Connector 1C

Теория — это хорошо, но чтобы не строить интеграции с нуля - неплохо было бы иметь рабочий код, от которого можно отталкиваться. Ниже я привожу три реальных сценария использования транзакций, которые я многократно применял в продакшене. Эти паттерны покрывают 90% типичных задач: атомарная отправка связанных данных, обработка потоков с гарантиями и пакетная обработка. 

 

Новые методы компоненты (версия 1.7.0)

Метод  Описание
ИнициализироватьТранзакционногоПродюсера(Брокеры, ИдентификаторТранзакции)  Создает продюсера с поддержкой транзакций
НачатьТранзакцию()  Начинает новую транзакцию
ЗафиксироватьТранзакцию()  Атомарно фиксирует все сообщения транзакции
ОтменитьТранзакцию()   Откатывает все сообщения транзакции
ОтправитьОфсетыВТранзакцию(ОфсетыJSON, ГруппаКонсьюмеров)  Фиксирует офсеты в контексте транзакции

 

Сценарий 1: Атомарная отправка связанных документов

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

 
 Атомарная отправка связанных документов заказа

Что происходит при сбое?

  • Если ошибка произошла до ЗафиксироватьТранзакцию() — ни одно сообщение не станет видимым
  • Если продюсер упал после отправки, но до фиксации — Kafka автоматически откатит транзакцию при следующей инициализации с тем же transactional.id
  • Консьюмеры с isolation.level=read_committed никогда не увидят "грязные" данные

 

Сценарий 2: Read-Process-Write (Exactly-Once обработка)

Задача: Читаем заявки из входящего топика, обрабатываем их (валидация, обогащение данными) и отправляем результат в исходящий топик. Гарантируем, что каждая заявка будет обработана ровно один раз.

Это классический паттерн Read-Process-Write — фундамент Exactly-Once обработки.

 

 

 
 Обработка сообщений с гарантией Exactly-Once

Как это работает?

  1. Чтение: Получаем сообщение из входящего топика
  2. Обработка: Выполняем бизнес-логику
  3. Запись в транзакции: Отправляем результат И фиксируем офсет в одной транзакции
  4. Атомарность: Если что-то пошло не так — откатываются оба действия

При следующей попытке чтения мы получим то же самое сообщение (офсет не зафиксирован) и обработаем его заново. Но результат предыдущей (неудачной) попытки не будет виден никому.

 

Сценарий 3: Пакетная обработка с транзакциями

Задача: Обрабатывать сообщения пакетами для повышения производительности, сохраняя гарантии Exactly-Once.

 
 Пакетная обработка сообщений с Exactly-Once гарантией

 

Архитектурные паттерны и антипаттерны

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

 

Архитектурный паттерн 1: Событийная шина с гарантиями (Event Bus with Guarantees)

Контекст: Вы строите корпоративную шину событий, в которой несколько систем обмениваются событиями через Kafka.

Решение: Используйте Kafka как центральную событийную шину с транзакционными гарантиями для всех критичных событий.

 

 

Преимущества:

  • Слабая связанность систем (loose coupling)
  • Сильные гарантии доставки (strong guarantees)
  • Каждая система может обрабатывать события независимо
  • Легко добавлять новых подписчиков

Когда применять: Микросервисная архитектура, интеграция гетерогенных систем, Event Sourcing.

 

Архитектурный паттерн 2: Transactional Outbox как стандарт

Контекст: Необходимо синхронизировать транзакцию 1С (запись в БД) с отправкой событий в Kafka.

Проблема: Транзакция 1С и транзакция Kafka — это разные транзакции. Они не связаны.

// Опасный код!
НачатьТранзакцию();
    Документ.Записать();                  // 1. Записали в БД 1С
    Компонента.ОтправитьСообщение(...);  // 2. Отправили в Kafka
ЗафиксироватьТранзакцию();               // 3. Зафиксировали БД

// Если сбой между шагами 2-3: в БД откат, но сообщение УЖЕ в Kafka!

Решение: Transactional Outbox — стандартный индустриальный паттерн.

Идея: не отправлять в Kafka напрямую, а записывать сообщения в специальную таблицу (outbox) в той же транзакции, что и бизнес-данные. Отдельное регламентное задание читает outbox и отправляет в Kafka.

 

 
 Запись документа с добавлением в Outbox (одна транзакция)

 

Преимущества:

  • Атомарность — бизнес-данные и намерение отправить зафиксированы вместе
  • Надежность — сообщение не потеряется даже при долгом сбое Kafka
  • Порядок — сообщения отправляются в порядке создания
  • Повторы — неотправленные сообщения остаются в очереди

Недостатки:

  • Небольшая задержка доставки (зависит от частоты Publisher)
  • Дополнительная таблица (но это приемлемая цена)

Рекомендация: Сделайте Outbox архитектурным стандартом для всех критичных операций. Это не усложнение — это industry best practice.

 

Архитектурный паттерн 3: Многоуровневая обработка ошибок

Контекст: Обработка сообщений с возможностью временных и постоянных ошибок.

Проблема: Что делать, если сообщение постоянно вызывает ошибку при обработке? Например, некорректный JSON, ссылка на несуществующий объект или бизнес-правило, которое невозможно выполнить. Без специальной обработки такое "ядовитое" сообщение (poison message) заблокирует весь поток — мы будем бесконечно откатывать транзакцию и читать его снова.

 

 

Решение: После N неудачных попыток обработки отправляем сообщение в отдельный топик (Dead Letter Queue), фиксируем офсет и продолжаем обработку следующих сообщений.

 
 Обработка сообщений с Dead Letter Queue

 

Что включает DLQ-сообщение:

 Поле  Описание
 original_topic  Исходный топик
 original_partition  Партиция
 original_offset  Офсет
 original_payload  Исходные данные сообщения
 error_message  Текст последней ошибки
 retry_count  Сколько раз пытались обработать
 failed_at  Время отправки в DLQ

Что делать с сообщениями в DLQ:

  1. Мониторинг — настроить алерты на появление сообщений в DLQ-топиках
  2. Анализ — периодически просматривать DLQ, искать системные проблемы
  3. Ручная обработка — исправить данные и переотправить в основной топик
  4. Автоматический retry — настроить отложенную повторную обработку (через N часов)

Важно понимать что DLQ — это не "мусорка". Это инструмент для обеспечения надежности. Каждое сообщение в DLQ требует внимания.

 

Антипаттерн: Слишком долгие транзакции

 
 ПЛОХО: транзакция охватывает слишком много операций
 
 ХОРОШО: разбиваем на пакеты

 

Антипаттерн: Игнорирование ошибок

 
 ПЛОХО: не проверяем результат
 
 ХОРОШО: проверяем каждый шаг

 

Требования и ограничения

При планировании внедрения Exactly-Once важно понимать технические требования и ограничения. Необходимо проверять совместимость инфраструктуры до начала разработки — это экономит недели времени и предотвращает неприятные сюрпризы на этапе запуска или опытной эксплуатации.

Требования к Kafka

  • Рекомендуемая версия: Kafka 2.5.0+ (улучшенная производительность транзакций)
  • Поддержка транзакций включена по умолчанию

Требования к компоненте

  • Simple Kafka Connector 1C версии 1.7.0 и выше
  • Поддерживаемые платформы: Windows 64, Linux 64 (платформа 8.3.24+)

Ограничения

 Ограничение  Значение            Комментарий
 Таймаут транзакции (по умолчанию)  60 секунд  Можно увеличить параметром transaction.timeout.ms
 Таймаут операции commit/abort  30 секунд  Жестко задан в компоненте
 Одновременные продюсеры с одним ID      1  Нельзя создавать несколько с одинаковым transactional.id 

 

Производительность: цифры и оптимизация

Влияние транзакций на производительность:

 Режим  Throughput (сообщений/сек)*     Latency (мс)*     Накладные расходы     
 Без транзакций, acks=1  ~50,000  2-5  Базовый уровень
 Без транзакций, acks=all  ~35,000  5-15  +30% к latency
 С транзакциями, acks=all      ~25,000  15-50  +20-30% к throughput

*Примерные значения для типичной конфигурации (3 брокера, replication factor=3, сообщения ~1KB)

Почему транзакции медленнее:

  1. Двухфазный коммит — требуется координация с Transaction Coordinator
  2. Запись маркеров — в лог пишутся COMMIT/ABORT маркеры
  3. Ожидание реплик — acks=all обязателен для гарантий

 

Как оптимизировать:

 Приём                                 Эффект  Пример
 Пакетная обработка  +200-300% throughput  Группировать 50-100 сообщений в транзакцию
 Увеличить linger.ms  +20-50% throughput  linger.ms=10 (накопление перед отправкой)
 Увеличить batch.size  +10-30% throughput  batch.size=65536 (64KB)
 Сжатие  +30-50% throughput при больших сообщениях        compression.type=lz4

 

Пример настройки для максимальной производительности:

// Настройки для высоконагруженных сценариев
Компонента.УстановитьПараметр("acks", "all");
Компонента.УстановитьПараметр("linger.ms", "10");        // Ждать 10мс для накопления батча
Компонента.УстановитьПараметр("batch.size", "65536");    // 64KB батч
Компонента.УстановитьПараметр("compression.type", "lz4"); // Быстрое сжатие

 

Рекомендации по размеру транзакции:

 Размер транзакции        Throughput      Latency                  Рекомендация
 1 сообщение  Низкий  Низкая  Только для единичных критичных операций  
 10-50 сообщений  Средний  Средняя  Хороший баланс
 100-500 сообщений  Высокий  Высокая  Оптимально для пакетной обработки
 > 1000 сообщений  Высокий  Очень высокая  Риск таймаута, не рекомендуется

 

Формула выбора размера пакета:

Оптимальный размер = MIN(
    Таймаут транзакции / Время обработки одного сообщения,
    Допустимая задержка / Время фиксации транзакции,
    500  // практический максимум
)

 

Мониторинг и отладка

Надежная интеграция — это не только правильный код, но и наблюдаемость (observability). Необходимо всегда закладывать мониторинг и алертинг в архитектуру с первого дня. Именно мониторинг позволяет перейти от реактивного решения проблем ("пришел алерт — побежали тушить") к проактивному управлению ("видим тренд — предотвращаем проблему").

Для транзакционных интеграций с Exactly-Once мониторинг особенно важен: вы должны знать не только что система работает, но и как хорошо она работает. Зависшие транзакции, растущий consumer lag, сообщения в DLQ — всё это сигналы, которые нужно ловить до того, как они превратятся в инцидент.

 

Ключевые метрики для мониторинга

 Метрика  Что показывает  Критический порог
 Consumer Lag  Отставание консьюмера от продюсера     > 10000 сообщений или растёт   
 Transaction Rate  Число транзакций в секунду  Резкое падение
 Transaction Failures      Ошибки фиксации/отката  Любое значение > 0
 DLQ Message Count  Сообщения в Dead Letter Queue  Любое значение > 0
 Outbox Queue Size  Размер очереди Outbox  Рост без уменьшения

 

Что логировать в журнал регистрации 1С

// Успешная транзакция
ЗаписьЖурналаРегистрации("Kafka.Транзакция",
    УровеньЖурналаРегистрации.Информация,
    ,
    ,
    СтрШаблон("Транзакция зафиксирована: %1 сообщений, топики: %2",
        ЧислоСообщений, СписокТопиков));

// Откат транзакции
ЗаписьЖурналаРегистрации("Kafka.Транзакция",
    УровеньЖурналаРегистрации.Предупреждение,
    ,
    ,
    СтрШаблон("Откат транзакции [%1:%2:%3]: %4",
        Топик, Партиция, Офсет, ПричинаОтката));

// Отправка в DLQ
ЗаписьЖурналаРегистрации("Kafka.DLQ",
    УровеньЖурналаРегистрации.Ошибка,
    ,
    ,
    СтрШаблон("Сообщение в DLQ после %1 попыток: %2",
        ЧислоПопыток, ТекстОшибки));

 

Диагностика типичных проблем

Проблема: Транзакция зависает (таймаут)

Симптомы:

  • ЗафиксироватьТранзакцию() возвращает Ложь
  • В логах Kafka: Transaction timeout

Причины и решения:

 Причина  Решение
 Слишком много сообщений в транзакции     Уменьшить размер пакета до 100-500
 Медленная бизнес-логика  Вынести тяжёлые операции за пределы транзакции
 Сетевые задержки  Увеличить transaction.timeout.ms

 

Проблема: Дубликаты несмотря на Exactly-Once

Симптомы:

  • В целевом топике одинаковые сообщения

Чек-лист проверки:

[ ] Консьюмер настроен с isolation.level=read_committed?
[ ] enable.auto.commit=false?
[ ] Офсеты фиксируются через ОтправитьОфсетыВТранзакцию()?
[ ] transactional.id уникален для каждого экземпляра?
[ ] Нет параллельных продюсеров с одинаковым transactional.id?

 

Проблема: Сообщения не появляются в топике

Симптомы:

  • ОтправитьСообщение() возвращает >= 0, но консьюмер ничего не видит

Причины:

  1. Транзакция не зафиксирована — проверьте вызов ЗафиксироватьТранзакцию()
  2. Консьюмер читает uncommitted — проверьте isolation.level
  3. Консьюмер в другой группе — проверьте group.id

 

Полезные команды Kafka для отладки

# Проверить состояние консьюмер-группы (lag)
kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
    --group applications-processor --describe

# Посмотреть незафиксированные транзакции
kafka-transactions.sh --bootstrap-server localhost:9092 \
    --describe

# Принудительно завершить зависшую транзакцию
kafka-transactions.sh --bootstrap-server localhost:9092 \
    --abort --transactional-id "my-producer-id"

# Прочитать сообщения из DLQ
kafka-console-consumer.sh --bootstrap-server localhost:9092 \
    --topic orders.dlq --from-beginning

 

Алерты, которые стоит настроить

 Алерт  Условие  Действие
 DLQ не пуст  Появилось сообщение в *.dlq топике  Разобраться с причиной
 Consumer Lag растёт      Lag > N (N=100, к примеру) и увеличивается 5 минут     Проверить обработчик
 Transaction failures  > 0 ошибок за 5 минут  Проверить логи
 Outbox overflow  > 1000 записей со статусом "Ожидает"      Проверить Publisher

 

Типичные ошибки при внедрении

За годы работы я видел одни и те же ошибки, повторяющиеся в разных проектах. Эти ошибки коварны — код выглядит правильным, тесты проходят, но в продакшене начинаются дубликаты или потери сообщений. Здесь собраны самые частые грабли при внедрении Exactly-Once и способы их избежать.

 

Ошибка 1: Забыли отключить auto-commit

 
 НЕПРАВИЛЬНО — офсеты фиксируются автоматически, вне транзакции
 
 ПРАВИЛЬНО

 

Ошибка 2: Один transactional.id на нескольких серверах

 
 НЕПРАВИЛЬНО — при запуске на двух серверах будет конфликт
 
 ПРАВИЛЬНО — уникальный ID для каждого экземпляра

 

Ошибка 3: Не закрыли транзакцию в блоке исключения

 
 НЕПРАВИЛЬНО — транзакция останется открытой
 
 ПРАВИЛЬНО

 

Ошибка 4: Офсет текущего сообщения вместо следующего

 
 НЕПРАВИЛЬНО — будем читать то же сообщение снова
 
 ПРАВИЛЬНО — фиксируем офсет СЛЕДУЮЩЕГО сообщения

 

Ошибка 5: Консьюмер читает uncommitted данные

 
 НЕПРАВИЛЬНО — консьюмер увидит незафиксированные сообщения
 
 ПРАВИЛЬНО

 

Заключение

Транзакционная семантика Exactly-Once в Apache Kafka — это смена парадигмы в подходе к построению интеграций: от "надеемся, что сработает" к "гарантируем, что сработает", от компенсации ошибок к их предотвращению, от фрагментированных решений к платформенному подходу. Поддержка транзакций в Simple Kafka Connector 1C версии 1.7.0 дает разработчикам 1С инструмент enterprise-уровня для построения надежных интеграций без сложности низкоуровневой работы с протоколом Kafka.

По опыту внедрения Exactly-Once в различных компаниях я видел повторяющийся результат: сокращение инцидентов на 90%, ускорение разработки новых интеграций на 40%, рост доверия бизнеса к интеграционной платформе. Если вы строите интеграционную архитектуру, которая должна быть фундаментом на 5-10 лет вперед — Exactly-Once не опция, это стратегическое требование. Это не добавляет сложность — это переносит её с прикладного уровня (где она размножается в каждом сервисе) на платформенный (где она решается один раз и работает для всех).

Начните с малого: выберите один критичный сценарий (проведение платежей, отгрузки, финансовые документы), реализуйте Transactional Outbox, запустите пилот. Результаты убедят вас и бизнес быстрее, чем любые теоретические аргументы.


Компонента Simple Kafka Connector 1C версии 1.7.0 доступна на GitHub: Simple-Kafka_Adapter

Вопросы и предложения — в Issues репозитория или в комментариях к статье.

Вступайте в нашу телеграмм-группу Инфостарт

Kafka exactly once гарантированная доставка интеграция simple kafka

См. также

Перенос данных 1C Программист 1С:Предприятие 8 1С:Управление производственным предприятием 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Платные (руб)

Перенос документов, начальных остатков и справочной информации из УПП 1.3 в ERP 2 | из УПП 1.3 в УТ 11 | из УПП в КА 2 | Правила конвертации (КД 2) | Более 360 предприятий выполнили переход с использованием этого продукта! | Сэкономьте время - используйте готовое решение для перехода! | Позволяет перенести из УПП 1.3 в ERP / УТ 11 / КА 2 всю возможную информацию | В переносе есть фильтр по организации и множество других опциональных параметров выгрузки | Есть несколько алгоритмов выгрузки остатков на выбор

58000 руб.

04.08.2015    183624    425    298    

437

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Программист 1С:Предприятие 8 1С:Комплексная автоматизация 1.х 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Платные (руб)

Перенос данных из 1С:Управление производственным предприятием 1.3 в 1С:Бухгалтерия предприятия 3.0 с помощью правил обмена | Можно выполнить переход с УПП на БП 3 или запускать выгрузку данных за выбранный период времени | Переносятся документы, начальные остатки и вся справочная информация | Есть фильтр по организации и множество других параметров выгрузки | Поддерживается несколько сценариев работы: как первичный полный перенос, так и перенос только новых документов | Перенос данных возможен в "1С: Бухгалтерия 3.0" версии ПРОФ, КОРП или базовую | Переход с "1С: УПП1.3" / "1С:КА 1.1" на "1С:БП3.0" с помощью правил конвертации будет максимально комфортным! | Можно бесплатно проверить перенос на вашем сервере!

50050 руб.

25.02.2015    180549    348    283    

409

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Программист 1С:Предприятие 8 1С:Розница 2 1С:Управление нашей фирмой 1.6 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Россия Платные (руб)

Правила в универсальном формате обмена для ERP 2.5, КА 2.5, УТ 11.5, БП 3.0, Розница, УНФ, для последних версий конфигураций. Ссылки на другие конфигурации в описании публикации. Правила совместимы со всеми другими версиями конфигураций новыми и старыми, поддерживающими обмен и синхронизацию в формате EnterpriseData. Не требуется синхронного обновления правил после обновления другой конфигурации, участвующей в обмене. Типовой обмен через планы обмена кнопкой Синхронизация вручную или автоматически по расписанию, или вручную обработкой.

22650 руб.

12.06.2017    157203    939    306    

475

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Программист 1С:Предприятие 8 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Управленческий учет Платные (руб)

Перенос данных из 1С:Управление производственным предприятием 1.3 в 1С:Бухгалтерия предприятия 3.0 с помощью правил обмена. Переносятся остатки, документы (обороты за период), справочная информация. Правила проверены на конфигурациях УПП 1.3 (1.3.262.x) и БП 3.0 (3.0.190.x). Правила подходят для версии ПРОФ и КОРП.

38000 руб.

15.12.2021    32141    236    61    

177

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Программист 1С:Предприятие 8 1С:Комплексная автоматизация 1.х 1С:Управление производственным предприятием 1С:Зарплата и Управление Персоналом 3.x Россия Бухгалтерский учет Платные (руб)

Правила переноса кадровых и расчетных данных и справочной информации из "1С:УПП1.3" или "1С:КА 1.1" в "1С:ЗУП 3.1 | Разработан в формате КД 2 (правила конвертации данных) | При выгрузке есть фильтр по организациям | Обновляется при выходе новых релизов 1С | Развитие алгоритмов | Расчетные документы переносятся в документ "Перенос данных" | Создаются документы "Начальная штатная расстановка" и "Начальная задолженность по зарплате", переносятся кадровые документы

58000 руб.

29.10.2018    60902    76    125    

74

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Программист 1С:Предприятие 8 1С:Управление торговлей 10 Россия Управленческий учет Платные (руб)

Перенос данных из 1С:Управление торговлей 10.3 в 1С:Управление торговлей 11.5 с помощью правил обмена. Переносятся остатки, документы (обороты за период), справочная информация. Правила проверены на конфигурациях УТ 10.3 (10.3.88.x) и УТ 11.5 (11.5.25.x).

38000 руб.

23.07.2020    65210    303    83    

243

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Программист 1С:Предприятие 8 1С:Комплексная автоматизация 1.х 1С:Управление торговлей 10 1С:Управление производственным предприятием Россия Платные (руб)

Регулярный обмен, выгрузка, перенос из КА 1.1, УПП 1.3, УТ 10.3 для обмена с любыми конфигурациями, поддерживающими обмен в формате EnterpriseData (КД3) - БП 3.0, ERP, КА 2, УТ 11, Розница 3, УНФ 3 и другими. Правила для старых и доработанных конфигураций не требуют синхронного обновления и совместимы с новыми и будущими конфигурациями. Обмен по расписанию, через папку, FTP, почту.

16531 руб.

18.02.2016    198468    659    543    

559

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Программист 1С:Предприятие 8 1С:ERP Управление предприятием 2 1С:Комплексная автоматизация 2.х 1С:Зарплата и Управление Персоналом 3.x Россия Платные (руб)

Перенос данных из ЗУП 3 в ЗУП 3 | из ЗУП 3 в КА 2 | из ЗУП 3 в ERP | Оперативно обновляется при выходе новых релизов 1С | Готовые правила конвертации (КД 2) для перехода с "ЗУП 3" на "УП ред. 3" / "КА, ред. 2" / "ERP, ред. 2" |Переносится нормативно-справочная информация и документы с движениями

55200 руб.

11.01.2021    36960    32    56    

34
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. gybson 13.01.26 12:46 Сейчас в теме
Если объект может быть отправлен много раз, то не имеет никакого значения как это произойдет и любой получатель должен иметь это в виду. Кроме того, при сбое транспорта (кафка) может быть использован другой транспорт. Поэтому лучше вообще не завязывать на транспорт критичные операции типа списания миллионов со счета. Сразу бы уж и Inbox описали. А когда у нас есть Inbox, то вообще не важно как туда данные попали.

В недавней практике было. Захотелось ребятам заново себе всех контрагентов отправить в систему. Долго думать не стали и перезаписали их все, прям вот Контрагент.Записать(). Волновало их то, что запись влечет много регистраций и отправок? Не очень. И всегда найдется оператор, который 100 раз перезапишет накладную, чтобы она "побыстрее дошла".
2. Shmell 655 13.01.26 13:15 Сейчас в теме
(1) 1. На перезапись всегда можно контроль повесить, я использую платформенное версионирование (ИсторияДанных). Если ничего не изменилось - то смысл в outbox писать нет.

2. Про inbox. В этом и суть, что реализовывать его на стороне каждого получателя вновь и вновь - сомнительно. Да, когда есть две системы, которые обмениваются годами друг с другом - можно сделать outbox, inbox, контроль дублей, как в источнике, так в приемнике - и это будет вполне рабочая приемлемая схема. Но когда потребителей множество и их количество увеличивается, например, в рамках микросервисной архитектуры - то использование exactly once дает дополнительные гарантии и убирает проблему с повторными отправками в случае ретраев. Использовать другой вид транспорта - да, как вариант, если ваша архитектура ландшафта это позволяет сделать...
3. van_za 314 14.01.26 06:54 Сейчас в теме
На практике работаем через outbox при этом работаем с уровнем гарантий At-least-once — может быть доставлено 1 или более раз (т.е. возможны дубликаты сообщения)
Обработать один раз уже обязанность
обработчика получателя.
В нагруженных сервисах хождения в внешние сервисы внутри транзакций запрещены на уровне конвенции, собственно для этого и только для этого и используется промежуточная таблица outbox куда можно записать в одной транзакции с событием, например проведением документа.
Тоже самое с приемом, используем для фиксирования inbox, промежуточную таблицу куда записываем сообщение и далее уже из нее читаем и обрабатываем, при этом есть гарантия что получили но нет гарантии что закоммитили в Кафку, теоретически можем записать в эту таблицу несколько раз.
5. Shmell 655 14.01.26 11:34 Сейчас в теме
(3)
при этом есть гарантия что получили но нет гарантии что закоммитили в Кафку, теоретически можем записать в эту таблицу несколько раз.

Отключайте автокоммиты, записали в inbox - зафиксировали коммит в кафке, причем это еще можно и в транзакции делать, на случай, если не удалось фиксацию в Кафке произвести.

(3)
Обработать один раз уже обязанность
обработчика получателя.


Когда сервисов-потребителей много и их число постоянно растет, реализация inbox и логики обработки дублей - может стать узким местом, когда нужна действительно мгновенная интеграция, да и реализовывать такое в каждом сервисе... Не спорю, inbox многое закрывает, но в real time сервисах он может стать неким оверхэдом. Поэтому, в каких то сервисах оставляем inbox, а в сервисах где realtime = exactly once. Все зависит от ситуации и от бизнес требований.
10. van_za 314 14.01.26 12:52 Сейчас в теме
(5)

причем это еще можно и в транзакции делать - нельзя обращаться к сервисам в транзакции, так не делают, это ошибка проектирования
https://habr.com/ru/companies/lamoda/articles/678932/
4. booksfill 14.01.26 11:31 Сейчас в теме
Можно вопроc по самой компоненте?

Там реализована технология push, проще говоря, можно ли подключить обработчик ожидания сообщений от очереди,а не лезть в очередь самим, проверяя не пришло ли сообщений?
Т.е. нужен механизм подписки на свои сообщения.

Из приведенных примеров в документации на github, это не ясно (или плохо смотрел).

Вообще, беда тех компонент по работе с брокерами, что я видел, в том, что это не реализовано и приходится самим проверять очередь, а это очень плохо сказывается, когда нужна быстрая реакция на получение сообщения и мы буквально организуем DOS атаку на бедную очередь. Да и сам 1С от этого работать начинает очень не очень.
6. Shmell 655 14.01.26 11:40 Сейчас в теме
(4) принцип получения сообщений - такой же как и в примерах на других языках программирования:
1. Подключаем компоненту
2. Создаем консьюмера
3. Подписываемся на сообщения
4. Получаем в цикле сообщения

Пример кода компоненты на 1С: https://github.com/NuclearAPK/Simple-Kafka_Adapter/blob/main/docs/examples/modern_consumer.md

никакого ddos здесь нет, как и излишней нагрузки на 1С...

Пример на c#:
...

using (var consumer = new ConsumerBuilder<Ignore, string>(config).Build())
{
    consumer.Subscribe(topics);

    while (!cancelled)
    {
        var consumeResult = consumer.Consume(cancellationToken);

        // handle consumed message.
        ...
    }

    consumer.Close();
}
Показать
7. booksfill 14.01.26 12:13 Сейчас в теме
Я про этот пример со ссылке и говорил

Получаем опрос в цикле?

Пока РазрешеноСлушать Цикл
СообщениеПрочитано = Компонента.ПрочитатьСообщение();

Да еще и вот тут каждый раз дергаем базу, получая константу в цикле же: РазрешеноСлушать = Константы.Слушать.Получить();

Думаю, за кадром в примере как раз и осталось, где и когда мы этот цикл запускаем.
8. Shmell 655 14.01.26 12:24 Сейчас в теме
(7)
Получаем опрос в цикле?


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

(7)
СообщениеПрочитано = Компонента.ПрочитатьСообщение();


да это запрос в цикле из таблицы в которой 1 запись. Константа приведена в примере как один из подходов прервать цикл. Я обычно использую ограничение по длительности выполнения задания.

(7)
Думаю, за кадром в примере как раз и осталось, где и когда мы этот цикл запускаем.


в документации и в докладах (запись есть на инфостарте) я рассказывал - что один из подходов - запускается все через регл задание, которое запускает отдельное фоновое задание, внутри которого крутится цикл. Каждые 15 (к примеру) секунд задание проверяет - а живо ли фоновое, если не живо - запускает его, а если фоновое активно - ничего не делает. Так реализованы обработчики процессов в 1С:Документооборот. Поэтому схема вполне рабочая.
9. Shmell 655 14.01.26 12:27 Сейчас в теме
(7) есть так же отдельное расширение, в котором все можно посмотреть (в плане кода, подходов) https://github.com/NuclearAPK/Kafka1CExtension
11. booksfill 14.01.26 13:11 Сейчас в теме
Расширение гляну, спасибо!

А пока у меня, на основе Вашего варианта, выходит, что:
Запускаем фоновое, в нем опрашиваем очередь, тут не понял: или вообще непрерывно или все же есть какая-то пауза.
Получается то, про что я и говорил - очень часто дергаем очередь .

На примере "нормального" языка программирования псевдокод должен бы выглядеть как-то так (проверку на прерывание цикла извне опустим):

int port = 1567;

while(true) {
   res = listen(port ); // НЕ получаем сообщения! Только инфу о их наличии.
   if {res == true} 
   { 
     
     if(call externalfn()) // тут уже лезем в очередь, желательно в отдельном потоке, и читаем наши сообщения
     {
       ConnectorKafka/MessagesWasReaded();
      }

   }
   wait(100); // освобождаем такты и ждем
}
Показать



А у нас получается что-то вроде:
while(true) {
   res = ConnectorKafka.getMessages(); // есть подписка поэтому без проблем - получаем только свои сообщения
   if {res != L""} 
       if(call externalfn(res)) {
           ConnectorKafka/MessagesWasReaded();
    }
  }
   /* дальше или непрерывно молотим, тут не понял, или завершаем и ждем когда нас опять запустит регламент */
}
Показать


Если так, то это будет нормально работать, ровно до момента, когда допустимо большое время ожидания, ИЛИ если мы можем в фоновом непрерывно бомбить брокер запросами.

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

Ну вот не верю, что Вы с этим не столкнулись (хотя бы с ростом нагрузки на CPU), я точно что-то недопонял.
13. Shmell 655 14.01.26 14:43 Сейчас в теме
(11) Сообщения получаются мгновенно, при наличии таймаута. Таймаут устанавливается перед чтением
// установка таймаута для ожидания сообщений - 5 сек. 
	Компонента.УстановитьТаймаутОжидания(5000);	


Работает это так - консьюмер каждые N мс, указанные в методе УстановитьТаймаутОжидания делает новую итерацию ожидания, но если сообщения поступили в топик - тут же, не дожидаясь завершения таймаута начнет забирать сообщения - все новые, которые поступили. Поэтому мы получаем сообщения как только они попали в топик.

Попробуйте это на практике и убедитесь что все работает мгновенно без какой либо нагрузки на процессор при ожидании сообщений
14. booksfill 14.01.26 15:05 Сейчас в теме
(13) Спасибо!
Попробую.
16. gybson 14.01.26 15:29 Сейчас в теме
(11) Даже если вы сделаете чтобы сервер сам слал вам сообщения, он будет слать их точно в таком же темпе. Вряд ли вы что-то выиграете.
17. booksfill 14.01.26 16:30 Сейчас в теме
(16)
Это не рассылка по UDP.
Он будет слать сообщения подписчику только в том случае, если они есть для данного подписчика.
Более того, ему не надо слать само сообщение, а только оповестить подписчика, что оно есть.

Впрочем, меня полностью устроил ответ в (13), для большинства случаев это подойдет.
18. gybson 14.01.26 17:59 Сейчас в теме
(17) Смотря кто "он". Кролик по вебсокету шлет сообщения целиком.
19. booksfill 15.01.26 09:50 Сейчас в теме
(18) Немного не про то.
Речь про внешнюю компоненту, а как она будет общаться с самим брокером и какой это брокер - на ее стороне.

А вот в 1С желательно чтобы она вызывала внешнее событие, если это "наше" сообщение.
А не нам приходилось самим в цикле опрашивать очередь.
Насколько я понимаю, механизм внешних событий, как раз для таких случаев и заточен, чтобы не заниматься самодеятельностью.

Кстати, по СЛУХАМ (не проверял), если уж зашла речь про кролика, компонента от "серебряной пули" это умеет.

P.S.
А вообще, можно только спасибо сказать ребятам, которые эти DLL для Kafka and Rabbit написали и бесплатно раздают.

Плюс есть исходники, если что-то не устраивает можно и самим доработать, но, увы, я последний раз на С++ писал лет 20 назад. Не потяну.
20. Shmell 655 15.01.26 10:02 Сейчас в теме
(19)
я последний раз на С++ писал лет 20 назад. Не потяну.


с нейросетями все стало значительно проще )
12. Alistan007 14.01.26 14:06 Сейчас в теме
Это что, попытка сделать из брокера сообщений ESB шину? )
15. Shmell 655 14.01.26 15:27 Сейчас в теме
(12) конечно же нет. Статья рассказывает о функциональности брокера, которая доступна через внешнюю компоненту. В шине, да есть outbox, но не у всех есть шина, есть компании где 8.3.1 еще установлено и работают на обычных формы.

Опять же, насколько мне известно - шина не поддерживает exactly once семантику, а это значит что дубликация возможна и по сути задачи дедупликации (Exactly-Once processing) перекладываются на конечные системы.
Для отправки сообщения требуется регистрация/авторизация