Курсы актерского мастерства

49
Школа актерского мастерства @remeniuk

Upload: vasil-remeniuk

Post on 14-Nov-2014

4.534 views

Category:

Documents


1 download

DESCRIPTION

Презентация "Курсы актерского мастерства", которая была сделана на первоей встреча энтузиастов Scala Беларуси.

TRANSCRIPT

Page 1: Курсы актерского мастерства

Школа актерского мастерства

@remeniuk

Page 2: Курсы актерского мастерства

• 70-ые, концепция «Актеров»

• 80-ые, Erlang, популяризация

и развитие

– OTP – фреймворк для создания

надежных систем,

толерантных к ошибкам

• 00-ые, Scala, cовременное

представление

Page 3: Курсы актерского мастерства

Почему актеры?

• Параллелизм без страха

• Хороший дизайн

• Простая расширяемость

– location transparency

• Инструментарий для создания надежных

систем

WIN!

Page 4: Курсы актерского мастерства

• scala.actors

• Akka

• Scalaz

• Kilim

• Lift

Page 5: Курсы актерского мастерства

Будущее: scala.actors + Akka

Как будет происходить объединение scala.actors и

Akka?

Предположение: единый API, и fault-tolerance

стек из Akka.

Page 6: Курсы актерского мастерства

scala.actors

actor{ loop{ react{ case msg => } } }

actor{ while(true){ receive{ case msg => } } }

! !! !? ?

Thread-based Event-driven

Page 7: Курсы актерского мастерства

Актер с состоянием

Page 8: Курсы актерского мастерства

• Модель publisher/subscriber – тривиальный подход - хранение в переменной

protected var subscribers = List[Subscriber]()

loop {

react{

case AddSubscriber(subscriber) =>

subscribers = subscriber :: subscribers

case Publish(event) =>

subscribers.foreach(_ ! event)

Актер с состоянием

Page 9: Курсы актерского мастерства

Хранение состояния в Erlang

loop(State) ->

receive

Msg ->

NewState = whatever(Msg),

loop(NewState)

end.

Передача состояния в рекурсии: • «чистый» функциональный подход • требует поддержки оптимизации рекурсии

Page 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)

Page 11: Курсы актерского мастерства

Идемпотентный актер

Page 12: Курсы актерского мастерства

Идемпотентный актер

f(x) = f(f(x))

Актер, устойчивый к получению

дупликатов сообщений

Page 13: Курсы актерского мастерства

Nested react/receive (вложенный

обработчик)

• обрабатывается только первое

сообщение – остальные пропускаются

loop {

react {

case Event(evt) =>

// необходимые вычисления...

def skip: Unit = reactWithin(0) {

case Event(event) if(event == evt) => skip

case TIMEOUT =>

}

skip

Page 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)

Page 15: Курсы актерского мастерства

Типизированный актер

Page 16: Курсы актерского мастерства

Типизированный актер

• Процессы в Erlang не типизированы – Придает гибкость модели

• Актеры, по своей природе, динамичны

• HotSwap обработчика

• Базовые актеры в Scala и Akka,

следующие идеологии Erlang, тоже не

типизированы

Page 17: Курсы актерского мастерства

Типизированные актеры иногда полезны:

• !?, !!, !!! – известный тип «ответа»

• специализация обработчиков

Page 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

Page 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)

Находит в контексте обработчик

нужного типа

Page 20: Курсы актерского мастерства

Альтернативный подход

• декларирование возможных типов

сообщений в объекте-компаньоне

• паттерн Active Object

– типизированные актеры в Akka:

trait Service {

def serve(arg: String)

}

class ServiceImpl extends TypedActor with Service {

def serve(arg: String) = //…

}

service.serve(“foo”)

Выполняется асинхронно

Page 21: Курсы актерского мастерства

Scatter/Gather рассеять/собрать

Page 22: Курсы актерского мастерства

Scatter/Gather рассеять/собрать

Page 23: Курсы актерского мастерства

Тривиальное решение – блокирующий

синхронный аггрегатор

• аггрегатор не может обрабатывать новые

сообщения, пока собирает ответы

react{

case Event(event) =>

val results = Futures.awaitAll(1000,

subscribers.map(_ !! event):_*)

// обработка собранных ответов...

reply(results)

FAIL!

reply(res)

Page 24: Курсы актерского мастерства

Можно прибегнуть к хитрости – перенести

ожидание, обработку и ответ в другой поток • неэффективное использование – выделенный

поток просто ждет

react{

case Event(event) =>

actor{

val results = Futures.awaitAll(1000,

subscribers.map(_ !! event):_*)

// обработка собранных ответов...

reply(results)

}

Page 25: Курсы актерского мастерства

Aсинхронная аггрегация – решение в Erlang-

стиле

1) канал для передачи результатов аггрегации

2) асинхронная сборка результатов:

• ответы «рабочих» обрабатываются

аггрегатором наравне с другими

сообщениями

• промежуточные результаты передаются

рекурсивно

Page 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)

case Result(res) =>

if(results.size != workers.size - 1) loop(res :: results)

else { // обработка собранных результатов...

replyChannel ! (res :: results); loop(Nil)

}

reply(Result(res))

Page 27: Курсы актерского мастерства

val aggregator = new AsynchAggregator(subscribers,

replyChannel)

aggregator ! Event("hello")

replyChannel.receive{

case message => // результат работы аггрегатора

}

Клиентская сторона (синхронное окружение)

• клиент ждет сообщение в канале, блокируя

поток

Page 28: Курсы актерского мастерства

val aggregator = new AsynchAggregator(subscribers,

replyChannel)

actor { aggregator ! Event("hello") }

replyChannel.receive{

case message => // результат работы аггрегатора

}

Клиентская сторона (синхронное окружение)

• клиент ждет сообщение в канале, блокируя

поток

Page 29: Курсы актерского мастерства

Другие библиотеки

• Функциональный подход c Promises в

Scalaz • Псевдо-императивный подход с

Dataflow в Akka

Page 30: Курсы актерского мастерства

Scalaz Promise[T]

Актер-работник асинхронно дает

аггрегатору обещание (promise), и выполняет

его

react {

case Message(msg) =>

val rez = new Promise[String]()(Strategy.Sequential)

reply(rez)

rez.fulfill{Thread sleep 1000; “Hello!”}

}

Page 31: Курсы актерского мастерства

Scalaz Promise[T]

Аггрегатор дает клиенту обещание результата,

декларируя будущую аггрегацию и обработку

case msg: Message =>

val res = workerz.map(worker => (worker !? msg)

.asInstanceOf[Promise[String]])

.sequence

.map{results =>

// обработка

results

}

reply(res)

PROFIT!!!

List[Promise[String]] => Promise[List[String]]

Page 32: Курсы актерского мастерства

Akka Dataflow

Dataflow concurrency – декларативная модель

безопасного параллелизма

Dataflow-переменная

• Изменение значения

вызывает цепную реакцию

• Инициализируется единожды

• Может безопасно

использоваться в

многопоточном окружении

Page 33: Курсы актерского мастерства

def sample{

val x0, x1, y0, y1 = Promise[Int]()

flow{

y0 << x0() + 1; println("y0 = " + y0())

y1 << x1() + y0(); println("y1 = " + y1())

}

flow{ x0 << 1 }

flow{ x1 << 2 }

}

Akka Dataflow*

scala> sample y0 = 2 y1 = 4

*базируется на continuations → нет lock’ов

Dataflow-переменные

Присвоение значения

Page 34: Курсы актерского мастерства

val result = Promise[String]()

val promises = List.fill(recipients.size)(Promise[String]())

recipients.zip(promises).map{case (recipient, promise) =>

(recipient !!! msg).map{result: String =>

flow{

promise << result }

}

}

Как применить dataflow для scatter /

gather?

• Рассылка, и асинхронная инициализация

dataflow-переменных

Page 35: Курсы актерского мастерства

def gather(promises: List[CompletableFuture[String]],

result: String = ""): String @cps[Future[Any]] =

promises match {

case head :: tail =>

gather(tail, head() + result)

case Nil => result

}

flow {

result << gather(promises)

}

Аггрегатор

Page 36: Курсы актерского мастерства

Балансировка нагрузки

Page 37: Курсы актерского мастерства

• Балансировка на уровне инфраструктуры

• Актер-роутер, работающий с пулом актеров, выбирающий «работника» согласно некому

алгоритму балансировки

• Балансировка на уровне

планировщика/диспетчера потоков

• scala.actors: fork-join pool • Akka: work-stealing dispatchers

Page 38: Курсы актерского мастерства

Актер-роутер

case class Ready(actor:

Actor)

def worker = actor{

loop{

react{

case msg: Ready =>

reply(this)

case msg =>

//обработка

class Balancer[A <: Actor](workers:

Iterable[A]) extends Actor{

def act = loop{

react{

case msg: Ready =>

case msg =>

val _sender = sender

workers foreach {_ ! Ready(this)}

react {

case Ready(worker) =>

worker.send(msg, _sender)

}

}

}

Page 39: Курсы актерского мастерства

+ • роутер незамедлительно узнает, когда

появляется незанятый работник

• роутер может работать с любыми типами

актеров (локальные, удаленные)

- • сообщения о статусе работников создают

много «шума»

• роутер заблокирован, пока не освободится

какой-нибудь «работник»

Page 40: Курсы актерского мастерства

Более эффективное решение в рамках одной

JVM - балансировка на уровне планировщика/

диспетчера потоков.

Akka

Пул актеров управляется work-stealing dispatcher.

Если какой-то актер из пула не занят, он «крадет»

сообщения из mailbox’а другого актера

Page 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 val actor2= actorOf[MyActor].start

actor1 ! LongRunningTask

actor1 ! LongRunningTask

actor2

«крадет» сообщение

Page 42: Курсы актерского мастерства

Супервизор

Page 43: Курсы актерского мастерства

“If somebody dies, other people

will notice” Programming Erlang, Joe Armstrong

Page 44: Курсы актерского мастерства

Линки объединяют группу актеров в единую

подсистему. Если один из актеров «умирает»,

прилинкованные актеры тоже прекращают работу

actor {

self link anotherActor

}

Ловушка перехватывает сигнал выхода

actor {

self.trapExit = true

… }

Линки и ловушки – базовые элементы для

построения систем на основе актеров

Page 45: Курсы актерского мастерства

• супервизор – любой актер, прилинкованный к

другому, и обрабатывающий сигнал выхода

• supervisor (OTP) - если контролируемый актер

умирает, перехватывает сигнал выхода, и

принимает меры, в соответствии с выбранной

стратегией

Супервизор

Page 46: Курсы актерского мастерства

Доступные реализации

• Akka Supervisors

• Scala-OTP

– хороший выбор, если вы работаете с

scala.actors

Page 47: Курсы актерского мастерства

SupervisorConfig(

OneForOneStrategy(List(classOf[Exception]), 3, 10),

Supervise(

myFirstActor,

Permanent) ::

Supervise(

mySecondActor,

Permanent) ::

Nil)

Akka Supervisors

Стратегия поведения в случае «смерти» актера

Наблюдаемые актеры

Вдохновлены супервизорами из Erlang/OTP

Page 48: Курсы актерского мастерства

• транзакционный актер

• каталог актеров и ресурсов

• фасад процессов

• многие другие

Page 49: Курсы актерского мастерства

Спасибо за внимание!

Вопросы?