Реактивные приложения на Java с Akka

Я работаю программистом уже более 13 лет: занимаюсь высоконагруженными и распределенными системами, рассматриваю и оцениваю разные подходы и решения.

В данный момент я выделяю два типа систем, которые определяют итоговый стек технологий, с которым следует иметь дело:
— системы, которые поставляют данные конечным пользователям;
— системы, предназначенные для вычислений.

Третьим типом может быть система, которая выполняет обе функции. Однако такие системы обычно строятся из подсистем, которые относятся к первым двум типам.

Я хочу поделиться с вами информацией об Akka и модели актеров. Если вы уже используете Akka в своих проектах, я верю, вы уже знаете все то, о чем я буду говорить здесь. Если же вы слышали об Akka и не уверены, подходит ли вам этот инструмент, я поделюсь с вами достаточной информацией для того, чтобы вы сумели определиться.

Многопоточность и масштабирование

С развитием технологий развивается и модель программирования. Ее эволюция началась с больших монолитных задач, которые на самом деле являлись набором инструкций для последовательного выполнения. Эволюционируя, эти задачи становились все больше и больше. Модульный подход позволил разделить большие задачи на мелкие и выполнять их последовательно, переключая контекст с одной задачи на другую. По дальнейшей эволюции стало ясно, что некоторые задачи могут выполняться независимо от других. Это стало основой для многопоточного программирования:

Однако даже с многопоточными программами мы на самом деле имеем на руках большое монолитное приложение, которому нужно все больше и больше ресурсов для дальнейшей эволюции. Для обеспечения достаточного количества доступных ресурсов мы дошли до больших серверных станций. При условии доступности ресурсов многопоточное программирование разрешило выполнять задачи параллельно.

Возможность выполнять задачи параллельно бросила вызов, связанный с масштабированием приложений. Масштабирование бывает вертикальное или горизонтальное.

При вертикальном масштабировании вы развиваете сервер, добавляя больше вычислительной мощности. С каждым обновлением следующая опция обходится все дороже и дороже. Также есть большой риск отказа системы, так как всю работу делает один большой сервер. Вы можете иметь запасной сервер, зеркальный и т.п. Однако это не меняет модель развития; вы все еще в плоскости вертикального масштабирования. Этот вызов перенес нас в плоскость горизонтального масштабирования.

При горизонтальном масштабировании вы добавляете еще один сервер вместо обновления уже существующего. Это позволяет покупать недорогое оборудование в большем количестве — в сумме получается дешевле, чем покупка и обновление больших серверов. Вдобавок вы получаете отказоустойчивый кластер. Даже если один сервер из десяти откажет, будет доступно достаточно ресурсов для полноценной работы системы. Обновление такого кластера означает добавление еще одного недорогого сервера (узла).

Резюмировать обе модели масштабирования можно так:

Вертикальное масштабированиеГоризонтальное масштабирование
Опции дороже с каждым обновлениемПо сравнению с вертикальным масштабированием, опции обходятся дешевле
Большой риск отказа системы при отказе оборудованияОтказ нескольких узлов не приводит к отказу всей системы (если система спроектирована правильно)
Ограниченные опции обновлений в будущемОбновлять систему легко
Один узел для всегоПараллельные узлы разрешают параллельные вычисления

Давайте честно, даже масштабируя ресурсы горизонтально, будете ли вы все еще выполнять то же самое приложение на более слабых серверах за лоад-балансером? Думаю, нет. Мы все еще можем иметь два или три больших сервера за лоад-балансером. Зачем нам зоопарк маленьких серверов? Эти вопросы правильно поставлены, если вы все еще разрабатываете приложения объектно-ориентированно.

Реактивные приложения

Если честно, услышав впервые термин «реактивные приложения», я был удивлен. Я мог представить реактивные двигатели, самолеты и даже машины, но не программы. Почитав немного, я понял, что имеется в виду: речь идет о распределенных системах, которые являются большими по своей природе. Большие системы нужно поддерживать некоторыми договоренностями и правилами, чтобы они оставались актуальными и расширяемыми спустя годы.

Реактивные приложения поддерживаются Реактивным манифестом. Реактивный манифест — это свод правил и договоренностей, которые могут удержать систему на плаву, если имплементированы правильно. На самом деле в нем нет ничего нового, многие из нас уже знакомы с этими подходами. Увы, иногда правила нарушаются желанием быстро имплементировать какую-то «фичу» или какими-то другими требованиями. Я верю в то, что Реактивный манифест был собран не для того, чтобы нас научить, а для того, чтобы напомнить «почему?», «как?» и «что?» мы делаем. Прочитать полный текст Реактивного манифеста можно на официальном сайте. В момент написания статьи актуальной была версия 2.0. Также доступен перевод на русский (правда, старой версии).

Основная идея реактивности заключается в том, что системы старого типа в основном развертывались на компьютерах, обрабатывали гигабайты информации, и был допустим отклик системы в несколько секунд. Современные системы развертываются на чем угодно — от мобильных устройств до облачных кластеров — и обрабатывают терабайты информации, а допустимое время отклика опустилось до десятков миллисекунд.

Чтобы называться реактивными, приложения должны быть отзывчивыми, упругими, эластичными и общаться асинхронным обменом сообщений:

— Отзывчивость — это быстрая реакция на заданный запрос. То есть приложение должно всегда и всегда обрабатывать и отвечать на запросы быстро, даже при больших нагрузках. Это также значит, что если где-то произошел сбой, система должна быстро обработать ситуацию и дать соответствующий ответ.

— Упругость — это означает, что приложение должно оставаться отзывчивым даже при возникновении отказа в некоторой части системы. Для этого отказ одного компонента обрабатывается другим, который в свою очередь тоже может делегировать обработку своего отказа другому компоненту. Таким образом, отказ одного компонента в системе не приводит к отказу всей системы.

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

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

В итоге большие системы строятся из меньших систем, поддерживая их реактивные свойства в себе. Такие приложения и называются реактивными.

Модель актеров

Если присмотреться, все вышеперечисленные свойства уже соблюдаются в Java EE. Хотя, иногда мы тратим очень много времени для достижения этих свойств и еще больше для поддержки итогового приложения. Конечно, если приложение уровня Enterprise будет развернуто на паре Enterprise серверов приложений (EAS) в кластере за лоад-балансером, я однозначно выберу Java EE и воспользуюсь поставляемым в коробке функционалом. Если же у меня будет десяток серверов, я скорее подумаю, сколько ресурсов освободится, если избавится от Enterprise серверов приложений. Только подумайте, сколько памяти, процессорного времени и денег можно сэкономить.

Есть модели программирования, которые извлекают пользу из кластера серверов, превращая их в один суперкомпьютер. Одна из таких моделей программирования — модель актеров, о которой мы и будем говорить.

Актер — это вычислительная единица. В ответ на сообщение, которое он получил, актер может:
— отправить определенное количество сообщений другим актерам;
— создать определенное количество новых актеров;
— определить поведение для обработки следующего сообщения, которое он получит.

Нет определенной последовательности вышеперечисленных действий, они могут быть выполнены параллельно. Философия модели актеров гласит, что «все вокруг актеры». Это похоже на философию «все вокруг объекты». При этом объектно-ориентированные программы обычно выполняются секвентально, когда актеры работают параллельно. Конечно, вы можете использовать потоки, чтобы добавить конкурентность в объектно-ориентированные программы. Обратите внимание, что в 64-битной системе поток Java занимает 1 мегабайт памяти, когда актер занимает до 300 байтов. Потоки будут ограничены в количестве, и придется пользоваться спецификой объектно-ориентированного подхода. Используя модель актеров, вы думаете об актерах вместо объектов и потоков. Можно иметь намного больше актеров, чем потоков.

Актеры инкапсулируют состояние и поведение и общаются исключительно посредством обмена сообщениями. Сообщения, полученные актером, ложатся в его почтовый ящик. Актеров лучше рассматривать как людей. Моделируя решение с моделью актеров, представьте группу людей, которым вы назначаете подзадачи. Подзадачи — это результат деления одной большой задачи на мелкие куски, которые делегируются актерам для выполнения. Вы можете назначить эти задачи актерам, организуя их в структуру, словно членов команды в экономической организации. Также подумайте о том, как эскалировать отказы на каждом уровне вашей организационной структуры.

Каждый актер может создавать дочерних актеров и быть их супервизором, словно менеджер команды. В практике лучше будет разделить задачу актера на мелкие управляемые подзадачи и разрешить подчиненным выполнить их в параллели. Если актер имеет важное состояние, которое нельзя потерять (например, если получим Exception), тогда всю задачу целиком лучше отдать на выполнение подчиненным и просто следить за процессом выполнения. Поэтому актеры никогда не приходят одни. Они приходят в системах и образуют иерархии (как файлы в UNIX). Системы актеров также могут формировать кластеры. Если представить актера как звезду, тогда система актеров — это кластер звезд. Кластеры систем актеров — это кластеры кластеров звезд. Таким образом, актеры создают свою вселенную. Это вселенная после будет развернута на предоставленном оборудовании и будет выполнять запрограммированную логику.

В мире актеров следуют принципу «Let It Crash» («позволь ему отказать») и так называемому шаблону Error Kernel Pattern (шаблон ядра ошибок или, точнее, обработки ошибок). Этот шаблон ведет нас к системе актеров, где супервизор каждого уровня иерархии в ответе за локальные ошибки. В данном контексте, ошибки — это exceptions, возникшие у подчиненных актеров, то есть сами актеры не должны их обрабатывать. В свою очередь, супервизор при возникновении ошибки может отловить ее и, как правило, продолжить, перезагрузить, остановить работу актера или же эскалировать exception своему супервизору, сообщая о падении всей своей иерархии подчинения. Ядро ошибок самой системы актеров формируется из системных актеров, которые отвечают за жизненный цикл всей системы. При этом каждый уровень иерархии в организационной структуре является локальным ядром ошибок, отвечающим за стратегию обработки ошибок, которая определит, как нужно реагировать на определенные ошибки дочерних актеров.

Работа с Akka

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

Akka — это самостоятельный инструмент. Ему не нужен сервер приложений, достаточно JVM и Java SE. С помощью Akka можно объединить несколько JVM в кластер. Akka предлагает модель актеров вместо объектно-ориентированной, которая считается распараллеленной по умолчанию.

С Akka мы получаем:
— параллелизм вместо конкурентности;
— асинхронное поведение по умолчанию;
— неблокируемые вызовы;
— выполнение без Deadlock, Starvation, Live-lock и Race Condition;
— разработка в однопоточной среде, когда задачи выполняются в параллели.

Разработка с моделью актеров в Akka не сложная. На момент написания статьи актуальной версией Akka была 2.4. Для начала подключите все необходимые зависимости в проект. Всю необходимую информацию можно получить на сайте akka.io. Также есть исчерпывающая документация для Java разработчиков на akka.io/docs.

Для быстрого старта нужно знать о нескольких классах из пакета akka.actor. Это:
— ActorSystem — класс имплементирующий систему актеров;
— UntypedActor — класс, который нужно унаследовать для создания класса актера и переопределить метод onReceive для обработки входящих сообщений данным актером;
— ActorRef — класс, инкапсулирующий ссылку на актер. Он же используется для отправки сообщений актеру.

Используя эти классы:
— вызовите ActorSystem.create()для создания системы актеров и получения объекта ActorSystem;
— вызовите ActorSystem::actorOf() для создания экземпляра актера и получения его ActorRef;
— используйте ActorRef::tell() для отправки сообщений актеру.

Это все! На самом деле, есть достаточно много нюансов, но, для первых шагов этого достаточно.

Остановимся на минуточку. Что, если мы разрабатываем локальное приложение? Помните ExecutorService или даже Multithreading API? Представьте систему, использующую все это. Насколько оно эффективно? Действительно, оно может эффективно использовать доступные ресурсы. Скажем, мы выучили Akka API и начали программировать актеров. Хорошо, что дальше? Мы получили те же результаты, как если бы использовать стандартные механизмы многопоточности в Java. Так ли оно? Да и нет. Да, потому что результаты могут быть похожи, и нет, потому что с Akka мы получаем похожие результаты, программируя в однопоточной среде, обходя все сложности разработки многопоточных приложений. Вы только описываете актеров и логику обработки сообщений, которые они получают. Кстати, сообщением может быть любой объект. Единственное правило, которое нужно помнить: сообщения должны быть immutable объектами. Потому что с Akka мы пишем код в однопоточной среде, когда система актеров выполняется в многопоточной среде.

Самая интересная часть работы с Akka наступит после того, как вы разработаете первое приложение. Система актеров может быть развернута как самостоятельное приложение. Актеры также могут обмениваться сообщениями по сети. Это позволяет организовать совместную работу отдельных систем актеров. И наконец, вы можете развернуть кластер из нескольких машин. Можно развернуть единую систему актеров на кластере машин, связанных в одну сеть. Таким образом, вы получаете настоящее параллельное выполнение актеров.

Есть много опций для лоад-балансинга и масштабирования вашего приложения. Вы можете заменить имплементации почтовых ящиков и определить свои правила приоритезации сообщений в очереди. Можно использовать маршрутизаторы, которые организуют пулы и группы актеров вместо одного актера, который обрабатывает сообщения. Можно заменить диспетчеры, которые определяют, как доступные потоки используются актерами и даже больше.

Дополнительно ко всему перечисленному, Akka предоставляет Functional Futures, имплементируя Java обертку над фъючерами из Scala. С помощью этого API вы даже можете реализовать симуляцию map-reduce. Доступно еще больше с таким функционалом и модулями, как Agents, Streaming API, HTTP стэк, Camel и другие.

Резюме

С Akka вы можете получить полноценный инструментарий, который позволяет разрабатывать высокопроизводительные отказоустойчивые распределенные приложения, которые разрешают рассматривать кластеры машин как единую систему.

Конечно, доступно много других фреймворков и инструментов, позволяющих разработчикам достичь похожих результатов. На мой взгляд, итоговое решение зависит от ответа на один основной вопрос: что будет делать ваше приложение? Я выбрал бы Akka как минимум для параллельных вычислений с небольшими затратами ресурсов. Akka умеет намного больше, и для меня это тот минимум, который определяет, где Akka может пригодиться.

Буду рад, если это статья поможет определить, интересна ли вам Akka для дальнейшего изучения. Желаю удачи!

Похожие статьи:
Всем привет! Каждый, кто стремится к совершенству, формирует для себя ряд правил, которые помогают ему двигаться в выбранном...
В прошлом году мы составляли список...
Ви просили нас кликати гостей, і ми почули. Представляємо спецвипуск подкасту з Дімою Малєєвим. Діма вже давненько...
Savvy IT School приглашает на курсы для начинающих программистов по специальности Java Developer. Для кого эта программа? Для...
Голова «фінкомітету» Верховної Ради Данило Гетманцев відповів на відкритий лист-звернення представників малого...
Яндекс.Метрика