Если вы разработчик на аутсорсе и вам поставили задачу «сделать обмен с Честным ЗНАКом из 1С» — вы, скорее всего, начали с документации API, написали несколько HTTP-запросов в Postman, получили ответы, и решили, что задача простая. Потом вы внедрили это в продакшен заказчика, и в понедельник утром ГИС МТ начал отвечать по 20 секунд на запрос, а к обеду стал возвращать 502. Ваше регламентное задание в 1С упало, очередь документов встала, а заказчик звонит с вопросом, почему товар не отгружается.
Эта статья — про то, как подготовить retry-логику к работе с API, который перегружен по определению. Не про идеальный мир, где все запросы проходят за 200 мс. А про реальный, где в пиковые часы ГИС МТ может не отвечать вообще, а ваш код должен выжить.
Почему стандартный retry в 1С не работает
Большинство обработок для 1С, которые можно найти на открытых источниках, реализуют retry примитивно: если запрос не прошёл — ждём 5 секунд и повторяем. Ещё раз не прошёл — ждём 10 секунд. Третий раз — пишем в журнал и прерываем выполнение. Это лучше, чем ничего, но при работе с ГИС МТ этого недостаточно по трём причинам.
Причина 1. 1С не умеет retry без блокировки
Регламентное задание в 1С выполняется синхронно. Пока оно ждёт ответа от ГИС МТ — оно занято. Если вы делаете retry внутри этого же задания, вы умножаете время выполнения на количество попыток. При трёх retry с паузой по 10 секунд одна операция занимает 30 секунд. Если в очереди 100 документов — регламентное задание будет работать 50 минут вместо 5. А если ГИС МТ лежит полностью — задание просто не завершится до перезапуска.
Причина 2. Нет различия между retriable и fatal ошибками
500-я ошибка от ГИС МТ — это retriable: повторим позже, когда система восстановится. 400-я ошибка «невалидный КМ» — это fatal: повторный запрос с теми же данными не исправит ситуацию. Стандартный retry не различает эти случаи и тратит попытки на то, что никогда не пройдёт.
Причина 3. Нет наблюдаемости
Когда retry происходит внутри регламентного задания 1С, вы не видите, сколько попыток было сделано, какие документы застряли, какова текущая задержка. Вы видите только финальный результат: успех или ошибка. Для отладки в продакшене этого недостаточно.
Архитектура retry, которая выживает при перегрузке ГИС МТ
Мы прошли через несколько итераций retry-логики при интеграции с Честным ЗНАКом и пришли к архитектуре, которая работает стабильно даже в дни, когда ГИС МТ «лежит». Она состоит из пяти слоёв.
Слой 1. Асинхронная очередь вместо синхронных вызовов
Первое и главное решение: вынести обращения к ГИС МТ из синхронного потока 1С в асинхронную очередь. Мы используем Symfony Messenger с Redis в качестве транспорта. Когда в 1С происходит событие, требующее обращения к ГИС МТ — например, отгрузка маркированного товара — 1С не делает HTTP-запрос сама. Она записывает событие в регистр, который коннектор читает по OData, и коннектор отправляет сообщение в очередь.
Это решает проблему блокировки: 1С завершает свою операцию за миллисекунды, не дожидаясь ответа от ГИС МТ. Consumer-процессы обрабатывают сообщения параллельно и независимо. Если ГИС МТ недоступен — сообщения накапливаются в очереди, а 1С продолжает работать.
Слой 2. Классификация ошибок до retry
Перед тем как решить, retry делать или нет, мы анализируем ответ ГИС МТ. Не HTTP-статус — он часто 200 даже при ошибке. А тело ответа. Мы выделили четыре класса ошибок.
| Класс | Признаки | Действие |
|---|---|---|
| Транзиентная | Таймаут, 502, 503, 504, пустой ответ | Retry с экспоненциальным backoff. Максимум 5 попыток. |
| Rate limit | 429, «превышен лимит», «система перегружена» | Retry с увеличенной задержкой (1, 5, 15, 30, 60 мин). Плюс глобальный rate limiter. |
| Валидационная | 400, «невалидный КМ», «контрагент не найден», «документ уже существует» | Без retry. Сразу в dead-letter с описанием ошибки для ручной обработки. |
| Авторизационная | 401, 403, «истёк срок действия сертификата» | Обновление УКЭП/токена, затем retry. Если не удалось — алерт администратору. |
Ключевое отличие от примитивного retry: валидационные ошибки не тратят попытки. Если КМ невалиден — мы не будем слать его в ГИС МТ 5 раз, надеясь, что вдруг станет валидным. Сразу отправляем в dead-letter, уведомляем оператора, и он исправляет данные в 1С.
Слой 3. Экспоненциальный backoff с джиттером
Простой фиксированный интервал между retry — плохая идея. Если ГИС МТ восстанавливается после сбоя, и все клиенты начинают слать запросы одновременно с одним и тем же интервалом — получается thundering herd, и система падает снова. Мы используем экспоненциальный backoff с джиттером: задержка растёт, но к ней добавляется случайное отклонение до 30%.
Наши интервалы: 1 минута, 5 минут, 15 минут, 30 минут, 1 час. После пятой попытки сообщение уходит в dead-letter. Это не значит, что проблема неразрешима — оператор может вручную переотправить после исправления внешних условий. Но система не будет бесконечно пытаться то, что не получается.
Слой 4. Circuit breaker
Если ГИС МТ возвращает ошибки подряд — retry только усугубляет ситуацию. Вы создаёте лишнюю нагрузку на уже упавшую систему и тратите ресурсы своего сервера на бесполезные попытки. Circuit breaker решает это: после 10 подряд идущих ошибок клиент переключается в состояние OPEN и перестаёт делать запросы на 10 минут.
В состоянии OPEN новые сообщения не отправляются в ГИС МТ, а накапливаются в очереди. Каждые 2 минуты клиент делает пробный запрос (half-open). Если он проходит — circuit breaker закрывается, и обработка возобновляется. Если нет — остаётся открытым. Это защищает обе стороны: и ваш сервис от бесконечных таймаутов, и ГИС МТ от лишней нагрузки.
Слой 5. Глобальный rate limiter
Даже когда ГИС МТ работает нормально, он имеет лимиты на количество запросов. Эти лимиты не всегда задокументированы, они могут меняться, и они зависят от типа операции. Мы реализовали глобальный rate limiter на уровне коннектора: не более N запросов в минуту к ГИС МТ, где N подбирается эмпирически и может отличаться для разных эндпоинтов.
Rate limiter работает через Symfony RateLimiter с фиксированным окном. Если лимит исчерпан — сообщение возвращается в очередь с задержкой 1 минуту, не тратя попытку retry. Это отдельный механизм от retry: retry ловит ошибки от ГИС МТ, rate limiter предотвращает их появление.
Практические сценарии: что делать, когда ГИС МТ «лежит»
Теория хороша, но разработчику на аутсорсе нужны конкретные рецепты. Вот три сценария, которые мы отработали в бою.
Сценарий 1. ГИС МТ не отвечает вообще (таймаут на все запросы)
Симптом: все запросы падают по таймауту, circuit breaker переходит в OPEN. Что делает система: новые сообщения накапливаются в очереди Redis. Consumer-процессы не пытаются делать запросы, а ждут. Каждые 2 минуты делается пробный запрос. Как только ГИС МТ отвечает — обработка возобновляется автоматически. Заказчик не получает ошибок, потому что 1С продолжает работать, а очередь просто подождёт.
Сценарий 2. ГИС МТ отвечает, но через 20–30 секунд
Симптом: запросы проходят, но с огромной задержкой. Стандартный таймаут в 10 секунд не подходит — нужно увеличить. Но увеличение таймаута ведёт к блокировке consumer-процессов. Решение: мы настроили два таймаута. Connection timeout — 5 секунд. Read timeout — 45 секунд. Если соединение не установилось за 5 секунд — retry. Если установилось, но ответ идёт долго — ждём до 45 секунд. Это позволяет пропустить медленные, но успешные запросы, и при этом не блокировать процессы на вечность.
Сценарий 3. Часть запросов проходит, часть падает с 500
Симптом: ГИС МТ работает нестабильно, отвечая то 200, то 500. Это самый сложный случай, потому что circuit breaker не сработает — ошибки не идут подряд. Решение: мы добавили «скользящее окно» ошибок. Если за последние 5 минут более 50% запросов вернули ошибку — временно снижаем rate limit вдвое и увеличиваем backoff. Это не останавливает систему полностью, но снижает нагрузку и повышает вероятность успеха для оставшихся запросов.
Как не потерять коды маркировки в retry
Самый страшный риск при retry — потерять код или, наоборот, создать дубль. ГИС МТ не идемпотентен для всех операций. Если вы отправили запрос на ввод в оборот, получили таймаут, и сделали retry — код может быть введён дважды. Или, наоборот, первый запрос прошёл, но вы не получили ответ, и retry создаёт второй документ.
Каждый запрос снабжается уникальным идентификатором. ГИС МТ отбрасывает дубли по correlation ID. Если retry отправляет тот же запрос с тем же ID — система вернёт результат первого запроса, а не создаст новый документ.
Перед отправкой запроса мы записываем в базу: «код X, операция Y, correlation ID Z, статус pending». После получения ответа обновляем статус. Если процесс упал между отправкой и получением — при перезапуске мы видим pending-запись и можем проверить статус в ГИС МТ, не отправляя заново.
Некоторые операции ГИС МТ идемпотентны по своей природе: получение статуса кода, получение списка документов. Их можно повторять без риска. Операции, изменяющие состояние — ввод в оборот, агрегация, выбытие — требуют correlation ID и локального хранения.
Что сказать заказчику, когда ГИС МТ лежит
Разработчик на аутсорсе сталкивается с неприятной ситуацией: система заказчика работает, но внешний сервис — нет. Заказчик не различает, где проблема: в вашем коде или в ГИС МТ. Ваша задача — дать ему инструменты для понимания ситуации.
Дашборд статуса интеграции. Простая страница, показывающая: сколько сообщений в очереди, сколько в обработке, сколько в dead-letter, состояние circuit breaker, последний успешный запрос к ГИС МТ. Заказчик видит, что ваш код работает, а ГИС МТ не отвечает — и звонит не вам, а в техподдержку оператора.
Уведомления. Если сообщение ушло в dead-letter — уведомление в Telegram или на почту ответственному лицу. Если circuit breaker открыт — уведомление, что интеграция временно приостановлена и возобновится автоматически. Это снижает тревожность заказчика и сокращает количество «панических» звонков.
Логи с correlation ID. Когда заказчик говорит «документ не прошёл» — вы за 10 секунд находите в логе запрос по correlation ID, видите, что был таймаут, три retry, и документ ушёл в dead-letter. Это не «ваш код сломан» — это «ГИС МТ не ответил, вот логи, вот время, вот статус».
Инструменты для диагностики: как понять, ГИС МТ лежит или ваш код сломан
Самая сложная задача при работе с перегруженным API — отличить проблему на своей стороне от проблемы на стороне поставщика. Заказчик не будет разбираться в деталях. Для него «не работает обмен» — это единственная метрика. Ваша задача — иметь данные, которые докажут, где именно проблема.
Health-check эндпоинт
Мы реализовали отдельную команду gis:health-check, которая каждые 2 минуты делает пробный запрос к ГИС МТ и записывает результат. Не бизнес-запрос — а простой GET статуса системы или получение списка документов за последний час. Это позволяет построить график доступности API за сутки, неделю, месяц. Когда заказчик говорит «вчера ничего не работало» — вы показываете график: «ГИС МТ был недоступен с 14:00 до 16:30, в это время ваши документы накапливались в очереди, и после восстановления были обработаны автоматически».
Логирование с контекстом
Каждый запрос к ГИС МТ логируется с полным контекстом: correlation ID, эндпоинт, время выполнения, HTTP-статус, размер тела ответа, количество retry, причина retry. Это не просто «запрос упал» — это «запрос X к эндпоинту Y упал с таймаутом после 45 секунд, это была попытка 3 из 5, предыдущие попытки были в 10:15 и 10:20». Такой уровень детализации позволяет разобрать любой инцидент за минуты, а не часы.
Сравнительный мониторинг
Мы настроили параллельный health-check с двух разных серверов в разных дата-центрах. Если оба показывают недоступность — проблема в ГИС МТ. Если один показывает доступность, а другой — нет — проблема в сети или маршрутизации на стороне заказчика. Это снимает споры «у нас всё работает, у вас нет».
1С-обработка vs отдельный сервис: где делать retry
Разработчику на аутсорсе часто предлагают «просто сделать обработку в 1С». Это быстрее, дешевле для заказчика на старте и не требует отдельной инфраструктуры. Но при работе с ГИС МТ этот подход имеет скрытые затраты, которые проявляются через месяц эксплуатации.
| Критерий | Обработка в 1С | Отдельный сервис (Symfony) |
|---|---|---|
| Retry без блокировки | Невозможен — регламентное задание ждёт | Очередь Messenger, consumer работает независимо |
| Circuit breaker | Требует ручной реализации, сложно тестировать | Готовые библиотеки (Symfony HttpClient, Gosub) |
| Rate limiting | Нет встроенного механизма | Symfony RateLimiter из коробки |
| Мониторинг | Журнал регистрации 1С | Prometheus, Grafana, алерты |
| Масштабирование | Ограничено мощностью сервера 1С | Горизонтальное — добавление consumer-процессов |
| Стоимость поддержки | Низкая на старте, растёт с объёмом | Фиксированная, не зависит от числа документов |
Вывод для разработчика на аутсорсе: если заказчик просит «просто обработку в 1С» — объясните ему, что это решит задачу на сегодня, но создаст проблемы при росте. Если объём документов не превышает 20–30 в день — обработка справится. Если больше — отдельный сервис окупится за 2–3 месяца за счёт снижения инцидентов и времени на поддержку.
Итог
Работа с API Честного Знака — это не интеграция в классическом понимании. Это интеграция с системой, которая по своей природе нестабильна, непредсказуема и не всегда документирована. Стандартный retry в 1С с фиксированной паузой не справляется с этой реальностью.
Надёжная retry-логика требует: асинхронной очереди, классификации ошибок, экспоненциального backoff с джиттером, circuit breaker, глобального rate limiter, correlation ID для идемпотентности и прозрачного мониторинга. Всё это — стандартные паттерны, которые Symfony реализует через Messenger, RateLimiter, HttpClient и Workflow. Не нужно изобретать велосипед внутри 1С — нужно вынести интеграцию в отдельный сервис, который умеет выживать.
Если вы разработчик на аутсорсе и перед вами стоит задача интеграции с Честным ЗНАКом — не начинайте с эндпоинтов. Начните с архитектуры retry. Это решение сложнее изменить потом, чем добавить ещё один метод API. Наш опыт вынесен в open-source коннекторы, где можно посмотреть реализацию всех паттернов, описанных в статье.