image

По akka мало материалов на Хабре. Решил перевести некоторые антипаттерны, описанные Мануэлем в своем блоге. Они действительно могут быть неочевидны людям впервые столкнувшимся с фреймворком.

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

Есть два пути получить слишком много акторов:

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

Давайте посмотрим на эти варианты в деталях.

Слишком много типов акторов


Общая идея примерно такая: «у нас есть акторы, поэтому все должно быть актором».

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

Но на самом деле, не все нужно делать асинхронно. Вызовы методов, которые связаны исключительно с ЦП (и не «блокируются» в том смысле, что они не полностью перегружают ЦП, например, вычисление значения Пи), не должны выполняться асинхронно.

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

Этот подход имеет два недостатка:

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

Поэтому при проектировании систем акторов надо думать о том, что на самом деле должно быть асинхронным, в основном это:

— вызовов внешних систем (за пределами вашей jvm)
— вызовы блокирующих операций (устаревшие API, тяжелые вычисления, …)

Слишком много акторов в рантайме


Общая идея примерно такая: ”чем больше у нас акторов, тем быстрее все пойдет".

И действительно, акторы легкие, и вы можете запускать миллионы из них на одной виртуальной машине. Да, вы можете. Но нужно ли?

image

Если вы можете, это не значит, что вы должны

Короткий ответ: не всегда – это зависит от того, что вы делаете с акторами.

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

Однако, если у вас есть несколько типов акторов, которые участвуют в вычислении чего-то – например, разбор XML-документа — сомнительно создавать миллионы таких акторов (не важно напрямую или через роутер).

Процессор имеет фиксированное количество ядер (аппаратных потоков) в своем распоряжении, и обработка сообщений акторами Akka выполняется в ExecutionContext, базирующемся на пуле потоков. По умолчанию это fork-join-executor, основанный на ForkJoinPool, добавленный в Java 7.

Но, несмотря на свое техническое преимущество, forkjoinpool не является волшебством, отменяющим законы физики. Если у вас есть один миллион акторов, каждый из которых анализирует XML-документ (уже загруженный в память) и 4 аппаратных потока, система не будет работать намного лучше, чем если бы у вас было только 4 актора, анализирующих эти XML-документы (при условии однородной нагрузки). Фактически, ваша система будет работать намного лучше всего с 4 акторами, потому что будут только минимальные накладные расходы с точки зрения планирования и управления памятью. Кстати, если в вашей системе всего несколько акторов, проверьте ваш пул потоков, который наверняка пытается повторно использовать один и тот же поток для одного и того же актора.

В общем, система не станет работать быстрее, если создать много акторов.

Акторы без состояния


Акторы объектно-ориентированы правильно (в отличие, скажем, от объектов в Java): их состояние не видно снаружи, и они общаются через сообщения. Невозможно сломать инкапсуляцию, потому что нельзя заглянуть в состояние актора во время его работы. В этом весь смысл акторов: они предоставляют вам иллюзию безопасного пространства, в котором сообщения выполняются последовательно, одно за другим, позволяя вам использовать изменяемое состояние внутри вашего актера, не беспокоясь о состоянии гонки (race conditions — прим. пер.) (ну, почти не беспокоясь: главное не позволить утечь стейту).

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

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

Комментарии (4)


  1. Mishiko
    20.08.2018 23:58

    Откуда взяли картинку? Впервые за день рассмеялся


    1. MaxVetrov
      21.08.2018 09:19

      Только из-за нее и зашел =) Нарушены все меры безопасности. Хотя один мужик в наушниках =) Авось пронесет!


      1. Maa-Kut
        21.08.2018 17:01

        Хотя один мужик в наушниках =)

        Чтобы не слышать воплей коллеги-верстака, не иначе.


  1. ggo
    22.08.2018 10:00

    Поэтому при проектировании систем акторов надо думать о том, что на самом деле должно быть асинхронным, в основном это:
    — вызовов внешних систем (за пределами вашей jvm)
    — вызовы блокирующих операций (устаревшие API, тяжелые вычисления, …)

    Да

    В общем, система не станет работать быстрее, если создать много акторов.

    Да

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

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