2. • 70-ые, концепция «Актеров»
• 80-ые, Erlang, популяризация
и развитие
– OTP – фреймворк для создания
надежных систем,
толерантных к ошибкам
• 00-ые, Scala, cовременное
представление
3. Почему актеры?
• Параллелизм без страха
• Хороший дизайн
• Простая расширяемость
– location transparency WIN!
• Инструментарий для создания надежных
систем
8. Актер с состоянием
• Модель publisher/subscriber
– тривиальный подход - хранение в переменной
protected var subscribers = List[Subscriber]()
loop {
react{
case AddSubscriber(subscriber) =>
subscribers = subscriber :: subscribers
case Publish(event) =>
subscribers.foreach(_ ! event)
9. Хранение состояния в Erlang
Передача состояния в рекурсии:
• «чистый» функциональный подход
• требует поддержки оптимизации рекурсии
loop(State) ->
receive
Msg ->
NewState = whatever(Msg),
loop(NewState)
end.
10. Модель publisher/subscriber
• Передача состояния в рекурсии
def loop(subscribers: List[Subscriber]): Unit
= react{
case AddSubscriber(subscriber) =>
loop(subscriber :: subscribers)
case Publish(event) =>
subscribers.foreach(_ ! event)
loop(subscribers)
}
def act = loop(Nil)
13. Nested react/receive (вложенный
обработчик)
• обрабатывается только первое
сообщение – остальные пропускаются
loop {
react {
case Event(evt) =>
// необходимые вычисления...
def skip: Unit = reactWithin(0) {
case Event(event) if(event == evt) => skip
case TIMEOUT =>
}
skip
14. Nested react/receive способен на
большее:
• обработка с приоритетом
reactWithin(0){
case Event(evt: String) =>
subscribers.foreach(_ ! evt)
case TIMEOUT => react {
case AddSubscriber(subscriber) =>
subscribers = subscriber :: subscribers
case Event(evt) =>
subscribers.foreach(_ ! evt)
16. Типизированный актер
• Процессы в Erlang не типизированы
– Придает гибкость модели
• Актеры, по своей природе, динамичны
• HotSwap обработчика
• Базовые актеры в Scala и Akka,
следующие идеологии Erlang, тоже не
типизированы
18. • Scala <2.7.x - InputChannel[T]
• Scala 2.8.x+ - Reactor[T]
sealed trait Message
class Foo extends Message
class Bar extends Message
class DatatypeReceiver[T >: Null <: Message]
extends Reactor[T]
Принимает сообщения
подтипа Message
19. Типизация актеров позволяет использовать
всю мощь системы типов Scala
– Typeclass паттерн
implicit val fooReceiver = new DatatypeReceiver[Foo]
implicit val barReceiver = new DatatypeReceiver[Bar]
def send[T >: Null <: Message : DatatypeReceiver]
(message: T) =
implicitly[DatatypeReceiver[T]] ! message
send(new Foo)
Находит в контексте обработчик
нужного типа
20. Альтернативный подход
• декларирование возможных типов
сообщений в объекте-компаньоне
• паттерн Active Object
– типизированные актеры в Akka:
trait Service {
def serve(arg: String)
}
class ServiceImpl extends TypedActor with Service {
def serve(arg: String) = //…
}
Выполняется
service.serve(“foo”) асинхронно
23. Тривиальное решение – блокирующий
синхронный аггрегатор
• аггрегатор не может обрабатывать новые
сообщения, пока собирает ответы
FAIL!
react{
case Event(event) =>
val results = Futures.awaitAll(1000,
reply(res) subscribers.map(_ !! event):_*)
// обработка собранных ответов...
reply(results)
24. Можно прибегнуть к хитрости – перенести
ожидание, обработку и ответ в другой поток
• неэффективное использование – выделенный
поток просто ждет
react{
case Event(event) =>
actor{
val results = Futures.awaitAll(1000,
subscribers.map(_ !! event):_*)
// обработка собранных ответов...
reply(results)
}
25. Aсинхронная аггрегация – решение в Erlang-
стиле
1) канал для передачи результатов аггрегации
2) асинхронная сборка результатов:
• ответы «рабочих» обрабатываются
аггрегатором наравне с другими
сообщениями
• промежуточные результаты передаются
рекурсивно
26. class AsynchAggregator(workers: Iterable[Subscriber],
replyChannel: Channel[Any]) extends Actor {
def loop(results: List[Any]): Unit = react {
case event: Event[Any] =>
workers foreach(_ ! event); loop(results)
reply(Result(res))
case Result(res) =>
if(results.size != workers.size - 1) loop(res :: results)
else {
// обработка собранных результатов...
replyChannel ! (res :: results); loop(Nil)
}
27. Клиентская сторона (синхронное окружение)
• клиент ждет сообщение в канале, блокируя
поток
val aggregator = new AsynchAggregator(subscribers,
replyChannel)
aggregator ! Event("hello")
replyChannel.receive{
case message => // результат работы аггрегатора
}
28. Клиентская сторона (синхронное окружение)
• клиент ждет сообщение в канале, блокируя
поток
val aggregator = new AsynchAggregator(subscribers,
replyChannel)
actor { aggregator ! Event("hello") }
replyChannel.receive{
case message => // результат работы аггрегатора
}
30. Scalaz Promise[T]
Актер-работник асинхронно дает
аггрегатору обещание (promise), и выполняет
его
react {
case Message(msg) =>
val rez = new Promise[String]()(Strategy.Sequential)
reply(rez)
rez.fulfill{Thread sleep 1000; “Hello!”}
}
31. Scalaz Promise[T]
Аггрегатор дает клиенту обещание результата,
декларируя будущую аггрегацию и обработку
case msg: Message =>
val res = workerz.map(worker => (worker !? msg)
.asInstanceOf[Promise[String]])
.sequence
.map{results =>
// обработка PROFIT!!!
results
}
List[Promise[String]] => Promise[List[String]]
reply(res)
32. Akka Dataflow
Dataflow concurrency – декларативная модель
безопасного параллелизма
Dataflow-переменная
• Изменение значения
вызывает цепную реакцию
• Инициализируется единожды
• Может безопасно
использоваться в
многопоточном окружении
37. • Балансировка на уровне инфраструктуры
• Актер-роутер, работающий с пулом актеров,
выбирающий «работника» согласно некому
алгоритму балансировки
• Балансировка на уровне
планировщика/диспетчера потоков
• scala.actors: fork-join pool
• Akka: work-stealing dispatchers
38. Актер-роутер
case class Ready(actor: class Balancer[A <: Actor](workers:
Actor) Iterable[A]) extends Actor{
def worker = actor{ def act = loop{
loop{ react{
react{ case msg: Ready =>
case msg: Ready => case msg =>
reply(this) val _sender = sender
case msg => workers foreach {_ ! Ready(this)}
//обработка react {
case Ready(worker) =>
worker.send(msg, _sender)
}
}
}
39. +
• роутер незамедлительно узнает, когда
появляется незанятый работник
• роутер может работать с любыми типами
актеров (локальные, удаленные)
-
• сообщения о статусе работников создают
много «шума»
• роутер заблокирован, пока не освободится
какой-нибудь «работник»
40. Более эффективное решение в рамках одной
JVM - балансировка на уровне планировщика/
диспетчера потоков.
Akka
Пул актеров управляется work-stealing dispatcher.
Если какой-то актер из пула не занят, он «крадет»
сообщения из mailbox’а другого актера
41. Akka: work-stealing dispatcher
object MyActor {
val dispatcher = Dispatchers.newExecutorBasedEvent>
DrivenWorkStealingDispatcher(name).build
}
class MyActor extends Actor {
self.dispatcher = MyActor.dispatcher ; ...
}
val actor1= actorOf[MyActor].start actor2
val actor2= actorOf[MyActor].start «крадет»
сообщение
actor1 ! LongRunningTask
actor1 ! LongRunningTask
43. “If somebody dies, other people
will notice”
Programming Erlang, Joe Armstrong
44. Линки и ловушки – базовые элементы для
построения систем на основе актеров
Линки объединяют группу актеров в единую
подсистему. Если один из актеров «умирает»,
прилинкованные актеры тоже прекращают работу
actor {
self link anotherActor
…
}
Ловушка перехватывает сигнал выхода
actor {
self.trapExit = true
…
}
45. Супервизор
• супервизор – любой актер, прилинкованный к
другому, и обрабатывающий сигнал выхода
• supervisor (OTP) - если контролируемый актер
умирает, перехватывает сигнал выхода, и
принимает меры, в соответствии с выбранной
стратегией