понедельник, 28 января 2013 г.

Акторная модель для java

Акторная модель - математическая модель параллельных вычислений, которая трактует понятие «актор» как универсальный примитив параллельного численного расчёта: в ответ на сообщения, которые он получает, актор может принимать локальные решения, создавать новых акторов, посылать свои сообщения, а также устанавливать, как следует реагировать на последующие сообщения. Модель акторов возникла в 1973 году. (http://ru.wikipedia.org/wiki/Модель_акторов). Имеется реализация модели на java (http://akka.io/).

На ресурсе проекта достаточно документации по сабжу с пояснениями и примерами. В остальном информации в рунете немного, наиболее интересное из того, что удалось найти - Akka для Java разработчика да Потоковая обработка данных с помощью модели акторов.
 
Поначалу модель и её реализация на java весьма зацепили. Возможность строить полностью асинхронные системы из набора квантов-акторов, каждый из которых умеет хорошо что-то делать, которые взаимодействуют через очереди, которые можно объединять с классическим объектно-ориентированным кодом... красота!
 
Полная асинхронность взаимодействия - основное отличие akka. Это одновременно и достоинство, и недостаток.
Код, написанный на akka, некрасив. И здесь я имею в виду не только и не столько эстетическую сторону, но и удобочитаемость, понятность, простоту отслеживания логической последовательности, низкий порог вхождения в процесс другого разработчика.
Так вот, akka-код нечитаем, непонятен и непрост. Из-за наличия большого числа акторов и особенностей вызова (типа getSender.tell(...) или getContext.actorOf(...).tell(...)) очень и очень трудно охватить всю картину целиком: понятно, что кто-то кому-то отправляет сообщение. Но кому и будет ли ответное - сразу не понять, надо найти вызываемый актор, просмотреть его код, найти обработчик именно исходного сообщения... Долго и неочевидно. Неочевидно даже для автора спустя короткое время. Пытаться же представить себя на месте разработчика, перед которым стоит задача понять чужую акторную систему хотя бы из всего-то нескольких десятков акторов, не хочется вообще.
Конечно,  комментарии кода и параллельная поддержка схемы акторов в каком-либо графическом виде весьма помогут в этом деле. Но на практике мне встречались, мягко говоря, немного разработчиков, готовых и способных хорошо документировать собственный код.
 
А если представить, что требуется просто удалить один из акторов или исключить его из цепочки обработки данных. Это означает, что во всех акторах, которые его вызывали, требуется исправить вызов на другой актор или убрать вызов вообще. Если где-то останется ссылка, то сообщение может уйти в несуществующий mailbox и фактически исчезнет.
 
Далее, сам код получается тяжёлым. Реализация akka на scala выглядит более лёгковесной; реализация же на java гораздо менее проста. Следующие два примера делают одно и то же.
Listing 1. scala
class ExampleActor extends Actor {
    val other = context.actorOf(Props[OtherActor], "childName")
    def receive {
        case Request1(msg) => other ! refine(msg)
        case Request2(msg) => other.tell(msg, sender)
        case Request3(msg) => sender ! (other ? msg)
    }
}
Listing 2. java
public class ExampleActor Extends UntypedActor {
    final ActorRef other = getContext().actorOf(new Props(OtherActor.class), "childName");
    @Override
    public void onReceive(Object o) {
        if (o instanceof Request1) {
            val msg = ((Request1) o).getMsg();
            other.tell(msg);
        } else if (o instanceof Request2) {
            val msg = ((Request2) o).getMsg();
            other.tell(msg, getSender());
        } else if (o instanceof Request3) {
            val msg = ((Request3) o).getMsg();
            getSender().tell(ask(other, msg, 5000));
        } else {
            unhandled(o);
        }
    }
}

В самом начале знакомства с akka меня зацепили утверждения о том, что можно легко "подружить" akka-мир и классический объектно-ориентированный java-код. Проверил на практике - можно, но не особенно легко. То есть два независимых гетерогенных компонента, которые обмениваются установленными сообщениями, могут быть написаны на чём угодно. А вот совместить в одном компоненте акторы и классическое взаимодействие сложно. Предлагаются на выбор TypedActors либо Future. Можно даже сделать развязку между синхронным ответом и вызовом актора. Но весьма осторожно, т. к. вызов через блокировку - прямой (как кто-то выразился в одном форуме, 99-типроцентный) deadlock.

Что ещё? Асинхронность обмена внутри акторной системы означает, что невозможно точно определить её состояние в заданный момент времени. А если возник глобальный epic fail, и приложение упало? Восстановить полностью контекст, передаваемые сообщения, очереди mailbox'ов и состояние системы невозможно. В общем-то, в akka предусмотрена возможность сохранения очередей в своей базе данных, но это незавидная задача для девелопера.

В целом, почти всё, что я перечислил выше, покрывается советами best practice по использованию akka:
  • храните состояние вовне;
  • стройте всю систему на акторах;
  • пишите асинхронный код;
  • избегайте косвенного взаимодействия акторов.

Некоторое время раздумывал, стОит ли попробовать эту концепцию для нового разрабатываемого приложения. Сделал прототип из полутора десятков акторов-заглушек. Работает... но что-то не то... Наверно, я просто не вник в самую суть концепции либо хочу чего-то такого, что не "дружит" с нею. Тем не менее, пока я не видел удачного, доступного и понятного примера реализации сколь-нибудь крупной системы на akka. И опасаюсь использовать её сам...

2 комментария:

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

    ОтветитьУдалить
    Ответы
    1. Забавный у вас комментарий получился.

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

      Поэтому мне остаётся ещё раз, спустя 2 года, повторить своё видение.

      Если требуется приложение, логику которого можно разбить на небольшое количество частей (акторов в терминах фреймворка).
      Если стоит задача обеспечить производительность (приводить какие-то числа tps не имеет смысла, потому что в разных проектах и продуктах под транзакциями / операциями могут пониматься разные по сложности действия).
      Если приложение не предполагается особо развивать и поддерживать.
      Если в компании нет сформировавшегося стека используемых инструментов.
      Если хочется «поиграться» с технологией.
      Тогда – ок, можно выбрать для реализации akka. И если разработчик не дурак – наверняка получится работающее и достаточно быстрое приложение.

      Но если вы сделаете на akka большую систему, которая будет состоять не из всего-то «одного-двух слоёв», которая будет жить и развиваться несколько лет, для которой весьма вероятна смена разработчиков...
      Вот тогда вы, с большой вероятностью, и столкнётесь лоб в лоб с «сложно и думать надо и страшно и поддерживать ужасно».
      И задача разработчика (лида, архитектора) заранее обдумать сценарии использования продукта, прикинуть стоимость реализации / сопровождения и ответить на простой вопрос «а оно нам надо?». И в зависимости от ответа принять решение и нести за него ответственность.

      Я этот фреймворк попробовал «на вкус». И поэтому мой ответ на поставленный вопрос – нет, не надо.

      Удалить