Один узел. Четыре ядра, 15 гигабайт памяти, Docker на Linux. Через него идёт около миллиона сообщений в сутки между десятком систем, и при этом управляющий слой (консоль и её API) умирал раз в один-два дня.
Ни одно падение не упиралось в производительность процессора.
Это разбор эксплуатации 1С:Шины 6.1.6 в проде розничной сети. В центре связки учётная система на 1С:УПП, вокруг неё зарплатная база, бухгалтерия, фронт-офис касс, кассовый сервис, BPM, WMS, CRM с программой лояльности, сервисы доставки и инвентаризации.
Две оговорки. Наблюдения за год, а замеры за последние тридцать суток: хранение метрик настроено на месяц, годовых рядов нет, экстраполировать не буду. И про жанр: как Шину разворачивать и как её наблюдать, на Инфостарте уже написано, в том числе свежий разбор отказоустойчивости от июля этого года. Повторять не буду, в паре мест сошлюсь.
Отдельная глава дальше - про то, как наш собственный мониторинг показывал 3,8 миллиона ошибок, которых не было.
Словарь на три строки
Процесс это одна интеграционная схема, у нас их 18. Узел это точка внутри схемы, где сообщение обрабатывается или передаётся дальше, их 173. Приложение это единица развёртывания внутри Шины, их запущено 15, и рестартовать их можно по отдельности, не трогая контейнер. Канал это направление обмена на стороне 1С, их 62. Участник это учётная запись подключения к Шине: кто под ней подключился, тот и забирает сообщения.
Про брокеры - дальше их будет два. Внутри Шины едет встроенный ActiveMQ Artemis. Рядом стоит внешний RabbitMQ, через него часть систем общается с Шиной. Ломались они по-разному.
Пять дефектов, которые копились с аптаймом
Теперь то, ради чего вы открыли статью. За год набралось пять дефектов, и все пять объединяет одно: на коротком стенде они невидимы, потому что стенд живёт сутки и пересоздаётся. Это не значит, что они невоспроизводимы - как минимум два повторяются за час.
1. Дефолт Docker в десять секунд превращал баг брокера в отказ
Журнал встроенного брокера рос на 10 гигабайт в сутки линейно с аптаймом. Пишется он файлами-сегментами, и их накопилось 3 956 - это 38 гигабайт. Диск расширяли с 98 до 148 гигабайт и упирались снова.
В логе при этом Cannot find add info ... on compactor - известный баг компактации Artemis, то есть уплотнения журнала, когда брокер выбрасывает отработанные записи. Баг воспроизводится во всех версиях вплоть до 2.24.0, а внутри Шины едет более старая 2.14.0 от 2020 года, то есть наша под него попадает. Внешнего broker.xml нет, настройками брокера не лечится. Пятилетний брокер без доступа к конфигу внутри коммерческого продукта - мягко говоря, не то, что ожидаешь от готового решения.
Триггер оказался не в брокере. В docker-compose.yml не был задан stop_grace_period, дефолт Docker - десять секунд. Брокер с большим журналом за это время закрыться не успевает, получает SIGKILL, журнал повреждается, компактор ломается, дальше рост.
Фикс целиком:
stop_grace_period: 300s
Результат: аптайм 8,5 суток, журнал 93 сегмента вместо 3 956, это 930 мегабайт против 38 гигабайт. Вхождений Cannot find add info в логах ноль.
Но заслугу этой строке я приписать не могу. Одновременно с ней мы выключили режим разработки (следующий дефект), а он ронял управляющий слой раз в двое суток. Меньше падений - меньше рестартов - меньше SIGKILL - меньше повреждений журнала. Это объясняет результат ничуть не хуже, чем grace period, а разделить вклад двух изменений я не могу: они ушли в одно окно, контрольной группы нет. Чтобы доказать вину дефолта, надо было замерить, сколько занимает штатное закрытие брокера. Не замерил.
Как журнал дорастал до 38 гигабайт при падениях раз в двое суток: 38 на 10 - почти четверо суток роста. Значит он переживал аварийные рестарты и копился сквозь них, что логично при повреждённом журнале: схлопнуться он уже не мог.
Отдельное наблюдение: при штатном рестарте восстановление журнала (recovery) схлопнуло его с 38 гигабайт до 270 мегабайт. То есть 37,7 гигабайта были мёртвыми записями - при условии, что recovery ничего не выбросил. Сохранность сверкой по идентификаторам мы не проверяли, хотя ниже, в инциденте с посторонним потребителем, я сам показываю, что статусам верить нельзя.
Этот дефект - из тех двух, что повторяются за час: налить журнал на стенде и дёрнуть docker stop, вся проверка.
2. Управляющий слой умирал раз в двое суток
Консоль и Console API переставали отвечать: запросы доходили, ответа не было. Брокер при этом живой, служебная проверка живости (heartbeat) отдаёт 200, процессор простаивает, обмены стоят.
В служебном логе незакрытых ресурсов сто процентов записей приходило из одной точки кода платформы: незакрытый результат запроса к внутренней базе консоли на H2 - это встроенная Java-СУБД, живёт файлом рядом с приложением. Темп примерно шесть штук в минуту, около 8 600 в сутки.
Усилитель нашёлся рядом: все приложения работали в режиме разработки с включённой отладкой. Тогда их было семнадцать, сейчас запущено пятнадцать. Фоновое задание опрашивало статус среды разработки и дёргало тот самый метод с утечкой. Как режим разработки оказался включён на всех продуктивных приложениях и дожил до привычных падений раз в двое суток - вопрос без хорошего ответа. Никто не смотрел.
Выключили - утечка остановилась ровно в момент изменения, не постепенно. Счётчик незакрытых результатов за весь новый аптайм 67, прирост за сутки ноль.
Чего в этой истории не хватает. Я показываю темп утечки, но не показываю предел, в который она упиралась. Восемь с половиной тысяч незакрытых результатов в сутки при пуле в несколько десятков соединений исчерпали бы его за минуты, а отказ наступал раз в двое суток - значит упиралось что-то другое: дескрипторы, память, что-то третье. Я не снял ни дамп потоков в момент зависания, ни лимиты. Связь «выключили режим разработки - перестало падать» у меня есть, механизма нет.
Урок про мониторинг тут важнее урока про флаг: сервер отвечал «я жив» ровно тогда, когда управление и обмены были мертвы. Наш watchdog проверял корневой URL и получал 301.
3. Диск был свободен наполовину, а inode заканчивались
Диск занят на 84 процента, 125 гигабайт из 148. Терпимо. А вот inode на 85 процентах, 8,27 миллиона, и это дни до аварии.
При включённом хранении доставленных Шина пишет каждое сообщение в два места: описание в базу PostgreSQL, тело - отдельным файлом на диске (тело бывает любым, от короткого XML до вложений на мегабайты, потому и файлом). База ротируется корректно, retention, то есть срок хранения, сутки. А файлы с телами сборщик мусора платформы не забирает никогда.
Лечение: ночной cron, удаляющий эти файлы старше retention. Результат замерен: диск 125 → 90 гигабайт, inode 8,27 → 1,53 миллиона, то есть освободилось 35 гигабайт и 6,74 миллиона файлов. Моя прикидка до уборки - «около 7,5 миллиона файлов на 40-45 гигабайт» - оказалась завышенной, верьте замеренной дельте.
Предупреждаю: рекомендация разрушительная. Мой вывод «эти файлы осиротели» держится на логике. Выборку имён с базой я не сверял. И «не удаляются никогда» - наблюдение за месяц на установке, где половину времени приложения крутились в режиме разработки и падал управляющий слой; может, сборщик мусора просто не отрабатывал по этой причине. Прежде чем ставить у себя cron, который сносит файлы из хранилища платформы, проверьте выборку на наличие ссылок.
Почему накопилось ровно месячный объём, если удаление не работает вовсе: хранение доставленных мы включали точечно в июне, под расследование инцидента с пропажей сообщений. Включили, разобрались, забыли выключить. Через месяц оно чуть не положило хранилище.
4. Скрипт бэкапа, который тихо умер
tar на живых данных отдаёт file changed as we read it и возвращает код 1. А в скрипте стояло set -euo pipefail - режим «умри при первой же ошибке», - и он честно убивал его на середине.
Итог: скрипт в crontab был, свежих бэкапов не было. Старые лежали, больше пятидесяти снапшотов на 24,5 гигабайта, то есть когда-то он работал и сломался в какой-то момент, а когда именно, мы не знаем: ротации не было, дат никто не смотрел. Плюс 7,1 гигабайта незавершённого архива внутри контейнера.
Проверяется это наличием свежего файла нужного размера. Строка в расписании не доказывает ничего.
5. Отвал потребителя от внешнего брокера
Это второй брокер из словаря, внешний, и ломается он, как и обещано, иначе.
Очередь есть, сообщения есть, потребителей ноль. Шина отвалилась как потребитель. Лечится рестартом приложения, контейнер трогать не нужно.
В агрегате это выглядело как 26 потребителей на 28 очередей. Само по себе это ничего не доказывает: часть очередей у нас без потребителей by design, о чём отдельный сюжет ниже. Правильный признак - очередь, где одновременно есть необработанный остаток и ноль потребителей.
Отставание копится не в Шине, а в 1С
Все пять дефектов ломали саму Шину. Самый дорогой отказ года выглядел иначе: со стороны шины всё было идеально.
На стороне приёмника 1С складывает сообщения до разбора в буфер - физически это три регистра сведений: основной в учётной системе, фронт-офис касс и кассовый сервис.
Максимумы за 30 дней по каждому:
| Буфер | Максимальный возраст | Максимальная глубина |
|---|---|---|
| основной | 111 698 с = 31,0 ч | 2 863 |
| фронт-офис касс | 2 532 с = 0,7 ч | 963 |
| кассовый сервис | 2 232 с = 0,6 ч | 391 |
Тридцать один час. Очереди самой Шины в этот момент были пусты.
Почему это не то, что вы подумали
Первая реакция на «31 час отставания» - разбор встал, в буфере гора сообщений. Посчитаем: основной буфер, дальше я буду звать его регистром-приёмником, принимает 6,18 миллиона записей за 14 дней, это 441 тысяча в сутки. Если бы разбор стоял 31 час, там лежало бы порядка 570 тысяч записей.
Их там не было. Замер по часам во время инцидента:
18.07 07:31 возраст 1,1 ч в буфере 44 18.07 23:31 возраст 10,7 ч в буфере 197 19.07 11:31 возраст 22,7 ч в буфере 310 19.07 19:31 возраст 30,7 ч в буфере 196 ← пик возраста
44 сообщения, 310, 196.
Разбор всё это время исправно работал, а стояла голова: одно сообщение не разбиралось и старело вместе с часами.
Причём таких сообщений было минимум два. Между первым и вторым замером прошло 16 часов, а возраст вырос только на 9,6 часа - значит первая голова как-то ушла, её сменила следующая. Дальше, с 23:31, возраст растёт синхронно с часами: за 12 часов плюс 12, за 8 часов плюс 8 (10,7 → 22,7 → 30,7). Вот эта часть чистая.
Чего я не знаю и знать бы хотел: что именно застряло. Тип сообщения, что его держало, чем эпизод кончился - сам рассосался, руками или рестартом. В разборе этого нет, и поэтому «мониторьте возраст» у меня получается советом мерить симптом болезни, которую я не диагностировал.
Эпизод повторился через два дня по той же схеме и доехал до 16,4 часа. Два случая это «повторилось». Воспроизводимость означала бы, что я умею вызвать отказ по требованию, а я не умею.
Максимум глубины, кстати, случился в другой день, 29 июля, и возраст старейшего был тогда ноль часов. Два максимума, выглядящие одним инцидентом, не связаны вообще.
Почему это касается и вас
Любой мониторинг длины очереди в те двое суток показывал зелёное.
А канал стоял тридцать один час.
Этот класс отказов ловится возрастом самого старого необработанного сообщения. Длина ловит остановку разбора, возраст ловит застрявшую голову. Это разные отказы, и второй при нормальной глубине невидим.
Когда соврал наш собственный мониторинг
Этот сюжет мы нашли, когда готовили материал, и он мне нравится больше всех остальных.
Метрика «ошибок текстового лога брокера» показывала 3 801 769. В логах при этом ноль таких строк.
Экспортёр - скрипт, который отдаёт метрики в Prometheus, - выполняет два действия подряд: берёт inode файла и считает совпадения по шаблону. Первую строку вывода читает как inode, вторую как счётчик. Лог у нас ротируется каждые девять минут, при журнале, растущем на десятки гигабайт, иначе никак. И в момент ротации grep не находит файл и не отдаёт вторую строку. Код невозмутимо принимает номер inode за число совпадений.
3 801 769 - это буквально inode файла лога. Метрика питала боевой алерт и месяцами никого не разбудила, что само по себе говорит о качестве наших алертов больше, чем хотелось бы.
Рядом нашлась вторая: максимум времени ответа консоли ровно 30,000 секунды. Ровно. Это не задержка, а штрафная заглушка при неудачном запросе токена авторизации к консоли. Как индикатор отказа годится, как метрика задержки нет - реальный 95-й перцентиль 23 миллисекунды.
И третий случай того же сорта, только жертвой был я сам. Считая объём трафика, я снял его вторым источником - со служебных HTTP-адресов приложений, отдающих счётчики. Получилось 8,47 миллиона сообщений за 8,5 суток аптайма, то есть 996 тысяч в сутки против 995 у Prometheus, расхождение 0,16 процента. Красиво.
Только Prometheus эти же адреса и опрашивает, счётчик там тот же самый. Я дважды прочитал одно число и чуть не назвал это независимой проверкой. Проверено на деле другое: сбор и хранение метрик ничего не теряют по дороге. Метод подсчёта этим не подтверждается.
А теперь неудобный вопрос к самому себе. Если один экспортёр показывал inode вместо счётчика, второй писал заглушку вместо задержки, а третью проверку я сам себе выдумал, почему вы должны верить 31 часу из того же хозяйства? Отвечаю: возраст сообщения считается из поля времени, которое отдаёт сама 1С. На часы машины мониторинга мы не смотрим с тех пор, как наступили на их расхождение с сервером. Когда именно перешли, до июльского эпизода или после, у меня не записано. Значит и к 31 часу оговорка нужна: порядок величины я подтверждаю почасовым рядом, точность до минут не гарантирую.
Урок шире Шины: мониторинг надо верифицировать. Возьмите пару самых пугающих метрик и сходите руками в источник. У нас на это ушло полчаса.
И грабля оттуда же: правка правил алертинга через sed -i не применяется. Файл, проброшенный в контейнер поштучно (bind-mount), привязан к inode, а sed -i создаёт новый файл. На хосте правка видна, контейнер читает старый, перезагрузка конфигурации отвечает 200 и грузит старое. Лечится записью поверх или рестартом. Это вторая из обещанных быстро воспроизводимых граблей. В пятёрку дефектов она не попала, потому что к длинному аптайму отношения не имеет вовсе: повторяется на ноутбуке за пять минут.
Три инцидента с данными и одно условие
Дефекты выше ломали саму Шину. Следующие три истории хуже: в них страдали данные, а журнал Шины при этом показывал «доставлено».
Посторонний потребитель съедал часть потока
Сообщение помечено «доставлено» в журнале Шины, а в продуктивной базе его нет.
Причина: второй сеанс 1С конкурентно читал того же участника шины, что и продуктив. Сообщения делились между ними.
Масштаб - тут надо аккуратно. Из пятидесяти проверенных сообщений в проде нашлись восемнадцать: потеряно 64 процента выборки. На всё окно, а это около семи тысяч сообщений, доверительный интервал даёт от 51 до 77 процентов, то есть от 3,5 до 5,4 тысячи. Точное «около 4 500», которое я написал сначала, этой точности не заслуживает.
Хуже другое: сплошная проверка была доступна. Идентификаторы всего окна есть, регистр с 6,18 миллиона записей есть, запрос пишется за минуту. Мы этого не сделали, и правильного ответа у меня нет - есть интервал.
Retention регистра-приёмника 14 дней против шести часов на Шине, поэтому «нет в регистре» не объясняется вычисткой по сроку. Но полностью исключить отбраковку бизнес-логикой или собственной дедупликацией приёмника я не могу: этой проверки мы тоже не делали.
Что спасло: приёмник идемпотентен, дубли отбрасываются, поэтому мы просто переслали всё окно целиком. Как именно устроена дедупликация - по идентификатору сообщения, по хешу, с каким окном - в разборе не зафиксировано. Вопрос при этом самый практический во всей статье. Знаю только объём: 91 781 повторный идентификатор в сутки, и это 21 процент от суточного потока этого приёмника (441 тысяча записей). От общего потока Шины доля была бы вчетверо меньше, поэтому знаменатель называю явно.
Двадцать процентов штатных повторов - повод разбираться, в статистику такое записывать рано. Мы не разбирались.
Главный урок: «доставлено» в журнале шины не равно «загружено» в системе-приёмнике. Счётчик недоставленных этот случай не ловит по построению - для Шины всё штатно. У нас 61 недоставленное на 26 миллионов, 2,5 на миллион, цифра прекрасная и совершенно не про то.
Переотправка - только кнопкой в консоли, публичного метода нет, автоматизировать можно роботом по UI. Для промышленной интеграционной платформы это странно.
Рестарт Шины клинит очереди в чужой базе
Cannot insert duplicate key по уникальному индексу входящей очереди канала. Очередь встаёт, сообщения не грузятся.
Позицию в очереди задаёт Шина. После рестарта она начинает нумеровать позиции с более раннего значения, и новые сообщения получают номера, которые 1С уже видела и считает обработанными. Очереди строго FIFO, поэтому одна коллизия в голове блокирует канал целиком.
Масштаб: крупнейшая таблица очереди 2,78 миллиона строк. Автолечение мы написали отдельным сервисом: он обходит очереди раз в минуту, за 30 дней 48 833 прохода, ноль ошибок, срабатывало около 22 раз.
Критерий, по которому сервис удаляет строку: позиция, где одновременно висит и необработанное, и уже обработанное сообщение с тем же идентификатором. Ошибиться тут можно, и цена ошибки - потерянное сообщение, поэтому у нас он работает только на входящих очередях и только на точном совпадении идентификатора.
Урок: рестарт Шины бьёт и за её пределами, по базам-приёмникам.
Сообщения в очереди без потребителя
Девять подтверждений улетели с ключом маршрутизации базовой очереди, то есть с адресом, по которому брокер выбирает очередь. У базовой потребителей ноль, а уйти они должны были в специализированную. До этого очередь была пуста весь месяц, залёт случился одной пачкой, предположительно в окно переподключения.
Очереди без потребителей by design - тихая дыра. Их надо либо явно исключать из алертов, либо мониторить отдельно.
Условие, при котором инциденты нерасследуемы
Это не инцидент, а рамка для всех трёх. Тела сообщений на шине живут около шести часов - столько доступна переотправка. Описания в базе живут сутки, но без тел бесполезны. Регистр в 1С хранит 14 дней.
Через сутки после сбоя расследовать уже не по чему. Это обратная сторона третьего дефекта: хранение доставленных либо съедает диск, либо его нет, когда нужно расследовать.
Теперь цифры, и сначала о том, как они посчитаны
До сих пор были истории. Дальше таблицы - но прежде надо сказать, как они посчитаны, иначе им нельзя верить.
Метрика node_message_counter считает сообщения на каждом узле схемы. Просуммировать все узлы - каждое сообщение посчитается по числу пройденных узлов. У нас это шестикратная разница: 26,1 миллиона против 147,5 миллиона за одно окно.
Поэтому считаем так:
- сообщения = сумма по процессам от максимального счётчика среди узлов процесса;
- события на узлах = сумма по всем узлам, и это метрика работы; сообщений она не считает.
sum(max by (process) (increase(node_message_counter[30d])))
Увидите в чужой статье про интеграцию красивое число - спросите, каким из двух способов оно получено.
Метод занижает, и это надо сказать прямо. Внутри одного процесса могут идти несколько независимых встречных потоков: в самом нагруженном у нас поток в одну сторону даёт 10,0 миллиона, встречный 9,8 миллиона. Максимум берёт только первое.
Насколько? Недоучёт 9,8 миллиона сверх посчитанных 26,1 - около 38 процентов по одному процессу. Сколько таких среди остальных семнадцати, не считал. Поэтому дальше везде читайте «не менее». Слово «вдвое» я писать не стану: из моих чисел оно не выводится.
Настоящая независимая сверка - число загруженных записей у приёмника с коэффициентом «сообщение к записям». Её я не делал.
Что показали замеры
Четыре среза: поток, суточный профиль, ресурсы, очереди.
Поток
Все цифры сняты 17 августа.
| Показатель | Значение |
|---|---|
| Сообщений за 30 дней | не менее 26 092 008 |
| Среднее по суткам (сумма за окно, делённая на 30) | около 870 000 |
| Медиана по суткам | 830 547 |
| Самые тяжёлые сутки | 2 976 871 |
| В среднем | около 10 сообщений в секунду |
| Пик пятиминутного окна | 129,4 в секунду |
| Пик часового окна | 102,6 в секунду |
Медиана и пиковые сутки разошлись в 3,6 раза.
Сразу оговорюсь, потому что вопрос законный: размер и состав типичного сообщения я не мерил. Поэтому «миллион в сутки» примерить на свою систему по этим цифрам нельзя, не зная, что лежит у нас внутри сообщения. Сравнивать имеет смысл динамику и профиль; абсолютные величины тут мало что дают.
Здесь я чуть не обманул сам себя. Первые десять суток окна давали в среднем 722 788 сообщений, последние десять - 958 108. Рост на 32,6 процента, и я сходу написал абзац про растущую нагрузку, по которой бессмысленно планировать. Абзац пришлось выкинуть: в первые десять суток управляющий слой падал раз в один-два дня и обмены стояли, в последние 8,5 суток - ни разу. Я измерил не рост нагрузки, а собственное восстановление после фиксов. Тридцать два процента это мера потерь на простоях, и так цифра куда интереснее.
Если считаете тренд по окну с инцидентами, вы считаете не тренд. Сначала аптайм, потом график.
Суточный профиль
Разброс между самым тихим ночным часом и пиковым дневным - больше тридцати пяти раз: 2,9 сообщения в секунду против 102,1.
Оговорка: числа с разных суток. Пиковый час это максимум за тридцать дней, ночной провал с типичных суток. Внутри одних суток размах меньше, отдельного замера нет. Максимум часа против типичной ночи - тот самый приём, о котором я предупреждал выше, называю его прямо.
Ещё у ночи есть своя вершина: 47,5 сообщения в секунду в час пакетного обмена, там, где ждёшь провала. Если строить порог от «ночью тихо», он сработает по этому батчу.
Алерт с фиксированным порогом на поток будет фолзить. У нас он звонил каждую ночь, пока мы не добавили условие на наличие необработанного остатка.
Запас по ресурсам
Память в таблице ниже - по контейнеру Шины, хост считается отдельно: там ещё внешний RabbitMQ и вспомогательные сервисы.
| Ресурс | Сейчас | Максимум за 30 дней |
|---|---|---|
| CPU хоста | 24,7 % | 59,7 % |
| CPU в среднем | 21,7 % | - |
| Память контейнера Шины | 5,5 ГБ | 8,25 ГБ |
| load average (1 мин) | 0,73 | 6,91 |
load average - среднее число процессов, ждущих выполнения. По процессору запас кратный (миллион сообщений при 22 процентах), по памяти скромнее: максимум контейнера 8,25 гигабайта при 15 на всём хосте, запас меньше двукратного, и лимит самого контейнера я не догадался зафиксировать.
Пиковый load average 6,91 на четырёх ядрах выглядит тревожно, и у меня была версия, что это очередь на диск - журнал встроенного брокера тогда рос на 10 гигабайт в сутки, а файлы прибывали сотнями тысяч в день (оба сюжета разобраны выше, в дефектах 1 и 3).
Замер её не подтвердил. iowait, то есть доля времени процессора в ожидании диска, в среднем 1,01 процента, максимум 11,12. Утилизация диска в среднем 5,56 процента, максимум 51,53. Процессов, застрявших в ожидании диска, максимум пять.
Но и объяснить пик мне нечем. В отдельные мгновения готовых к выполнению процессов было до сорока. Но сорок готовых на четырёх ядрах дали бы стопроцентную загрузку, а максимум по хосту 59,7. Значит это доли секунды, и пиковый load ими не объясняется.
Вдобавок iowait и утилизацию диска я снял за неделю, а максимум load average взят за тридцать суток. Отсутствие iowait в одном окне ничего не говорит про максимум в другом. Итог: причину пикового load average я не установил.
Очереди самой Шины не копятся
| Показатель | Значение |
|---|---|
| В очередях сейчас | 56 сообщений |
| Максимум за 30 дней | 422 |
| 95-й перцентиль | 49 |
| Максимум неподтверждённых | 22 |
Двадцать шесть миллионов сообщений в месяц - и максимум очереди 422.
Оговорка, которую обязан применить и к удобной цифре: это максимум по точкам съёма, между замерами могли быть всплески. То же ограничение я предъявлял к load average, и нечестно вспоминать про него только там, где оно снижает страшное число.
Это подтверждает то, что вы видели в июльском инциденте: очередь шины пустая, а канал стоит.
Шина или прямой REST
Все замеры выше про то, как шина живёт. Остался вопрос, зачем она вообще: не дешевле ли десять систем связать напрямую. Честного ответа у меня нет, есть только структура своего трафика.
В схемах есть и HTTP-узлы, и очереди. Через HTTP за месяц прошло 1 907 700 сообщений, и 1 891 016 из них - один обмен с CRM и программой лояльности. Архитектурного паттерна тут нет.
Поэтому вывод «команда осознанно развела HTTP и очереди по типам задач», который просился, я делать не буду: из процентов намерение не выводится, авторов схем я не спрашивал. Историческое наслоение объясняет картину ничуть не хуже. И «один из самых нагруженных обменов» тоже неверно: 1,89 миллиона против 10,0 миллиона у крупнейшего процесса - это в пять раз меньше.
Классификация сделана по именам узлов в метриках. Имена вроде FromHTTP однозначны, но десятки узлов с нейтральными именами по метрике не читаются. Разбивку всех 173 узлов по типам даёт только консоль, и эти десять минут я не потратил.
Что даёт очередь и чего она стоит
| Критерий | Очереди | Прямой REST |
|---|---|---|
| Приёмник недоступен | копятся, доставятся позже | ошибка у отправителя, повтор пишете сами |
| Всплеск нагрузки (у нас 3,6×) | сглаживается очередью | приёмник принимает удар целиком |
| Гарантии доставки | at-least-once, durable, подтверждения | ровно то, что напишете |
| Переотправка после инцидента | есть журнал доставленных, но только через UI | только если храните исходящие сами |
| Связность | отправитель не знает приёмника | жёсткая: адрес, схема, версия |
| Один ко многим | штатно | отправлять N раз самому |
| Задержка | выше: несколько узлов плюс брокер | ниже: один вызов |
| Отладка | сложнее: узлы, очереди, журналы | проще: запрос и ответ |
| Стоимость эксплуатации | высокая, см. всё выше | низкая |
Шина не убирает проблемы интеграции, она меняет их набор. Из моих четырёх сюжетов три специфичны именно для брокерной доставки: откат позиций при рестарте, очередь без потребителя, конкурирующий потребитель на одном участнике.
А вот четвёртый, «доставлено не равно загружено», при REST никуда не девается - там это ответ 200 до фиксации транзакции, и ловится он ровно так же, сверкой по идентификаторам. Я сначала написал, что при REST такого не бывает в принципе. Неправда: не бывает первых трёх, четвёртый остаётся.
Считать «оправдана ли шина» надо через часы сопровождения: сколько человеко-часов за год съели пять дефектов, три инцидента, самописный сервис автолечения, ночной cron и экспортёры, один из которых врал, - я не считал. Поэтому вместо критерия скажу как есть: мы остались на шине, потому что десять систем через прямые вызовы связывать дороже. Для двух систем и сотни запросов в сутки я бы её не ставил.
Про отказоустойчивость: добавлю один факт
Тема разобрана в июльской статье; добавлю то, что определяет всю конструкцию.
Системные хранилища Шины живут на встроенной H2, и вынести их во внешнюю СУБД нельзя. Проверено по конфигурации нашей установки 6.1.6.
Дальше идут выводы, и я помечаю их именно так. Два узла мы не разворачивали. Раз системные базы локальные, отказоустойчивость серверного слоя остаётся только через репликацию тома. Замок, не дающий регламентному заданию выполниться дважды, хранится в локальной H2 каждого узла - значит два активных узла выполнят каждое задание по два раза. Плюс конкурирующие потребители и риск split-brain, когда оба узла решат, что главные они.
Штатного режима, где несколько узлов работают как одна система, у продукта нет: руководство описывает один узел, а представитель вендора на профильной конференции этого года сказал, что реализовать это сложно и конкретных планов нет.
Реалистичный максимум - active-passive с виртуальным адресом и fencing, то есть гарантированным отключением отказавшего узла, внешний отказоустойчивый брокер как буфер, PostgreSQL под кластерным менеджером, идемпотентные приёмники. Горизонтальное масштабирование - функциональным шардингом: разные процессы на отдельные одноузловые экземпляры.
Контраргумент про запас: расти вертикально есть куда по процессору, 22 процента средней загрузки. Только процессор нас и не убивал. Запас надо считать по тому, что упирается - по памяти, inode, скорости восстановления журнала (у нас на большом журнале это девять минут, и идёт оно в один поток) и по H2, которую не разнести. Такого замера у меня нет.
Чеклист
Двадцать пунктов. Те, что разобраны выше, идут с отсылкой; остальные подтверждены практикой, но в тексте не раскрыты.
Развёртывание и рестарты
stop_grace_periodв Docker: дефолт десять секунд для брокера с большим журналом мал, мы поставили 300.- Рестарт Шины не бесплатен для приёмников: откатывает позиции и может заклинить входящие очереди в 1С.
- Рестарт может обнулить хранилище доставленных, а это единственный источник для переотправки. Сначала переотправка, потом рестарт.
- Время старта плавает: штатно две минуты, около четырёх с половиной если стартовый скрипт контейнера (entrypoint) делает рекурсивный
chown, до девяти если брокеру нужно восстановление большого журнала. - Watchdog с коротким терпением убивает Шину посреди загрузки и уходит в петлю перезапусков. У нас это случилось.
development-modeна проде роняет управляющий слой.- Выключение режима разработки передеплоивает приложение и обнуляет счётчики метрик. Провал на графиках в этот момент не инцидент - и, если он попал в ваше окно замера, объёмы за это окно занижены.
Мониторинг
- Зонд обязан трогать управляющий слой. Корневой URL отдаёт 301, когда обмены уже стоят.
- Мониторить inode наравне с гигабайтами.
- Мониторить возраст старейшего сообщения наравне с длиной очереди.
- Пороговые алерты на поток не работают при суточном разбросе в десятки раз.
- Алерт «очередь без потребителя» фолзит на очередях, где потребителя нет by design. Строить на связке «есть остаток И нет потребителя».
- Правка правил алертинга через
sed -iне применяется при bind-mount одного файла. Писать поверх. - Верифицировать собственные метрики. Наша показывала inode вместо счётчика и питала боевой алерт.
Данные и доставка
- «Доставлено» на шине не равно «загружено» в приёмнике. Сверять end-to-end по идентификаторам.
- Идемпотентность приёмника обязательна. Модель доставки at-least-once, без дедупликации переотправка даст дубли документов.
- Окна хранения у шины и приёмника различаются на порядки.
- Переотправка только через UI консоли, публичного метода нет.
- Один участник шины - один потребитель. Второй подключившийся сеанс молча съест часть потока.
- Хранение доставленных включать точечно и временно, с напоминанием на выключение. На нагруженных каналах это 250 тысяч файлов в сутки, которые платформа не удалит.
Открытый вопрос
Мой тезис после года: в связке шина плюс 1С отказ чаще возникает на приёмнике - как застревание, а не нехватка пропускной способности. У меня одна установка, так что это наблюдение и на закономерность не тянет.
По какому раннему признаку вы отличаете «шина не успевает» от «приёмник встал»? К моменту, когда видно обе стороны, ответ уже очевиден.
И второй, для тех, кто мониторит возраст сообщений: вы считаете его от часов сервера мониторинга или от времени, которое отдаёт сама 1С? Мы перешли на время источника, но, возможно, есть решение красивее.
Другие наши инструменты диагностики 1С:
- Карта объёмов базы 1С - из чего состоит база и куда ушли гигабайты. В этой истории пригодилась бы на стороне приёмника: регистры буфера обмена растут молча, а в списке самых тяжёлых таблиц видны сразу.
- Чек-ап СУБД под 1С - 40 с лишним проверок настроек сервера СУБД под платформу. У нас под приложениями Шины живут 24 базы PostgreSQL, и настройки им достались по умолчанию.
- Трансформатор SQL в запрос 1С - когда в плане запроса или в блокировке видно имя таблицы, а надо понять, какой объект конфигурации за ним стоит. Ровно та карта имён, которой не хватает, когда разбираешь затык во входящей очереди чужой базы.
Вступайте в нашу телеграмм-группу Инфостарт