В продуктовой и заказной разработке
Микросервисная архитектура. Часть 2: практика
Сибирикс
Микросервисная архитектура. Часть 2: практика
В продуктовой и заказной разработке
30 июля у нас в студии прошел холивар на тему микросервисов, на котором Денис — старший разработчик команды SingularityApp — рассказал о нашем опыте ухода от монолитной архитектуры.
SingularityApp — мощный планировщик дел и задач, разработкой которого в Сибирикс занимается отдельная команда. Десктоп, iOS, Android и даже PWA-версия. Синхронизация между устройствами пользователей. Интеграция с календарями и просто календари, встроенные в приложение. Масса кайфовых фишек, например — таймер Помодоро или распознавание отметок в распечатанных списках дел.

Микросервисы в заказной разработке

Работать с микросервисами мы умеем уже очень давно. Если вы еще не знаете, что это такое — прочитайте первую часть нашего обзора. На самом деле ничего страшного в них нет.

Году так в 2014 мы разрабатывали на Symfony логистический проект — расчет маршрутов доставки. Когда пользователь выбирает, например, доставку из Копенгагена в Токио, и ему предлагается несколько маршрутов по разным ценам.

Сначала маршруты рассчитывались откровенно долго: на поиск одного маршрута уходило двадцать-тридцать секунд, а маршрутов получалось несколько. Чтобы решить проблему, мы использовали одновременно несколько сервисов (демонов) расчета маршрута плюс сервер очередей.
Демон — компьютерная программа в UNIX-подобных системах, запускаемая самой системой и работающая в фоновом режиме без прямого взаимодействия с пользователем.
Пользователь выбирал город. Задача попадала на сервер очередей. Демоны забирали эту задачу и рассчитывали каждый свой маршрут. То, что в один поток рассчитывалось бы 5−10 минут, мы получали за 30 секунд за счет параллельного расчета. Для сегодняшних реалий это очень много, но на момент разработки проекта — это был прорыв.

Микросервисы удобно реализуются при разработке на фреймворках — мы любим Laravel, Symfony — тоже ок. А вот для сайтов на Битрикс — все не так просто. Интернет-магазин нашего постоянного клиента Ormatek работает именно на Битриксе. На начало 2021 года там было почти 200 тысяч товарных предложений.
Товарное предложение (или SKU) — это вариант товара. Например, кровать модели Dario Classic. Чтобы её купить, надо выбрать размер, основание и вариант исполнения (цвет и материал обивки). Товарное предложение — это кровать, для которой уже выбраны все доступные параметры.

Может показаться странным, что у магазина матрасов и мебели такой объемный каталог. Но каждая кровать может быть представлена в десятке размеров. С десятком вариантов оснований. А еще в 40+ вариантах исполнения. Если всё это перемножить — получается 4000+ вариантов на одну кровать. Очень серьезно.

Такой объем каталога создавал две проблемы:

  • Во-первых, импорт товаров на сайт из 1С занимал до 8 часов.
  • Во-вторых, поиск по сайту работал мягко говоря не спеша.
Денис
Cтарший разработчик команды SingularityApp
Везде, где есть «тяжелые» процессы, теоретически их можно вынести в специальное окружение. Для этого и предназначены микросервисы — решать проблему узких мест на проектах.

Простыми словами — вы берете тяжелый и неповоротливый процесс. Убираете его с сайта, и запускаете в отдельном контейнере, где он работает сам по себе. А потом обращаетесь к нему при необходимости.

Микросервис (или сервис, называют по-разному) может быть как большим, так и совсем маленьким — это не принципиально. Да, для каких-то языков программирования это реализуется проще, для других — сложнее.
На Орматеке мы вынесли поиск, фильтрацию, сортировки и импорт в микросервисы.

Для поиска по сайту мы внедрили поисковую систему Elasticsearch. При старой архитектуре (использовался штатный модуль поиска 1С-Битрикс) поиск занимал около 10 секунд, что для обычного пользователя слишком много.

При поиске на такой объемной базе товаров Битрикс генерирует очень тяжелые запросы, которые нельзя поменять. Такие запросы получаются, например, когда Битриксу надо сформировать страницу списка кроватей из 120 тысяч SKU. Или при фильтрации, когда по свойствам надо сформировать и вывести нужные карточки товаров. Помимо свойств товаров, запрос должен учитывать ещё и их изображения. Какую именно картинку для товара выводить, заказчик задает через отдельное свойство-флаг.
Да, сначала мы пытались оптимизировать битриксовый поиск. И даже довели его скорость до 8 секунд. Но уперлись в ограничения Битрикса. Начали думать в сторону кеширования, но в случае с фильтром кеширование бесполезно, так как каждую комбинацию и каждый параметр фильтрации (малейшее движение ползунка) кешировать нецелесообразно.

Итак, мы решили перенести сортировку и фильтры в Elasticsearch. Если очень примитивно и схематично, то вот что у нас происходит теперь:

  • есть MySQL (база данных сайта), в нём по прежнему хранятся SKU,
  • есть Elasticsearch, в него мы тоже поместили все SKU,
  • когда нам нужны определенные SKU с определенными свойствами, мы эти параметры передаем в Elasticsearch и за 0,5 сек. получаем не сами SKU, а список их id-шников. Эти id мы отдаем в MySQL и говорим: «Выведи их на страницу».

В рамках оптимизации мы написали каждому параметру фильтрации/сортировки во всех разделах товаров свой запрос в Elastic. Суммарно, страницы списка товаров, в том числе кроватей, стали загружаться менее, чем за 3 секунды.

Микросервисы для SingularityApp

Денис
Cтарший разработчик команды SingularityApp
Раньше мы использовали монолитную архитектуру. Она нас победила. Пришлось разобрать ее на запчасти.
Итак, SingularityApp состоит из множества различных решений для пользователей. Их становится все больше — новые фичи добавляются несколько раз в месяц (и это не считая постоянных микроулучшений).

Изначально мы реализовали для Singularity монолитную архитектуру. Но в какой-то момент, года так через полтора от начала разработки, это стало серьезным ограничением. Нам потребовалось значительно переработать один из логических блоков программного кода на сервере. Чтобы пользователи со старыми версиями приложения продолжали работать, для них нужно было сохранить старую серверную архитектуру, а для новых версий — использовать новую. Варианта было два: или разворачивать для них второй сервер, либо менять архитектуру.
Вариант со вторым, дублирующим, сервером мы сразу (почти) отбросили — были риски дублирования пользовательских данных. Например, расписание у пользователя могло выдать два раза одну и ту же задачу.

Мы начали переводить SingularityApp на микросервисную архитектуру. Разбили функционал приложения на блоки, которые вынесли в отдельные сервисы. Да, так немного сложнее, так как для каждого сервиса нужен интерфейс, по которому с ним можно взаимодействовать. Но это позволило увеличить скорость разработки, так как теперь каждый сервис может делать отдельная команда и его можно тестировать изолированно. А значит, мы можем разрабатывать фичи параллельно.

Заодно решили проблему «узких мест» — часто выполняемых участков кода, которые своей производительностью ограничивали работу всей системы в целом. Весь функционал, который требует много ресурсов, например поиск или оптическое распознавание (OCR), мы также вынесли в отдельные сервисы, работающие в изолированной среде. Чтобы они не отбирали ресурсы у остальных частей проекта.
лето сочи
Появилась возможность гибко управлять серверными ресурсами, на которых работает проект. Для критичных сервисов всегда можно выделить отдельный сервер. Если функционал не очень важен и не хочется позволять ему сильно много — пожалуйста. Можно дать ему, скажем, один процессор и полмегабайта памяти и сказать: «Крутись, как можешь». При монолитной архитектуре ограничить по ресурсам какую-то функциональность проекта было нельзя — там работает принцип «кто первый встал — того и тапки».

Кроме отдельных физических серверов для выделения ресурсов под отдельные сервисы может использоваться специальное программное обеспечение, так называемые докеры.
Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации, контейнеризатор приложений.
По сути докер — это виртуальный сервер. Можно создать в рамках одного физического сервера (компьютера) много виртуальных серверов и разделить ресурсы между ними так, как надо нам. А потом разместить наши микросервисы в докерах, чтобы они не толкались и не дрались друг с другом за ресурсы.

В качестве бонуса — в каждом докере можно использовать свое программное обеспечение. Например, у нас есть сервер разработки — dev. Там докеры используются, в том числе, для сайтов с разными версиями PHP, чтобы ничего не конфликтовало.

Еще добавилась такая такая штука, как балансировщик нагрузки (мы используем Docker Swarm). Это, по сути, система-оркестратор, которая позволяет управлять всем зоопарком микросервисов и гибко перераспределять ресурсы между ними.

Теперь мы при необходимости можем запускать на сервере несколько экземпляров одного сервиса (если это потребуется для распределения нагрузки), а низконагруженные сервисы оставлять по одному. Даже можем поднимать на нескольких серверах разные сервисы. А балансировщик нагрузки позволяет это делать практически без вмешательства человека.

Переход с монолитной на микросервисную архитектуру

Все было готово. Но у нас уже были живые пользователи в SingularityApp. Огорчать и терять их было нельзя. Надо было перевести их на новую архитектуру так, чтобы они ничего не заметили, кроме того, что все стало работать лучше и быстрее.

Сначала мы оставили одновременно два сервера SingularityApp: новые устройства пользователей автоматически заходили на микросервисный сервер, а старые — на монолитный. Никакой практической разницы для пользователей не было, приложение на обоих серверах работало одинаково.

Постепенно пользователи обновляли свои приложения и автоматически перетекали на новый сервер. В начале 2021 года последний из них незаметно для себя перестал пользоваться монолитом. Сейчас монолитный сервер отключен.
Кстати, использование микросервисной архитектуры позволяет повысить отказоустойчивость. Вы, наверное, слышали о масштабных авариях в дата-центрах в последний год. Когда данные на серверах десятков тысяч их клиентов безвозвратно утрачивались? Естественно, нам захотелось (и мы сделали), чтобы даже если дата-центр сгорит, приложение продолжало работать — просто плавно переключилось на резервную копию.

Сейчас осталась одна проблема: когда мы обновляем какой-то модуль, нам приходится этот сервис выключать и поднимать новый. Возникает небольшой downtime (время, когда сервис недоступен) — обычно 5−10 секунд, максимум — до минуты. В это время пользователи не могут его использовать. Кажется, мелочь — какие-то секунды. По факту в итоге приходит пара сообщений в поддержку с жалобами от пользователей, которые «удачно» попали в те несколько секунд, пока сервис был недоступен.
Хотите деталей? Смотрите видео с холивара: