Накопить за 7 лет терабайты данных в монолите и решить, что пора оптимизировать систему и разбивать её на микросервисы — страшный сон для айтишника. Добавим сюда ещё база на решениях от 1С и необходимость обеспечить непрерывность процесса. Стало страшно? А для нас — возможность показать экспертность и решить нетривиальную задачу. О том, как «кролик» помог сдвинуть гору рассказал архитектор 1С компании «Автомакон» Виталий Осиповский.
С 2013 года ВкусВилл проводил внутреннюю автоматизацию с использованием программ на базе 1С. За годы активного развития и эксплуатации база увеличилась до нескольких терабайт, кратно выросло количество операций, что спровоцировало большой объем работы для администраторов, оптимизаторов и команды разработчиков. Например, тяжелые алгоритмы планирования, закрытий, генерации документов могли негативно влиять на работу всей базы.
Любые сбои и простои были неадекватно затратны и влияли на все компоненты системы в целом. А сама система не годилась для быстрого масштабирования бизнеса. Поэтому уже в 2020 году приняли решение о поэтапном разделении монолита на отдельные программные компоненты.
Команда ВкусВилла сформировала следующие ключевые требования к новому функционалу систем:
Работа сотрудников должна быть также комфортна, как и в исходном монолите. Механизмы интеграции должны быть скрыты от рядового пользователя.
Слабая связность, а именно независимое функционирование, т.е. при изменениях, техническом обслуживании или остановке одних компонент другие должны продолжать функционировать в прежнем режиме.
Масштабирование — возможность добавлять новые компоненты путем настройки без существенных доработок.
Процессы внутри монолита сильно связаны по коду и данным, качественно разделить их оказалось сложной задачей. На первом этапе решили запустить компоненты на единой кодовой базе, отключив в каждой из них неиспользуемый функционал, а для синхронизации общих данных использовать обмен.
Приступаем к разбиению монолита
В качестве технологии, обеспечивающей обмен данными между компонентами, был выбран RabbitMQ, а технологии сериализации сообщений — «Конвертация данных 3.0» от 1С.
В используемой программе 1C набор общих данных был довольно масштабен: все данные были доступны для всех процессов, точное разделение и рефакторинг процессов могли серьезно увеличить время реализации проекта, а их отсутствие — серьезно усложнить и перегрузить обмен.
Команда проекта разработала план. Он предполагал решение вопроса самых объемных и интенсивно изменяющихся структур данных. А уже после разделения можно было проводить плановую тонкую оптимизацию и перестройку.
![Рис. 1. Старая и новая схема компонентов Рис. 1. Старая и новая схема компонентов](https://habrastorage.org/getpro/habr/upload_files/49a/f89/a33/49af89a33c8697a17642d253721a8c1b.jpeg)
Рис. 1. Старая и новая схема компонентов.
Для того, чтобы исключить влияние на работу основного функционала, в новой архитектуре отправкой и получением сообщений занимаются отдельные процессы.
На первом этапе генерацией сообщений также занимался отдельный процесс, который запускался по расписанию и формировал сообщения о текущем состоянии всех измененных объектов. Он был реализован на механизме фоновых заданий.
Такой подход привел к следующим результатам: если между запусками генератора объект успевал многократно измениться, формировалось только одно сообщение с последним его состоянием. При этом история изменений объекта в базах получателях оставалась неполной, что зачастую было неудобно пользователям. В итоге генератор сообщений поместили в процесс — источник изменения.
![Рис. 2 Изменение процесса передачи сообщений Рис. 2 Изменение процесса передачи сообщений](https://habrastorage.org/getpro/habr/upload_files/7f1/812/fff/7f1812fffb1dc123b1957bb977b6f16a.jpeg)
How It's Made
Команда придумала, как оптимизировать процессы отправки и получения сообщений с помощью RabbitMQ. Вот какой алгоритм у нас получился в итоге.
Процесс отправки
Этап 1. Формирование сообщений для «Документа» стартует в подписке на событие «ПриЗаписи».
Определение, что «Документ» участвует в обмене. Иначе ничего делать не нужно.
Проверка, что запись выполняется не обменом. Объекты, полученные из обмена, не должны снова регистрироваться в обмен при записи. Для этого процедура приема сообщений добавляет дополнительное свойство записываемому объекту, по которому можно понять, что объект отправлять не нужно.
Проверка, что отправка объекта разрешена. Отсеивание объектов по сложным критериям. Например, не выгружать в статусах «Оформление/Подготовка», но выгружать в статусе «Готов». Это может кратно уменьшить количество версий в обмене.
Получение настроек маршрутизации. Для отправки необходимо знать сервер, точку обмена, виртуальный хост и ключ маршрутизации. Возможны два способа задания ключа маршрутизации — явное указание ключа для объекта, либо программный алгоритм для сложных случаев. Программно решаются, например, кейсы с несколькими базами складов, где решение об отправке документа принимается на основе свойств «Склада» указанного в документе.
Если ключи не определены, алгоритм завершается.
Сериализация объекта по правилам «Конвертации данных».
Запись в регистр «ИсходящиеСообщенияRMQ» сообщения и соответствующих параметров отправки.
Этап 2. Отправка сообщений. Выполняется отдельными процессами в разрезе подключения Rabbit и ключа маршрутизации.
Чтение массива сообщений из регистра «ИсходящиеСообщенияRMQ» для заданного ключа маршрутизации.
Подключение к Rabbit.
В цикле отправка сообщений. Если отправка прошла успешно, удаление сообщения из «ИсходящиеСообщенияRMQ» запись в «ОтправленныеСообщенияRMQ».
Процесс получения
Этап 1. Получение сообщений и сохранение объектов в базу, выполняется фоновыми заданиями в разрезе очередей. 1 очередь = 1 задание.
Подключение к Rabbit и чтение сообщений в цикле.
Десериализация сообщений в объекты по правилам «Конвертации данных».
Запись объекта в базу в режиме «ОбменДанными.Загрузка» = истина.
Запись документа в регистр «ОтложенныеДвиженияДокументов».
Этап 2. Допроведение документа по регистрам, выполняется фоновыми заданиями.
Одно задание отвечает за допроведение нескольких типов документов.
Например, в монолите складские документы пишут проводки во вспомогательные регистры финучета. При разделении эти регистры не нужны для функционирования базы складов, поэтому в складских базах движения не выполняются. Когда документ передается в базу «Финансов», с ним передаются данные складских регистров (не формируются заново), а проводки регистров необходимые для базы «Финансов» формируются.
Решение для двустороннего обмена
Важным вопросом были задачи двустороннего обмена: возникает конфликт, когда один и тот же объект может быть изменен сразу в нескольких базах. Мы решили это следующими способами: явного указания приоритета определенной базы и разделения доступа по реквизитам, т.е. в каждой базе разрешено менять фиксированный набор реквизитов, непересекающийся с наборами реквизитов доступных для редактирования в других базах.
![Рис. 3 Решение проблемы двустороннего обмена Рис. 3 Решение проблемы двустороннего обмена](https://habrastorage.org/getpro/habr/upload_files/850/3b7/d1d/8503b7d1db614237c3bf235288064141.png)
Мы используем обмен с видом Direct: отправитель классифицирует сообщение, присваивая ему при отправке определенный ключ. На основании ключа Rabbit принимает решение о наборе очередей, в которые должно быть доставлено сообщение. Получатель с помощью регламентного задания читает и обрабатывает сообщения: одно регламентированное задание читает одну очередь. Мощность обработки сообщений одним заданием ограничена, поэтому получателю может соответствовать несколько очередей.
![Рис. 4 Обмен с видом Direct Рис. 4 Обмен с видом Direct](https://habrastorage.org/getpro/habr/upload_files/111/b34/a6c/111b34a6c5180e09de0cd0a645dd6604.jpeg)
Маршрутизация Rabbit
Маршрутизация в Rabbit очень полезна не только для самих задач маршрутизации, но и балансировки нагрузки. Мы применяем ее в разных кейсах, если есть сообщения, которые должны быть доставлены нескольким получателям. Например, это относится к данным НСИ, изменение справочника «Номенклатура» в центральной базе должно быть доставлено во все базы, где предполагается работа с номенклатурой.
Изменения многих таблиц для одного получателя можно передавать через одну очередь, но бывают ситуации, когда изменений в таблице много и для нее обмен приходится осуществлять через две и более очереди для каждого получателя. При этом определением ключа маршрутизации можем гарантировать, что определенная запись будет попадать все время в одни и те же очереди, чтобы сохранить очередность версий для каждого объекта. Иногда в связи с бизнес-логикой приходится делить изменения в таблицах на оперативные (например, текущий день) и неоперативные. Оперативные необходимо выделять в отдельную очередь для ускорения их доставки получателям.
В зависимости от сложности передаваемых объектов скорости обработки достигают до 30 сообщений в секунду, некоторые сложные объекты обрабатываются по сообщению в секунду. Как правило, скорость приема меньше скорости отправки в очередь, что может быть потенциальной проблемой в случае, если кто-то решит перепровести документы за 3 года. Сейчас проблема решается на уровне «алармов» и отключением спамера от очереди, но поиск элегантного решения продолжается.
Ошибки и мониторинг
Для поддержки безотказного функционирования системы все части подключены к мониторингам, позволяющим поддержке оперативно реагировать на нештатные ситуации. Например, как только сообщение получило недопустимый ключ маршрутизации или просрочено максимальное время отправки в очередь, поддержка получает уведомление Telegram.
Rabbit подключен к мониторингу Zabbix для постоянного контроля жизнеспособности сервера и уведомляет о своих нештатных ситуациях, например, превышение допустимых лимитов на количество сообщений в очереди, объем используемой памяти.
![Рис. 5 Пример ошибки в Telegram Рис. 5 Пример ошибки в Telegram](https://habrastorage.org/getpro/habr/upload_files/ece/c54/f1c/ecec54f1cf3d6f4689bf70851df70e7b.png)
Бывают ситуации, когда получатель не может «переварить» полученное сообщение. После нескольких попыток он помещает сообщение в отдельную, специально созданную для каждого получателя, очередь ошибок. Сообщения из очереди ошибок периодически обрабатываются отдельным регламентным заданием, при этом мониторинг уведомляет о данном инциденте через Telegram.
Заключение
С помощью подхода использования Rabbit и «Конвертация данных 3.0» удалось создать и поддерживать систему из 5 программных компонент, обменивающихся ~2 млн. сообщений в сутки, при этом процесс разделения продолжается. Rabbit показал себя исключительно надежной системой, обеспечивающей работу без сбоев в режиме 24/7.
*По данным исследования "INFOLine Retail Russia ТOP-100. Итоги 2021 года. Тенденции 2022 года"