[Akka] Classic Actors

smlee·2023년 8월 23일
0

Akka

목록 보기
6/50
post-thumbnail

Timers, scheduled messages

메시지들은 Scheduler를 직접적으로 사용함으로써 미래에 보내지도록 설정할 수 있다. 하지만, 스케줄링 기간이나 액터에서의 개별 메시지들은 named timer들을 사용하는 것이 더 편리하고 안전하다.
스케줄링된 메시지들의 라이프사이클은 액터가 재시작되고 타이머로부터 제어가 된다면 관리하기 어려워진다.

import scala.concurrent.duration._

import akka.actor.Actor
import akka.actor.Timers

object MyActor {
  private case object TickKey
  private case object FirstTick
  private case object Tick
}

class MyActor extends Actor with Timers {
  import MyActor._
  timers.startSingleTimer(TickKey, FirstTick, 500.millis)

  def receive = {
    case FirstTick =>
      timers.startTimerWithFixedDelay(TickKey, Tick, 1.second)
    case Tick =>
    // ...
  }
}

Scheduler documentationfixed-delayfixed-rate 스케줄링의 차이점이 상세히 적혀있다. 하지만, 2개의 차이점을 구분하기 힘들고 어떤 것을 선택해야할 지 모른다면 위의 예시 코드처럼 startTimerWithFixedDelay를 사용하면 된다.
(위의 스케줄러 문서는 나중에 자세히 정리할 예정이다.)

위의 타이머들은 그 타이머를 가지고 있는 actor의 라이프사이클 내에서 사용된다. 그리고 이 타이머들은 액터들이 재시작되거나 정지되면 취소된다. 이때, TimerScheduler는 thread-safe하지 않고, 타이머를 가지고 있는 액터와 반드시 같이 사용해야 한다.

Stopping actors

(1) stop

액터들은 ActorRefFactory에 있는 메서드인 stop을 통해 액터들을 멈출 수 있다. context.stop(ActorRef) 형태로 사용한다. 이러한 stop 메서드는 액터 그 자신이나 자손 액터들 그리고 시스템을 위한 top-level 액터들을 멈출 때 사용한다.
액터의 실질적인 종료는 비동기적으로 실행된다.

class MyActor extends Actor {

  val child: ActorRef = ???

  def receive = {
    case "interrupt-child" =>
      context.stop(child)

    case "done" =>
      context.stop(self)
  }

}

위는 stop을 사용한 코드의 예제이다. 현재의 메시지를 처리하는 과정에서 액터는 멈춘다. 그러나, 메일박스의 추가적인 메시지는 처리되지 않는다. 기본적으로, 이 메시지들은 ActorSystem의 deadLetters에 이 메시지들이 보내진다.

(2) PoisonPill

그리고, PoisonPill이라는 메시지를 액터에게 보냄으로써 액터를 멈출 수 있다.

class PingActor extends Actor{

  private val log = Logging(context.system, this)
  private var countDown:Int = 100

  override def receive: Receive = {
    case Pong =>
      log.info(s"${self.path} received pong, count down $countDown")

      if (countDown > 0) {
        countDown -= 1
        sender() ! Ping
      } else {
        sender() ! PoisonPill
        self ! PoisonPill
      }

  }
}

이전 포스트들에서 사용했던 클래스이다. 이때, PoisonPill이라는 메시지를 보내는 것이다.
PoisonPill이 보내지면 mailbox에 enqueue된다. mailbox에서는 이전에 enqueue되었던 메시지들을 모두 처리하고 PoisonPill을 처리한다. 그리고 액터를 멈춘다.

(3) kill

kill 역시 액터를 멈추게 하는 메시지이다. 하지만, PoisonPill과는 다르게 액터가 ActorKilledException을 throw하도록 하며 failure를 발생시킨다. 액터는 연산을 멈추고 어떻게 이 failure를 다룰 것인지 물어본다. 즉, 액터를 재개할 것인지 뭇는것이다. 재시작하거나 완전히 종료시킨다.

context.watch(victim) // watch the Actor to receive Terminated message once it dies

victim ! Kill

expectMsgPF(hint = "expecting victim to terminate") {
  case Terminated(v) if v == victim => v // the Actor has indeed terminated
}

(4) Graceful Stop

gracefulStop은 종료를 기다려야할 때나 몇몇 액터들의 종료 순서를 정해야할 때 사용하기 좋다.

import akka.pattern.gracefulStop
import scala.concurrent.Await

try {
  val stopped: Future[Boolean] = gracefulStop(actorRef, 5 seconds, Manager.Shutdown)
  Await.result(stopped, 6 seconds)
  // the actor has been stopped
} catch {
  // the actor wasn't stopped within 5 seconds
  case e: akka.pattern.AskTimeoutException =>
}

gracefulStop은 성공적으로 리턴되면, 액터의 postStop이 실행된다.

Become/Unbecome

Akka는 runtime에 메시지루프를 hotswapping하는 것을 도와준다.

hot swapping
hot swap이란 컴포넌트들을 전원이 들어와 있는 상태에서 지우거나 컴퓨터 시스템에 플러깅하는 행위를 뜻한다. 즉, 컴퓨터의 일부분이 컴퓨터나 서버의 재부팅이나 shut down 없이도 변경되는 것을 뜻한다.

become을 사용하여 Hotswap을 하려면 다음과 같다.

class HotSwapActor extends Actor {
  import context._
  def angry: Receive = {
    case "foo" => sender() ! "I am already angry?"
    case "bar" => become(happy)
  }

  def happy: Receive = {
    case "bar" => sender() ! "I am already happy :-)"
    case "foo" => become(angry)
  }

  def receive = {
    case "foo" => become(angry)
    case "bar" => become(happy)
  }
}

become 메서드 안에 들어가는 인자는 많은 곳에서 유용하다. 이것은 현재 행위를 대체하는데 사용된다. 즉, unbecome을 사용하지 않는다면 다음 행위는 명시적으로 설치된다.

Reference

0개의 댓글