Walking in my shoes — стоп, а вони марковані?

З 2019 року в Росії діє закон про обов'язкове маркування. Закон поширюється не так на всі групи товарів, і терміни набрання чинності обов'язковим маркуванням для товарних груп різні. Першими під обов'язкове маркування потрапляють тютюн, взуття, ліки, пізніше додадуться інші товари, наприклад, парфуми, текстиль, молоко. Це законодавче нововведення спонукало до розробки нових ІТ-рішень, які дозволять відстежити весь ланцюжок життя товару з моменту виробництва до покупки кінцевим споживачем, усім учасникам процесу: як сама держава, так і всі організації, що реалізують товари з обов'язковим маркуванням.

У Х5 система, яка відстежуватиме товари з маркуванням та обмінюватиметься даними з державою та постачальниками, отримала назву “Маркус”. Розкажемо по порядку, як і хто її розробляв, який у неї стек технологій, і чому нам є чим пишатися.

Walking in my shoes — стоп, а вони марковані?

Справжній HighLoad

"Маркус" вирішує безліч завдань, головне з них - інтеграційна взаємодія між інформаційними системами Х5 і державною інформаційною системою маркованої продукції (ГІС МП) для простеження руху маркованої продукції. Також платформа зберігає всі коди маркування, що надійшли до нас, і всю історію руху цих кодів по об'єктах, допомагає усунути пересорт маркованої продукції. На прикладі тютюнової продукції, яка увійшла до перших груп маркованих товарів, лише одна фура цигарок містить близько 600 000 пачок, кожна з яких має свій унікальний код. І завдання нашої системи відстежити та перевірити легальність переміщень кожної такої пачки між складами та магазинами, і зрештою перевірити допустимість їх реалізації кінцевому покупцю. А касових операцій ми фіксуємо близько 125 000 на годину, і ще треба зафіксувати, як кожна така пачка потрапила до магазину. Таким чином, з урахуванням усіх переміщень між об'єктами, ми очікуємо десятки мільярдів записів на рік.

Команда М

Незважаючи на те, що «Маркус» вважається в рамках Х5 проектом, він реалізується за продуктовим підходом. Команда працює по Scrum. Старт проекту був влітку минулого року, але перші результати прийшли лише у жовтні — було повністю зібрано власну команду, розроблено архітектуру системи та куплено обладнання. Зараз у команді 16 осіб, шість із яких займаються розробкою backend та frontend, троє системним аналізом. Ручним, навантажувальним, автоматизованим тестуванням та супроводом продукту займаються ще шість осіб. Крім цього, у нас є SRE-фахівець.

Код у нашій команді пишуть не лише розробники, практично всі хлопці вміють програмувати та пишуть автотести, навантажувальні скрипти та скрипти автоматизації. Ми приділяємо цьому особливу увагу, тому що навіть підтримка продукту потребує високого рівня автоматизації. Колегам, які раніше не програмували, завжди намагаємось підказати та допомогти, дати в роботу якісь невеликі завдання.

У зв'язку з пандемією коронавірусної інфекції ми перевели всю команду на віддалену роботу, наявність усіх інструментів для управління розробкою, побудований workflow в Jira та GitLab дозволили легко пройти цей етап. Проведені на відстані місяці показали, що продуктивність команди від цього не постраждала, для багатьох підвищився комфорт у роботі, єдине, не вистачає живого спілкування.

Зустріч команди до віддалення

Walking in my shoes — стоп, а вони марковані?

Зустрічі під час віддалення

Walking in my shoes — стоп, а вони марковані?

Технологічний стек рішення

Стандартним репозиторієм та інструментом CI/CD для Х5 є GitLab. Ми використовуємо його для зберігання коду, безперервного тестування, розгортання на тестових та продуктивних серверах. Також ми використовуємо практику code review, коли як мінімум 2 колегами потрібно схвалити внесені розробником зміни до коду. Статичні аналізатори коду SonarQube та JaCoCo допомагають нам тримати код чистим та забезпечити необхідний рівень покриття unit-тестами. Усі зміни у коді обов'язково мають пройти через ці перевірки. Усі тестові сценарії, які проганяються вручну, згодом автоматизуються.

Для успішного виконання бізнес-процесів "Маркусом" нам довелося вирішити низку технологічних завдань, про кожну по порядку.

Завдання 1. Необхідність горизонтальної масштабованості системи

Для вирішення цього завдання ми обрали мікросервісний підхід до архітектури. При цьому було дуже важливо зрозуміти сферу відповідальності сервісів. Ми постаралися поділити їх за бізнес-операціями з урахуванням специфіки процесів. Наприклад, приймання на складі — це не дуже часта, але дуже об'ємна операція, в ході якої треба максимально швидко отримати у держрегулятора інформацію про одиниці товару, кількість яких в одній поставці доходить до 600000, перевірити допустимість прийому цього товару на склад і віддати всю необхідну інформацію в системі автоматизації складу. А ось відвантаження зі складів має набагато більшу інтенсивність, але при цьому оперує невеликими обсягами даних.

Всі послуги ми реалізуємо за принципом stateless і навіть внутрішні операції намагаємося розділяти на кроки, використовуючи, як ми їх називаємо, self-топики Kafka. Це коли мікросервіс відправляє повідомлення сам собі, що дозволяє збалансувати навантаження за більш ресурсомісткими операціями та спрощує супровід продукту, але про це пізніше.

Ми вирішили виділити в окремі послуги модулі взаємодії із зовнішніми системами. Це дозволило вирішити проблему зовнішніх систем, що часто змінюються API, практично без впливу на сервіси з бізнес-функціоналом.

Walking in my shoes — стоп, а вони марковані?

Всі мікросервіси розгортаються в кластері OpenShift, який вирішує проблему масштабування кожного мікросервісу, так і дозволяє нам не використовувати сторонні інструменти Service Discovery.

Завдання 2. Необхідність підтримки високого навантаження та дуже інтенсивного обміну даними між сервісами платформи: лише на фазі запуску проекту виконується близько 600 операцій на секунду. Ми очікуємо на збільшення цього значення до 5000 оп/сек у міру підключення торгових об'єктів до нашої платформи.

Це завдання вирішували розгортанням кластера Kafka та практично повною відмовою від синхронної взаємодії між мікросервісами платформи. Це вимагає дуже уважного аналізу вимог до системи, тому що не всі операції можуть бути асинхронними. При цьому ми не просто передаємо події через брокер, а й передаємо у повідомленні всю необхідну бізнес-інформацію. Таким чином, розмір повідомлення може сягати кількох сотень кілобайт. Обмеження на обсяг повідомлень у Kafka вимагає від нас точного прогнозування розміру повідомлень, і, при необхідності, ми їх ділимо, але поділ це логічне, пов'язане з бізнес-операціями.
Наприклад, товар, що приїхав в автомобілі, ми ділимо по коробах. Для синхронних операцій виділяються окремі мікросервіси та проводиться ретельне тестування навантаження. Використання Kafka поставило перед нами інший виклик – перевірка роботи нашого сервісу з урахуванням інтеграції Kafka робить усі наші unit тести асинхронними. Це завдання ми вирішували написанням власних брухту з використанням Embedded Kafka Broker. Це не скасовує необхідності написання unit-тестів на окремі методи, але складні кейси ми вважаємо за краще тестувати з використанням Kafka.

Дуже багато уваги приділили трасуванні логів, щоб їх TraceId не губилися при виникненні винятків під час роботи сервісів або при роботі з Kafka batch. І якщо з першим особливих питань не виникло, то в другому випадку ми змушені записати в балку всі TraceId, з якими прийшов batch, і вибрати один для продовження трасування. Тоді при пошуку по початковому TraceId користувач легко виявить з яким продовжилося трасування.

Завдання 3. Необхідність зберігання великої кількості даних: більше 1 мільярда маркувань на рік тільки по тютюну надходить у X5. До них потрібен постійний та швидкий доступ. Загалом система має обробляти близько 10 мільярдів записів з історії руху даних маркованих товарів.

Для вирішення третього завдання було обрано NoSQL базу MongoDB. У нас побудований шард із 5 нод і в кожній ноді Replica Set із 3 серверів. Це дозволяє масштабувати систему горизонтально, додаючи нові сервери в кластер, та забезпечити її стійкість до відмови. Тут ми зіткнулися з іншою проблемою — забезпечення транзакційності в кластері mongo з урахуванням використання мікросервісів, що горизонтально масштабуються. Наприклад, одне із завдань нашої системи — виявляти спроби повторного продажу товарів з однаковими кодами маркування. Тут з'являються накладки з хибними скануваннями або з хибними операціями касирів. Ми виявили, що такі дублі можуть виникати як усередині одного batch Kafka, що обробляється, так і всередині двох паралельно оброблюваних batch. Таким чином, перевірка на появу дублів шляхом запиту до бази нічого не давала. Для кожного з мікросервісів ми вирішували проблему окремо, виходячи з бізнес-логіки цього сервісу. Наприклад, для чеків додали перевірку всередині batch та окрему обробку на появу дублікатів при вставці.

Щоб робота користувачів з історією операцій ніяк не впливала на найголовніше — функціонування наших бізнес-процесів, всі історичні дані ми виділили в окремий сервіс з окремою базою даних, який також отримує інформацію через Kafka. Таким чином, користувачі працюють із ізольованим сервісом, не впливаючи на сервіси, що обробляють дані по поточних операціях.

Завдання 4. Повторна обробка черг та моніторинг:

У розподілених системах неминуче виникають проблеми та помилки доступності баз даних, черг, зовнішніх джерел даних. У разі "Маркуса" джерелом таких помилок є інтеграція із зовнішніми системами. Необхідно було знайти рішення, що дозволяє робити повторні запити з помилкових відповідей з якимось заданим тайм-аут, але при цьому не припиняти обробку успішних запитів в основній черзі. Для цього було обрано так звану концепцію “topic based retry”. Для кожного основного топіка створюється один або кілька повторюваних топіків, в які направляються помилкові повідомлення і при цьому виключається затримка на обробку повідомлень з основного топіка. Схема взаємодії

Walking in my shoes — стоп, а вони марковані?

Для реалізації такої схеми нам потрібно було таке — інтегрувати це рішення зі Spring і уникнути дублювання коду. На просторах мережі ми натрапили на подібне рішення, засноване на Spring BeanPostProccessor, але воно здалося нам надто громіздким. Нашою командою було зроблено простіше рішення, що дозволяє вбудуватися в цикл Spring зі створення consumer і додатково додавати Retry Consumer-и. Прототип нашого рішення ми запропонували команді Spring, переглянути його можна тут. Кількість Retry Consumer-ів і кількість спроб кожного consumer-a налаштовується через параметри, залежно від потреб бізнес-процесу, і щоб все запрацювало, залишається лише поставити знайому всім Spring-розробникам анотацію org.springframework.kafka.annotation.KafkaListener.

У випадку, якщо повідомлення не могло бути оброблено після всіх повторних спроб, воно потрапляє в DLT (dead letter topic) за допомогою Spring DeadLetterPublishingRecoverer. На прохання підтримки, ми розширили даний функціонал і зробили окремий сервіс, який дозволяє переглядати повідомлення, що потрапили в DLT, stackTrace, traceId та іншу корисну інформацію щодо них. Крім того, були додані моніторинги та алерти на всі DLT топіки, і зараз, по суті, поява повідомлення в DLT топіці є приводом для розбору та дефекту. Це дуже зручно — за назвою топіка ми одразу розуміємо, на якому етапі процесу виникла проблема, що значно прискорює пошук її кореневої причини.

Walking in my shoes — стоп, а вони марковані?

Зовсім недавно ми реалізували інтерфейс, що дозволяє повторно надіслати повідомлення силами нашої підтримки після усунення їх причин (наприклад, відновлення працездатності зовнішньої системи) і, звичайно, закладу відповідного дефекту для аналізу. Тут і стали в нагоді наші self-топики, щоб не перезапускати довгий ланцюжок обробки, можна перезапустити його з потрібного кроку.

Walking in my shoes — стоп, а вони марковані?

Експлуатація платформи

Платформа вже знаходиться в продуктивній експлуатації, щодня ми проводимо постачання та відвантаження, підключаємо нові розподільчі центри та магазини. В рамках пілота система працює з групами товарів "Тютюн" та "Взуття".

Вся наша команда бере участь у проведенні пілотів, аналізує проблеми, що виникають, і вносить пропозиції щодо вдосконалення нашого продукту від поліпшення логів до зміни в процесах.

Щоб не повторювати своїх помилок, всі кейси, знайдені в ході пілота, знаходять своє відображення в автоматизованих тестах. Наявність великої кількості автотестів та unit-тестів дозволяють проводити регресійне тестування та ставити хотфікс буквально протягом кількох годин.

Зараз ми продовжуємо розвивати і вдосконалювати нашу платформу і постійно стикаємося з новими викликами. Якщо вам буде цікаво, ми розповімо про наші рішення в наступних статтях.

Джерело: habr.com

Додати коментар або відгук