이번 강의에서는 액터가 메시지를 주고 받는 기본적인 내용을 다루었다.
예시가 많았는데 아무래도 영문 구어체를 번역해서 보다보니 이해가 조금 안되는 부분도 있다.
Akka는 Actor 모델 기반의 동시성 프레임워크이다. Actor는 시스템 내의 작은 연산 단위로서, 메시지를 통해 서로 통신한다. 각 Actor는 고유한 메일박스를 가지며, 메시지는 비동기적으로 처리된다. Actor의 주요 특징은 캡슐화, 병렬 실행, 메시지 기반 통신 등 이다.
Actor 간의 유일한 통신 방법은 메시지 전송이다.
tell 메서드를 사용하여 메시지를 보낸다. 메시지는 불변해야 하며 직렬화할 수 있어야 한다.
Actor는 자신의 참조를 self 또는 context.self를 통해 알 수 있다.
또한 메시지를 보낸 Actor의 참조는 sender 또는 context.sender를 통해 알 수 있다.
메시지의 수신자가 없거나 응답을 보낼 Actor가 없을 때, 해당 메시지는 Dead Letters로 보내진다. Dead Letters는 처리되지 않은 메시지를 수집하는 Akka의 가상 Actor이다.
Actor는 메시지를 다른 Actor에게 전달할 수 있다. 이때 원래의 보내는 이의 참조를 유지하면서 메시지를 전달하는 것이 가능하다.
Actor는 완전히 캡슐화되어 있어, 외부에서 데이터에 접근하거나 메서드를 호출할 수 없다.
Akka는 Actor의 원칙을 강제하며, 이 원칙에는 메시지 기반 통신, 비동기 및 논블로킹 처리 등이 포함된다.
주석 부분에 추가로 공부한 내용이 있다.
object ActorCapabilities extends App {
class SimpleActor extends Actor {
override def receive: Receive = {
// context.sender(=sender) 는 액터 참조를 반환함.
// sender() 는 마지막으로 보낸 행위자 참조
// 어떤 순간의 모든 액터에 대해 context.sender 에는 해당 액터의 액터 참조가 포함됨.
case "Hi!" => sender() ! "Hello, there!" // 메시지에 대한 답장
case message: String => println(s"[$self] I have received $message") // 문자열
case number: Int => println(s"[simple actor] I have received a NUMBER: $number") // 숫자
case SpecialMessage(contents) => println(s"[simple actor] I have received something SPECIAL: $contents")
// self 참조를 사용하여 메시지 스스로에게 전달 가능
case SendMessageToYourself(content) =>
self ! content
case SayHiTo(ref) => ref ! "Hi!" // alice is being passed as the sender
case WirelessPhoneMessage(content, ref) => ref forward (content + "s") // i keep the original sender of the WPM
}
}
// 액터 내부구조 생성
val system = ActorSystem("actorCapabilitiesDemo")
val simpleActor = system.actorOf(Props[SimpleActor], "simpleActor")
simpleActor ! "hello, actor"
// 1 - 메시지는 어떤 타입이든 될 수 있다.
// a) 메시지는 immutable 해야한다.(불변)
// b) 메시지는 직렬화 가능해야한다. = 메시지를 바이트 스트림으로 변환하여 다른 JVM 으로 전송할 수 있다.
// 메시지를 주고받기 위해 주로 케이스 클래스와 케이스 객체를 사용함. 이는 대부분의 요구사항을 충족시켜줌
// 메시지를 전송할 때, Akka 는 해당 메시지 타입에 대한 객체를 찾음 (42)
simpleActor ! 42 // who is the sender?!
// special message 라는 케이스 클래스를 정의하면, 전송 가능함. 이를 위해 수신하는 쪽에서도 이 메시지를 지원하도록 해야함.
case class SpecialMessage(contents: String)
simpleActor ! SpecialMessage("some special content")
/**
2 - 액터는 자신과 그 주변 환경에 대한 정보를 갖고 있다.
각 액터는 context 를 갖고 있는데, 이는 액터가 실행되는 환경에 관한 정보를 참조하는 복잡한 데이터 구조이다.
context 는 액터가 실행되는 actor system 에 대한 접근 권한을 갖고 있으며 액터 참조에도 접근할 수 있다. 이것이 'self' 이다.
context.self === `this` in OOP
context.self => Actor[akka://actorCapabilitiesDemo/user/SimpleActor#-12345678]
위의 예시에서 SimpleActor : 이름, 12345678 : 식별자
context.self 에 접근할 수 있기 때문에, self 참조를 사용하여 메시지를 자신에게 전송할 수 있다.
SendMessageToYourself 이 메서드는 처음 받았을 때 위의 case 문에서 분해 되었고, self 는 메시지의 내용을 전달받았다.
!! 이 모든 메시지와 행동들은 완전히 블로킹 되지 않는 비동기방식으로 이루어짐 !!
*/
case class SendMessageToYourself(content: String)
simpleActor ! SendMessageToYourself("I am an actor and I am proud of it")
// 3 - 액터는 context 를 사용하여 메시지에 답할 수 있다.
val alice = system.actorOf(Props[SimpleActor], "alice")
val bob = system.actorOf(Props[SimpleActor], "bob")
/*
Akka 는 이처럼 행위자 참조를 사용하여 메시지를 보낼 행위자를 파악함
시스템 액터의 결과는 액터 참조이다
앨리스와 밥 사이에서, Sender 는 밥 이다.
액터가 다른 액터에게 메시지를 보낼 때, 그들은 스스로를 sender 로 전달한다.
일반적으로 self 를 생랼하는 이유는, self 가 암시적 값이기 때문이다.
*/
case class SayHiTo(ref: ActorRef)
alice ! SayHiTo(bob)
// 4 - dead letters
// dead lettters : Akka 내부의 가짜 액터, 어떤 사람에게도 보내지지 않은 메시지를 받는 역할
// 보내는 이가 없다면, 응답이 dead letters 로 감
alice ! "Hi!" // reply to "me"
// 5 - forwarding messages
// D -> A -> B
// forwarding = sending a message with the ORIGINAL sender
case class WirelessPhoneMessage(content: String, ref: ActorRef)
alice ! WirelessPhoneMessage("Hi", bob) // noSender.