Як мігрувати навантажений проект на микросервисы: досвід маркетплейса
Привіт, я — Дмитро Немеш, СТО в компанії Lalafo, волонтер в GeekHub , понад 7 років працюю з PHP/Java/Node.js. У цій статті розповім, як мобільний С2С маркетплейс переходив на микросервисы і з якими проблемами зіткнувся.
Lalafo — це додаток для купівлі та продажу б/у речей, авто та пошуку нерухомості, роботи і послуг. У ньому застосовується машинне навчання і комп'ютерне зір для розпізнавання товарів на фото, виявлення шахраїв, поліпшення релевантності контенту. Сьогодні Lalafo активний на 4 ринках, на 3 з них сервіс став мобільним маркетплейсом № 1. Сьогодні проект обробляє понад 900 запитів в секунду, і його машини навантажені на 25-30% потужності.
Спочатку проект був побудований на монолітній архітектурі. Після детального аналізу моноліту виявилося, що продукт мав ряд проблем. Це проблеми як з самою якістю коду, так і з можливістю прочитати і зрозуміти чужий код, так як над проектом послідовно працювало дві команди.
Чому микросервисы
Перед нами стояло завдання об'єднати 7 баз даних в одну. Змінити базу даних означало повністю переписати проект, тому що при цьому змінюється структура таблиць, сутностей та сама логіка. Перш ніж прийняти рішення, куди рухатися, ми вирішили проконсультуватися з висококласними розробниками з highload проектів. В результаті прийшли до 3-м варіантами рішень:
- переписати моноліт;
- перейти на сервісно-орієнтовану архітектуру;
- перейти на микросервисно-орієнтовану архітектуру.
Микросервисы імпонували більше за інших. Тому ми поспілкувалися з людьми, які вже впровадили микросервисы, з людьми, яким не вдалося впровадити микросервисы, і оцінили з якими проблемами зіткнемося. В результаті вирішили зупинитися на микросервисах.
Основні виклики
Нам потрібно було об'єднати 7 баз даних з 7-ми ринків в одну глобальну. Це потрібно було зробити з розрахунком, що всі дані будуть спочатку з'єднані в одну глобальну базу даних, а потім рознесені в окремі бази даних для кожного микросервиса. Також нам потрібно було зберегти всі зв'язки між сутностями. Що це означає? Наприклад, потрібно було вирішити всі конфлікти ID-шників (тобто юзер під ID 1 — це 7 різних користувачів в 7 різних країнах). При цьому один і той же користувач міг бути зареєстрованим в 7 різних країнах. Потім потрібно було повторити старий API, щоб усі додатки продовжували працювати. У Lalafo кілька мільйонів клієнтів, і безшовний перехід був критично важливим моментом.
І на додаток, нам потрібно було зберегти ЅЕО трафік. Це означає, що всі проіндексовані посилання повинні були залишитися доступними за тими ж адресами з тими ж ID, що і до міграції.
Які бувають моделі микросервисов
Розрізняють 3 моделі микросервисов: звездоподобная, модель Twitter, сервісно-орієнтована. Для Lalafo ми зупинилися на останній і ось чому.
Звездоподобная — це коли у вас в центрі є залишок моноліту, або супермикросервис, який комунікує певну бізнес-логіку або завдання на микросервисы. Це найпопулярніша модель, коли є залишок або повноцінний моноліт, але певну частину логіки виносять в микросервисы. Нормальна система, але, на жаль, вона нам не підходила. Нам потрібно було все переписувати з нуля.
Модель Twitter — ми її використовували. У нас весь API версійний: версія 1, версія 2 використовує саме цю модель. Є фронт-микросервис, він працює як front controller в MVC фреймворках. Він приймає на себе всі запити, і він же їх веріфіцірует, аналізує і перенаправляє на інші микросервисы. Все добре, чудово, нам подобається, але на фронт-микросервис доводилося дуже багато трафіку. Тому виникло бажання його переписати. У нас він написаний на PHP, оптимально було б перейти на Rust або Go, щоб він працював швидше. Хоча на той момент проблем з продуктивністю не було, перш ніж переписувати його ми подумали, а може є якийсь інший варіант? І спробували сервісну модель.
Сервісно-орієнтована. Сервісна модель полягає в тому, що сам клієнт звертається в ті микросервисы, які йому потрібні. Ця модель добре спрацювала для нас, правда тільки її 3-я версія. З нею все відмінно, крім 2-х недоліків з якими можна жити.
- Складність адміністрування.Всі наші микросервисы працюють у приватній мережі, і до них ніяк не можна достукатися ззовні. Щоб взаємодіяти з микросервисами, потрібно прописати шлях на балансере. На наших 24 микросервисах вже більше сотні цих роутов. Що значно ускладнює життя сисадміну.
- У роботі з микросервисом з'являється елемент сервісу.Якщо микросервис займається безпосередньо однією пов'язаною доменної областю або маленької завданням: оголошення, користувач або ще щось, то микросервис починає сам визначати наявність доступу користувача до певного ресурсу. Для цього микросервис ходить на юзер-микросервис, витягує дані, перевіряє їх, веріфіцірует і після цього дає відповідь. З-за такої логіки микросервисы починають поступово розростатися в повноцінні сервіси і займатися тим, чим по ідеї не повинні.
Як ми мігрували
Коли ми вирішили почати розробку, нам потрібно було зрозуміти, чи можемо ми працювати з микросервисной архітектурою. Для цього ми розробили SDK (Logs, InfluxDB, Services, Helpers, HttpClient), який допомагав синхронізуватися з різними микросервисами і прискорити розробку. Також ми розробили інструменти, які допомагали працювати з микросервисами у вигляді ORM. Все це було зроблено, щоб розробка залишилася максимально схожою на звичну розробку моноліту. В результаті розробникам було нескладно звикнути до неї, так як стиль коду залишався схожим на монолітний.
Ми також адаптували компоненти фреймворку, які ми використовували для access control, users, логування. У результаті розробник використав всі необхідні компоненти як і раніше. Даний підхід допоміг нам за 3 місяці написати повністю робочу MVP версію проекту, який писався більше 2-х років.
Ми об'єднали 7 баз даних за допомогою микросервиса «migrate», також він умів доливати дані. Цей микросервис відсилав всю історію старих і нових ID микросервис «map», за яким будувалася карта старих і нових ідентифікаторів. Це вирішувало проблему зворотної сумісності та збереження SEO трафіку. Також ми зарезервували діапазон ідентифікаторів для старих даних, щоб можна було визначити, де дані з моноліту, а де ті, які вже створені в новій архітектурі. Після цього ми створили ще один микросервис, який розніс дані з великої бази по окремих баз даних микросервисов. В результаті ми отримали дані, де всі записи зберегли всі відносини один з одним, але мали нові ID і карту для отримання даних за старими ID. Діапазон-роздільник ID допоміг нам зрозуміти, які дані потрібно діставати по карті, а які залишити як є.
Процес переходу
Migrate микросервис запускався для конкретної країни (ринку), переливав дані в загальну базу, створював карту зв'язків і потім розносив дані по окремих баз микросервисов. Наступним кроком було перемикання на балансере трафіку з моноліту на микросервисы. У фіналі ми доливали дані, які створилися або змінилися на час закінчення першої заливки і перемикання трафіку.
Stack, який ми обрали:
- PHP 7, Yii2, Codeception;
- Python — data science стек для комп'ютерного зору і машинного навчання;
- NodeJS — comet server для сокетів;
- PostgreSQL — основна база даних,
- MongoDB, Cassandra, Google BigQuery — БД для наших кастомних аналітик;
- Redis — кеш і сесії;
- ElasticSearch — повнотекстовий пошук;
- RabbitMQ, Kafka — черги та message bus для аналітики;
- InfluxDB+Grafana — метрики;
- Graylog2, Zabbix — логування та моніторинг системи;
- GitLab, Kubernetes, Docker, CloudFlare;
В результаті ми прийшли до такої кількості микросервисов:
Core:user, catalog, chat, sender, moderation, payment, security, fraud.
Supplementary:page, location, SEO, translation, Migrate-app, map, mobile-api, cache, analytics, upload, file node.
AI:classify, classify-analytics, duplicates, image processing, content filtering.
Після того, як ти все життя писав моноліти, ти сідаєш за микросервисы і виникають три категорії відчуттів:
- Ідеально! Ти працюєш з деякими з них і думаєш: «Круто, це дійсно працює!».
- З іншими ти думаєш: «щось не так, ці кілька микросервисов можна було об'єднати в один».
- Ти бачиш, що микросервис працює погано. І це не проблема микросервиса.
Ідеальні микросервисы:translations, sender, analytics, security, upload, classify.
Микросервисы, які хочеться об'єднати:user, catalog, location.
Микросервисы, які хочеться переосмислити:fraud, moderation.
Важливість тестування
З микросервисной архітектурою неможливо жити, не використовуючи автотесты. Ми використовуємо гібридні Codeception тести, суміш Acceptance (REST Module) з Functional тестами — це оптимальний варіант. У нас виходять легко читаються Acceptance тести, прості у написанні та підтримки, плюс з фикстурами і моками, як у функціональних тестах. На їх прогін потрібно трохи часу, у зв'язку з чим у нас швидкий continuous integration — у нас маленькі микросервисы, у яких маленькі набори тестів, безпосередньо пов'язані з цим микросервисом. Як тільки якась зміна сталося, continuous integration відразу ж тестит це зміна незалежно від гілки.
Ручне тестування можливо тільки з додатковими інструментами, такими як Postman. В останніх версіях можна створювати сценарії і імпортувати їх, що дозволяє нашим тестувальникам в складних кейсах відсилати не кроки відтворення у вигляді тягаючи в Jira, а сценарій для Postman. Також наша QA команда використовує JMeter для автоматизації своїх тест-кейсів, тестування публічного API і навантажувального тестування.
Висновок:
- автотесты — must have;
- ручне тестування — біль;
- Acceptance + functional tests (REST Module);
- Fast continuous integration.
Микросервисная комунікація
- REST API with http-cache;
- Queue Rebbit MQ/Kafka;
- Global events with Kafka (Message Bus/ Event-drive).
REST API дозволяє легко організовувати, оптимізувати і версионировать комунікацію між микросервисами. Плюс у сисадміна з'являються нові інструменти для порятунку нас і проекту ночами. Якщо щось пішло не так, сисадмін знаходить проблемний шлях і підключає HTTP кеш на цей шлях, проблемний микросервис практично не викликається, але зате відповіді віддає (не завжди актуальні, але це інше питання). Ми в цей час аналізуємо логи, фиксим, і при цьому програма не падає.
Черги Rabbit MQ — за рік у нас не було жодної проблеми з Rabbit MQ. Стабільно і надійно працює, тільки позитивний досвід. Але треба віддати належне — це заслуга того, що у нас не величезні дані в черзі.
Kafka — крута штука. Дуже нагадує черги, але це не зовсім вони, це скоріше система логів з можливістю обміну повідомленнями. Ви підключаєтеся як до звичайної черги і можете відпрацьовувати один і той же івент кілька разів, в залежності від того, які микросервисы підписані на цей топік, так як у кожного микросервиса свій оффсет. Для такої реалізації в RabbitMQ вам потрібно один меседж кидати в кілька черг для кожного микросервиса або перекидати з однієї черги до іншої по мірі виконання. Пропускна здатність набагато вище, ніж у Rabbit MQ. Зараз ми працюємо над тим, щоб всі наші івенти автоматом потрапляли в Kafka.
Активні і пасивні микросервисы
Коли людина веде відразу кілька микросервисов, увага розпорошується. Він може забути або не встигнути зробити тест, щось пропустити. Тому ми прийшли до висновку, що оптимальні співвідношення це:
- 1 розробник — один активний микросервис;
- 1 розробник — 3 пасивних микросервиса, які він знає.
Микросервис — це те ж саме, що і моноліт, тільки в інших масштабах.Якщо є помилки — вони в микромасштабах. Микросервисы розростаються, після чого їх потрібно розбивати на кілька микросервисов. Ви не можете наперед сказати, який микросервис зросте, а який ні.
Стабільність микросервисов
Якщо дотримуватись рекомендацій по створенню микросервисной архітектури, у вас повинно бути багато серверів. Наприклад: є микросервис 1 і микросервис 2. На кожен тип або групу микросервисов повинен бути балансер, а на кожну групу микросервисов повинна бути своя база даних в режимі master-slave. І виходить так, що на кожну групу ми повинні виділити від 5 серверів для того, щоб добитися максимальної відмовостійкості. Якщо у вас 20 микросервисов, теоретично повинно бути більше 100 серверів. Це якщо ми говоримо про своє залозі.
У нас було 3 потужні сервера, на яких знаходилися всі наші микросервисы. У роботі з микросервисами краще мати багато дрібних, ніж кілька потужних серверів. Високонавантажених микросервисы ми виносимо на окремі, більш дрібні сервера. В результаті ми зрозуміли, що це великий перенапружив з обслуговування та моніторингу. На сьогоднішній день ми перейшли на Kubernetes. В процесі переходу і роботи нам довелося попотіти. Результати хороші. Потрібно інвестувати багато ресурсів, але воно того варто. Потрібно відзначити, що сервери з базами даних живуть окремо.
Існує думка, що микросервисы — це мегастабильные системи. Я можу сказати, що стабільність у микросервисов дійсно висока. Коли ми запускалися, проектів були проблеми зі стабільністю. Але якщо ви все правильно робите з самого початку, в результаті у вас буде дуже стабільне додаток. Для цього потрібно дотримуватися підходу, який говорить, що всі микросервисы можуть падати і не відповідати. Для того щоб перевірити, чи правильно написані микросервисы, ви повинні відключати микросервисы і дивитися, як інші працюють без них. Щоб вони вміли нормально працювати і мали сценарії на той випадок, якщо один з них впаде чи не відповість. От коли ви всі ці комбінації спробуєте, вручну або автоматом, тоді ця система буде працювати мегастабильно.
Висновки
Важливі моменти при переході на микросервисы:
- микропроблемы краще, ніж великі проблеми на моноліті;
- контейнеризація і автотесты — must have;
- легке масштабування як проекту, так і команди;
- інтеграція нових технологій;
- плавна міграція з моноліту краще, ніж force;
- потужна та сучасна система моніторингу;
- хороший системний адміністратор або DevOps;
- окрема тема — це контейнеризація, яка буде пояснена в окремій статті. Але якщо коротко сказати, то використання Kubernetes + Docker + GitLab CI виводить управління та моніторинг микросервисов на інший рівень.
Микросервисы — це ідеальна екосистема для експериментів:
- переписувати весь проект не потрібно, завдяки консультаціям ми за рік не переписували жоден микросервис повністю;
- Fast CI — швидкий CI допоміг нам позбутися багатьох проблем;
- новий функціонал з'являється швидше, ніж його можуть протестить;
- зворотна сумісність всіх частин програми: ви завжди можете повернутися до попередньої версії і робити тестування на старій чи новій версії;
- можливість масштабування;
- низька вартість помилки — навіть якщо джуниоры щось зіпсували, будь микросервис можна переписати протягом 1-3 днів.
«Розподілений моноліт»
Нещодавно я був на івенті PHP Friends meetup #4 (Tech Leads Panel), на якому я черговий раз розповідав про наш досвід і микросервисах. Я познайомився з Максимом Волошиним з OWOX (дуже технічний і досвідчений фахівець) і почув від нього фразу «розподілений моноліт» — це дало поживу для роздумів. Так як у нас різноманітні микросервисы — деякі абсолютно самостійні, а деякі щільно зав'язані на інші микросервисы — у мене з'явилося відчуття, що 2 наших микросервиса (User і Catalog) можна справедливо віднести до распределенному моноліту.
Особливо це стало відчуватися після зміни основного функціоналу пару тижнів тому. Хоча ми і навчили ці микросервисы жити один без одного, у випадку падіння одного з них залежність даних дуже висока. Боротися з цим можна і потрібно з допомогою денормалізації даних, але це рішення проблеми, а суть — що це не зовсім микросервисы, і можна їх назвати «розподілений моноліт».
У мене в голові виробилося правило: якщо ти денормализируешь дані з іншого сервісу з даними твого микросервиса, то у тебе не микросервис. Його можна по-різному називати: сервіс, розподілений моноліт, самодостатнє додаток і т. д. Слизька приставка «мікро» дає багато приводів для суперечок з приводу трактування. З-за цього можу з чистою совістю сказати, що за останні 2 роки, спілкуючись з різними хлопцями, які працюють або працювали з микросервисами, я побачив, що реалізації настільки відрізняються один від одного, що здається що під словом «микросервис» намагаються піднести все, що не є моноліт.
У мене є рекомендація — мігрувати поступово і виводити в микросервис те, що не має залежностей від інших микросервисов або моноліту (як функціонально, так і на рівні даних). Тоді у вас з'явиться розуміння і досвід, після чого ви почнете бачити тонку грань між микросервисом і всім іншим.
Опубліковано: 15/12/17 @ 08:00
Розділ Сервіси
Рекомендуємо:
Здати кров усім офісом: ДонорUA пропонує новий формат корпоративного дня
DOU Ревізор у Львові: «Все, що треба для комфорту в офісі Intellias»
Чому Португалія не для всіх. Тестування країни українцем
Ринок праці 2017: зростання 27%, дефіцит ІТ-фахівців, релокация сеньйорів
Information Security дайджест #6: F*ck Responsible Disclosure