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

Модель акторов — это математическая модель параллельных вычислений. Была предложена в 70х годах Карлом Хьюитом. Эта модель получила широкое применение в некоторых языках программирования(Erlang, Elixir) и фреймворках(ActiveJava).

Акторы — альтернативный подход к организации паралелльных вычислений и распределенных систем.

Акторы

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

Оne ant is not an ant

Актор получает сообщения асинхронно. Но обрабатывает их по очереди. Если вы отправляете 3 сообщения одному актору — он обрабатывает их по одному. Если вы хотите чтобы сообщения обрабатывались параллельно — нужно создать по одному актору для каждого сообщения и передать каждому актору по сообщению.

Пока актор обрабатывает сообщение — он должен хранить где-то сообщения продолжающие поступать к нему. Это хранилище традиционно называют ‘mailbox’.

Когда актор получает сообщение — он может сделать одно из следующих действий:

  • создать новый актор
  • отправить сообщение другим акторам
  • поменять внутреннее состояние

Отказоустойчивость

В Erlang есть концепция — let it crash. Смысл ее заключается в том что программист не должен писать код пытаясь избежать все возможные проблемные кейсы — поскольку невозможно предусмотреть все. Erlang — позволяет упасть — но так чтобы падение было контролируемым — что делать после падения — перевести единицу кода к стабильному состоянию. Это достигается с помощью модели акторов.

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

Распределенность

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

Рассмотрим (весьма грубый) пример реализации модели акторов на JavaScript.

Пример реализации на Javascript

Для создания уникального идентификатора актора мы использовали Sybmol который возвращает уникальное значение при каждом вызове.

Реализация Поведения:

import ActorModel from "actor-model";
// Как актор должен реагировать на сообщения
const counter = {
// Начальное состояние
init() {
return { count: 0 };
},
// Определить методы которые должны быть вызваны при получении сообщения
incrementBy(state, { number }) {
let count = state.count + number;
// возвращает значение - может изменить стэйт актора
return { count };
},
logTotal(state) {
// не возвращает ничего - не может менять стэйт актора
console.log(state.count);
}
};
// Создать актор
const address = ActorModel.start(counter);
// Выведем начальное состояние
ActorModel.send(address, ["logTotal"]); // => { count: 0 }
// Увеличим счетчик на 2
ActorModel.send(address, ["incrementBy", { number: 2 }]);
// Выведем текущее состояние
ActorModel.send(address, ["logTotal"]); // => { count: 2 }

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

import EventEmitter from "events";
const mailbox = new EventEmitter();
// актор
const ActorModel = {
//создаем актор
start(behavior) {
//создаем уникальный адрес
const address = Symbol();
// начальный стэйт
let state = typeof behavior.init === "function" ? behavior.init() : {};
// обработка полученного сообщения
// если метод Поведения имеет возвращаемое значение - использовать его как новый стэйт
mailbox.on(address, function([method, message]) {
state = behavior[method](state, message) || state;
});
//возвращаем уникальный адрес
return address;
},
// отправить сообщение по уникальному адресу 
send(target, message) {
mailbox.emit(target, message);
}
};
export default ActorModel;

Резюме

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

Каждый актор характеризуется:

  • Актор имеет внутреннее состояние, которое невидимо для окружающего мира
  • Актор взаимодействует с другими акторами путем асинхронных сообщений
  • У каждого актора есть уникальный адрес по которому можно отправить сообщение

По получении сообщения актор может

  • Создать новый актор
  • Отправить сообщение другому актору
  • Поменять внутреннее состояние