Akka. О чем все эти Акторы и зачем вам Akka?

Maxim Sidorov
May 13, 2018 · 6 min read
Акка — гора в Швеции (источник)

Что такое Akka и почему именно Akka? Akka — это фреймворк для двух языков, работающих под JVM, Java и Scala. Это фреймворк (вот его maven полочка https://bit.ly/2KfRIzx), предоставляющий готовую среду для построения высоконагруженных систем, которые эффективно используют ресурсы машины, и даже намного больше:

А так же высочайшая точка в Швеции (https://bit.ly/2rD92HK).

Вышел он из рантайма языка Scala. В ’06 у разработчиков языка возникла идея вывести его в отдельное решение, в рамках рантайма он предоставлял средства разработки многопоточных приложений с использованием легковесных потоков. Собственно легковесные потоки (так же их называют грин-треды, потоки пользовательского уровня) сами по себе и есть средство решения проблемы эффективной утилизации ресурсов. И вот в ’10 выходит первая версия (а именно 0.5) фреймворка и сейчас они добрались уже до 2.5.12 (истории и сжатой информации можно похватать тут https://bit.ly/2grzNvH).

Так почему именно Akka? Akka только потому, что вы очень хотите написать (переписать, если вы хотите координально улучшить) высоконагруженный проект и ограничены решением на JVM и языками Java и Scala.

Согласно сериалу “Видоизменненный углерод” (https://bit.ly/2DoP1rr) убийство может совершить только тот, у кого были мотивы, навыки и средства. И JVM и один из языков — это ваши навыки и средства, а мотивами может стать ваша проблема, которую сейчас очерчу.

Проблема массового обслуживания

У вас есть решение, которое предоставляет доступ к некоторому бизнес-функционалу, скажем 1e4 (или 10'000 пользователей), которые в активном режиме подключены к вашем серверу с некоторым бизнес-функционалом и шлют в него запросы. Если решать в классической парадигме архитектуры веб-серверов (веб-сервер — не обязательно готовое решение как Apache HTTP Server или NGINX, а ваше собственное приложение, имеющее доступ, в классике, по протоколу HTTP), то выглядеть это будет следующим образом: запрос пользователя вызовет в потоке, разбирающем новые подключения, процесс порождения нового потока с контекстом этого подключений (средства многопоточности в этом подходе нам предоставляет операционная система, а это потоки уровня ядра).

Классическая архитектура веб-сервера (оригинальная статья)

Стоимость порождения такого потока (память + процессорное время) удовольствие дорогое, плюс все это добро диспетчизировать (переключать конкесты, вымещать из процессора, так как выделенный квант для этого потока истек). Так и появился первый порог в этой проблеме C10K — 10'000 одновременно обслуживаемых подключений (подробнее читаем тут, картинка и последующее решение от Netflix из этой статьи). Уже эволюционированной задачей будет C10M (как решили это на Java). Это и есть ваш мотив в идеальном убийстве. Здравый смысл сам подсказывает: переиспользуй — стратегия ресурсного пулинга (pooling). И теперь у нас есть абстрактный поток уже пользовательского уровня приложения. Но собирать самому — для энтузиастов и исследователей, прагматикам можно воспользоваться тем, что они сделали прагматически настроенные энтузиасты и исследователи.

Асинхронная событийная архитектура веб-сервера (оригинальная статья)

Akka — готовый инструмент для решения задач массового обслуживания

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

И так, Akka — фреймворк, предоставляющий средства разработки высоконагруженных систем (одновременно обслуживающих более 10К пользователь, для кого-то и это не нагрузки). Делает он это, используя модель Акторов, впечатленный языком Erlang.

Модель сама по себе очень простая.

Основная философия — все есть Актор.

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

  1. Отправить N сообщений другим Акторам (помимо себя)
  2. Породить M новых Акторов
  3. Изменить внутреннее состояние (что отразится на последующие реакции на входящие сообщения)

Очень похоже на ООП, неправдали. Но!! ООП сейчас — это идея потерянная за реализацией, вот поэтому и поэтому.

Теперь как все это выглядит в Akka:

  • Actor — класс описывающий логику работы с сообщениями, унаследованный от AbstractActor (UntypedActor, AbstractPersistentActor); по-сути, сущность, инкапсулирующая логически завершенный функционал, атомарно выполняющийся в однопоточном режиме (многопоточность достигается за счет порождения нескольких одинаковых экзепляров вашего актора). Если кто помнит Smalltalk, то вызывает до боли известные мотивы.
  • Messages —экземпляр класса, отправлемый одним Актором асинхронно, и так же асинхронно получаемый другим
  • Dispatcher — диспетчер, отвечающий за помещение акторов на исполнение на пуле потоков, когда приходит новое сообщение для этого актора, местный кукловод.
  • Mailboxes — очередь сообщений, хранящая переданные конкретному актору послания, в отдельном случае почтовый ящик делится группой акторов

Основные принципы, которые следует помнить:

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

Сначала мы описываем всех участников нашего Мерлезонского балета:

  • Акторов и сообщения, которые они понимают
  • Наполнение акторов: то как они должны реагировать на известные сообщения (а так же на неизвестные)
  • Процесс бутстрапинга всей нашей системы (в рамках одной программы можно создать несколько систем)

И… все начинается с первого сообщения, которое попадает передается некоторому актору (через ActorRef и то как его можно получить), а далее:

  1. О новом сообщении оповещается Dispatcher, который управляет данным актором
  2. Dispatcher ставит обработчик актора входящих сообщений (Receive) на поток из управляемого им пула
  3. До тех пор, пока актор выполняет обработку текущего сообщения, новые сообщения копятся в его Mailbox’е
  4. И… так далее, в соответствии с вышеописанной моделью

Кому интересны рассказы? Нужен код и эксперименты!

Для этого возьмем их quick start и прокачем его, чтобы прощупать основные концепции. Результат можно забрать от сюда.

Основные вещи, которые можно там посмотреть:

  • Создание акторов через сообщение, передача начальной конфигурации через сообщение
  • Обращение к актору через объект ActorRef напряму и через локальный путь
  • Роутинг с двумя стратегиями: RoundRobin (каждому по-очереди) и Broadcasting (широковещательно)
  • Работа с очередью RabbitMQ в docker-образе

Суть системы:

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

Логическая архитектура:

SuperMessageSystem main нашего приложения печати.

MessagesPublisher — main дикого клиента, публикующего на RabbitMQ очередь 5 миллионов заданий на печать, потом команду изменения шаблона, потом снова 5 миллионов сообщений

Наши Акторы:

  • FetcherActor организует создание системы GreeterActor’ов и PrinterActor’ов, подключается к RabbitMQ, который можно поднять через docker-compose (файлик для композера в корне проекта, как это сделать)
  • GreeterActor подготавливает данные для принтера в соотвествии с тем шаблоном, на который он настроен (новый можно передать с помощью сообщения)
  • PrinterActor “печатает” то, что подготовил Greeter, и скучает если в течении 10 секунд не было никаких сообщений

Эксперимент:

На моем ноуте (i7 2.6GHz 8 ядер, 16Gb) забьем RabbitMQ очередь 2 миллионами сообщениями и посмотрим по логам за сколько наш супер принтер сможет разобрать все.

Посреди процесса будет отправлено так же сообщение о реконфигурации шаблона печати.

Концептуально это 2 миллиона клиентов, единовременно захотевших распечатать свои данные.

Давайте сначала посмотрим сколько у нас занимает чистая выемка сообщений, так как для нас это будет предельный случай работы системы.

Время чистой выгрузки: 2 минуты 40 секунд или ~17000 сообщений в секунду.

Давайте выставим по 20000 акторов, занимающихся шаблонизацией и 20000 занимающихся печатью (приблизительно получится там самая проблема C10К)

Таким образом время “печати” 2 минуты 57 секунд, с конкурентностью ~40К исполнителей. Причем следует заметить, что создание 40000 Акторов заняло около секунды.

Итог

  1. Akka — фреймворк, дающий из коробки возможность собирать приложения, адаптированные под высокие нагрузки, в парадигме модели Акторов.
  2. Познакомились с проблемой C10K. Если вы писали такие системы, то можете считать себя специалистом по highload системам.
  3. Легковесные потоки (в Akka — они же Акторы) позволяют решать проблему несовместимости тяжелых потоков уровня операционной системы и высоконагруженных систем.
  4. Посмотрели самые базовые принципы и элементы, которые дает фреймворк Akka и провели эксперимент, показывающий как в одном приложении живут 40К конкурентных нитей исполнения кода

В следующих частях

  1. Reactive Manifesto. Глубокое погружение в Akka. Основные реактивные шаблоны проектирования. Akka Streams, Akka HTTP.
  2. Построение высокопроизводительного кластера средствами Akka.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade