Паттерны проектирования и контейнеры, без которых нам никак
Языки программирования — как рецепты крафтового пива: непрерывно усложняются. Сперва программисты задавали железякам инструкции простыми низкоуровневыми командами. Потом появился код, в котором можно было использовать переменные и операторы. А когда переменных и операторов стало слишком много, люди научили языки программирования группировать код в функции. В конце концов функций тоже стало слишком много — и тогда инженеры придумали классы. Затем пакеты, чтобы группировать классы вместе и повторно использовать их в разных проектах.
Благодаря развитию интернета у нас появились огромные библиотеки и готовые к использованию сервисы — их можно скачивать и подключать в свои проекты, получая огромные готовые блоки функциональности.
Контейнер как базовый строительный блок
Направление тренда понятно — каждые несколько лет мы получаем в свои руки все более крупные строительные блоки для конструирования наших программ. Самые крупные на сегодняшний день блоки — это контейнеры, которые умеют принимать наши данные на вход и выдавать результат на выход.
Контейнеры (как и любые другие блоки кода) можно комбинировать между собой, следуя определенным принципам. Многие годы экспериментов привели к тому, что инженеры разработали некоторые крайне удачные приемы совместного использования контейнеров и описали свои идеи в виде шаблонов.
Давайте посмотрим несколько шаблонов проектирования контейнеризованных приложений и попробуем понять, как работает применение паттернов проектирования на таком высоком уровне.
Паттерн «Адаптер»
Паттерн, который пришел к нам из мира объектно-ориентированного программирования. В мире контейнеров этот паттерн работает аналогичным образом — он трансформирует определенным образом внешний интерфейс контейнера в какой-то другой интерфейс, соответствующий духу и стилистике вашей основной системы.
Допустим, у вас есть контейнеризованное приложение, которое умеет принимать картинки по HTTP и классифицировать изображения по разным типам. А основная система не использует HTTP для коммуникаций, потому что вы все решили построить вокруг шины сообщений.
Можно, конечно, расковырять приложение внутри контейнера и научить его работать через шину. Но тогда придется создавать свою версию этого сервиса, обновлять его и следить за проблемами внутри. Чтобы этого не делать, умные инженеры советуют написать адаптер, который проксирует сообщения шины через себя и переводит их в обращения к HTTP.
Вот и все: вы запускаете сервис в двух контейнерах — один реализует функциональность распознавания картинок, а второй — переводит инструкции для распознавалки из формата сообщений шины в формат HTTP. Такой адаптер можно использовать повторно и запускать через него и другие сервисы, которые не могут работать через шину сообщений, — это огромная экономия времени и сил разработчиков.
Паттерн «Шардированный сервис»
Шардирование появляется, когда данных становится очень много. Допустим, вам нужно положить в базу данных 1000 Тб записей. На сегодня ни один из жестких дисков в мире не сможет вместить такой массив информации. Поэтому данные придется разбивать на несколько серверов базы данных (БД) — шардов.
После того как мы раскидали данные по серверам, их нужно еще как-то найти. Для этого нужно иметь некий способ установить нахождение данных в системе по их идентификатору.
Можно решать это на уровне приложения и в каждой программе реализовывать метод, который будет находить нужные серверы БД по идентификатору данных и запрашивать данные оттуда.
А можно реализовать приложение в контейнере, которое умеет принимать запросы на чтение данных из БД и автоматически доставать их из нужного шарда. Внешне такое контейнеризованное приложение выглядит как сервер БД, но на деле оно является прослойкой, которая знает все о состоянии шардов и том, где и как лежат данные в кластере. Вместо реального выполнения запросов в БД прослойка формирует правильный запрос на нужный сервер, достает данные и возвращает их клиенту.
Плюс очевиден — у вас лишь одна точка для управления всеми параметрами шардинга. И в случае изменений в структуре кластера или алгоритмах разбивки данных по шардам вам нужно менять код только в одном месте.
Паттерн «Очередь задач»
Ситуация, когда у вас в систему поступают задачи, а несколько процессов (или серверов) их разгребают — стандартный подход к проектированию архитектуры. Для этого мы упаковываем наше приложение для разгребания очереди в контейнер и учим его забирать данные из шины сообщений.
Оформляем программу в контейнер и запускаем несколько копий в кластере Kubernetes или Docker Swarm. Если задачи копятся в очереди — динамически увеличиваем число контейнеров с программой. Если задач мало и очередь пустая — динамически отключаем часть контейнеров и направляем свободные ресурсы на другие задачи.
Для примера: представьте, что у вас работает сервис потоковой обработки видео. Пик нагрузки в нем приходится на период между 6 и 11 вечера. Для того чтобы всё работало как надо, большую часть времени мы будем держать в кластере 10 контейнеров, а на пиковые часы поставим 100. Изи!
Паттерн «Реплицированный сервис»
Этот паттерн логически связан с паттерном «Очередь задач». Допустим, у вас в системе есть очень важный сервис, отказ которого парализует работу всего вашего бизнеса. Такой сервис стоит держать запущенным в нескольких экземплярах — это называется репликацией.
Контейнеризация же упрощает репликацию. Например, вы запускаете сколько угодно копий сервиса и раздаете запросы всем копиям по очереди с помощью алгоритма Round Robin.
В случае если одна из копий сервиса повисает или выходит из строя, кластер контейнеров перераспределяет задачи между живыми копиями программы и старается перезапустить упавший контейнер.
Конечно, реализовать репликацию можно и без контейнеров, но их применение делает развертывание и управление копиями гораздо более элементарным и дешевым.
Где-то мы все это уже видели…
Все эти паттерны — классические подходы из мира ООП и многопоточного программирования. Мы всего лишь взяли то, что было придумано до этого и адаптировали эти идеи к очень большим строительным блокам, состоящим из целых сервисов.
Смело открывайте классические книги по паттернам проектирования и пробуйте адаптировать традиционные шаблоны к миру контейнеров — тогда у вас на выходе получится очень дешевая и гибкая система.
Да, возможно, для этого вам потребуется изменить свои взгляды на проектирование систем на высоком уровне, но, как говорится, без труда не вытащишь и мышку из компа!