Actor 생성

Ada·2023년 8월 21일

Akka

목록 보기
2/32

클래스 정의

receive 메서드를 통해 구현

receive 메서드는 메시지 처리 방법 구현과 함께 표준 Scala 패턴 일치를 사용하여
Actor가 처리할 수 있는 메시지를 정의하는 일련의 case 문(PartialFunction[Any, Unit] 타입을 가지고 있는)을 정의해야 합니다.

import akka.actor.Actor
import akka.actor.Props
import akka.event.Logging

class MyActor extends Actor {
  val log = Logging(context.system, this)

  def receive = {
    case "test" => log.info("received test")
    case _      => log.info("received unknown message")
  }
}

receive 메시지 루프는 수락할 수 있는 모든 메시지에 대해 패턴 매칭을 제공해야 하며
알 수 없는 메시지를 처리하려면 위와 같은 기본 사례가 있어야 합니다.

그렇지 않으면 UnhandledMessageActorSystemEventStream 에서 발생합니다.

receive 메서드의 결과는 액터 내에 '초기 동작' 으로 저장되는 부분 함수 객체입니다.

import akka.actor.{ Actor, ActorRef, ActorSystem, PoisonPill, Props }
import language.postfixOps
import scala.concurrent.duration._

case object Ping
case object Pong

class Pinger extends Actor {
  var countDown = 100

  def receive = {
    case Pong =>
      println(s"${self.path} received pong, count down $countDown")

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

class Ponger(pinger: ActorRef) extends Actor {
  def receive = {
    case Ping =>
      println(s"${self.path} received ping")
      pinger ! Pong
  }
}

    val system = ActorSystem("pingpong")

    val pinger = system.actorOf(Props[Pinger](), "pinger")

    val ponger = system.actorOf(Props(classOf[Ponger], pinger), "ponger")

    import system.dispatcher
    system.scheduler.scheduleOnce(500 millis) {
      ponger ! Ping
    }

Props

Props 는 액터 생성을 위한 옵션을 지정하는 구성 클래스이며 변경 불가능합니다.

관련 배포 정보를 포함하는 액서 생성을 위한 공유 가능한 레시피입니다.

// props 인스턴스 생성 예

import akka.actor.Props

val props1 = Props[MyActor]()
val props2 = Props(new ActorWithArgs("arg")) // careful, see below
val props3 = Props(classOf[ActorWithArgs], "arg") // no support for value class arguments

두 번째 변형은 생성자 인수를 Actor 생성자 인수에 전달하는 방법을 보여주는데,
이는 액터의 외부에서만 사용해야 합니다.

마지막 줄은 사용 중인 컨텍스트에 관계 없이 생성자 인수를 전달할 가능성을 보여줍니다.
일치하는 생성자의 존재는 Props 객체를 생성하는 동안 확인되며, 일치하는 생성자가 없거나 여러개일 경우 IllegalArgumentException 이 발생합니다.

Actor Props 를 생성하는 데 추천되는 방식은 Actor 생성자가 value 클래스를 인수로 사용하는 경우 지원되지 않습니다.

액터를 다른 액터 안에 선언하는 것은 액터 캡슐화를 깨뜨리며 매우 위험합니다. 액터의 this 참조를 Props 에 절대 전달하면 안됩니다.

엣지 케이스

akka.actor.Props 액터 생성에는 두 가지 엣지 케이스가 있습니다.

  1. AnyVal 인수를 가진 액터
case class MyValueClass(v: Int) extends AnyVal

class ValueActor(value: MyValueClass) extends Actor {
  def receive = {
    case multiplier: Long => sender() ! (value.v * multiplier)
  }
}
val valueClassProp = Props(classOf[ValueActor], MyValueClass(5)) // Unsupported
  1. 기본 생성자 값을 가진 액터
class DefaultValueActor(a: Int, b: Int = 5) extends Actor {
  def receive = {
    case x: Int => sender() ! ((a + x) * b)
  }
}

val defaultValueProp1 = Props(classOf[DefaultValueActor], 2.0) // Unsupported

class DefaultValueActor2(b: Int = 5) extends Actor {
  def receive = {
    case x: Int => sender() ! (x * b)
  }
}
val defaultValueProp2 = Props[DefaultValueActor2]() // Unsupported
val defaultValueProp3 = Props(classOf[DefaultValueActor2]) // Unsupported

두 경우 모두 일치하는 생성자를 찾을 수 없다는 IllegalArgumentException 메시지가 표시됩니다.

권장 사례

각 액터의 companion 객체에 factory 메소드를 제공하여 액터 정의에 최대한 근접한 적합한 Props 생성을 유지하는 것이 좋습니다.
이것은 동반 객체 내에서 지정된 코드 블록이 둘러싸는 범위에 대한 참조를 유지하지 않기 때문에
by-name 인수를 사용하는 Props.apply(...) 메소드 사용과 관련된 위험을 방지합니다.

object DemoActor {

  /**
   * Create Props for an actor of this type.
   *
   * @param magicNumber The magic number to be passed to this actor’s constructor.
   * @return a Props for creating this actor, which can then be further configured
   *         (e.g. calling `.withDispatcher()` on it)
   */
  def props(magicNumber: Int): Props = Props(new DemoActor(magicNumber))
}

class DemoActor(magicNumber: Int) extends Actor {
  def receive = {
    case x: Int => sender() ! (x + magicNumber)
  }
}

class SomeOtherActor extends Actor {
  // Props(new DemoActor(42)) would not be safe
  context.actorOf(DemoActor.props(42), "demo")
  // ...
}

또 다른 좋은 방법은 Actor가 수신할 수 있는 메시지를 Actor의 companion 객체에 선언하여 수신할 수 있는 내용을 쉽게 알 수 있도록 하는 것입니다.

object MyActor {
  case class Greeting(from: String)
  case object Goodbye
}
class MyActor extends Actor with ActorLogging {
  import MyActor._
  def receive = {
    case Greeting(greeter) => log.info(s"I was greeted by $greeter.")
    case Goodbye           => log.info("Someone said goodbye to me.")
  }
}

Props 로 액터 만들기

액터는 ActorSystem 및 ActorContext 에서 사용할 수 있는 actorOf factory 메소드에 Props 인스턴스를 전달하여 생성됩니다.

import akka.actor.ActorSystem

// ActorSystem is a heavy object: create only one per application
val system = ActorSystem("mySystem")
val myActor = system.actorOf(Props[MyActor](), "myactor2")

ActorSystem 을 사용하면 액터의 컨텍스트를 사용하는 동안 최상위 액터(액서 시스템이 제공하는 가디언 액터가 감독하는)를 생성하고 자식 액터를 생성합니다.

class FirstActor extends Actor {
  val child = context.actorOf(Props[MyActor](), name = "myChild")
  def receive = {
    case x => sender() ! x
  }
}

어플리케이션의 논리적 오류 처리 구조에 맞도록 계층 구조를 만드는 것이 좋습니다.

actorOf 에 대한 호출은 ActorRef의 인스턴스를 반환합니다.
이것은 액터 인스턴스에 대한 핸들이며, 그것과 상호 작용하는 유일한 방법입니다.

ActorRef 는 변경할 수 없으며, 그것이 나타내는 액터와 일대일 관계를 가집니다.
또한 직렬화와 네트워크 인식이 가능하기 때문에 유선으로 전송하거나 원격 호스트에서 사용할 수 있으며 네트워크 전체의 오리지널 노드에서 여전히 동일한 액터를 나타냅니다.

name 매개 변수는 선택 사항이지만, 액터의 이름을 지정하는 것이 좋습니다.
-> 로그 메시지와 액터 식별에 사용되기 때문입니다.

이름은 비어있거나, $ 로 시작되면 안 되지만, URL인코딩 문자(예: 공백의 경우 20% ) 를 포함할 수 있습니다.

이름이 동일한 부모의 다른 자식에 의해 이미 사용 중인 경우 InvalidActorNameExcetion 이 발생합니다.

액터는 생성될 때 자동으로 비동기식으로 시작됩니다.

actor Props를 인스턴스화 하는 권장 방법은 호출할 올바른 액터 생성자를 결정하기 위해
run-time 에 reflection 을 사용하며,
기술적 제한으로 인해 해당 생성자가 value 클래스인 파라미터를 사용할 때 지원되지 않습니다.

이러한 경우 파라미터를 압축 해제하거나 생성자를 수동으로 호출하여 Props를 만들어야 합니다.

class Argument(val value: String) extends AnyVal
class ValueClassActor(arg: Argument) extends Actor {
  def receive = { case _ => () }
}

object ValueClassActor {
  def props1(arg: Argument) = Props(classOf[ValueClassActor], arg) // fails at runtime
  def props2(arg: Argument) = Props(classOf[ValueClassActor], arg.value) // ok
  def props3(arg: Argument) = Props(new ValueClassActor(arg)) // ok
}

의존성 주입

actor 에 파라미터를 사용하는 생성자가 있는 경우
파라미터 또한 Props 의 일부여야 합니다.

그러나 실제 생성자 파라미터가 의존성 주입 프레임워크에 의해 결정되는 경우와 같이
factory 메서드를 사용해야 하는 경우가 있습니다.

import akka.actor.IndirectActorProducer

class DependencyInjector(applicationContext: AnyRef, beanName: String) extends IndirectActorProducer {

  override def actorClass = classOf[Actor]
  override def produce() =
    new Echo(beanName)

  def this(beanName: String) = this("", beanName)
}

val actorRef: ActorRef =
  system.actorOf(Props(classOf[DependencyInjector], applicationContext, "hello"), "helloBean")

🧨 경고
lazy val 를 사용하여 항상 동일한 인스턴스를 반환하는 IndirectActorProducer 를 사용하고싶을 수 있습니다.
이것은 actor restart 의 의미에 반하기 때문에 지원되지 않습니다.

의존성 주입 프레임워크를 사용할 때, actor beansingletone 범위를 가져서는 절대 안됩니다.

profile
백엔드 프로그래머

0개의 댓글