Akka Actor Model

fana·2022년 11월 15일
0

Actor Model

  • OOP에서 “모든 것은 객체다”라고 하는 것 처럼, 액터 모델에서는 “모든 것은 액터다”라는 기본 철학을 가지고 있는 것
  • 액터
    • 다른 액터에게 유한한 개수의 메시지를 보낼 수 있다.
    • 유한한 개수의 새로운 액터를 만들 수 있다.
    • 다른 액터가 받을 메시지에 수반될 행동(behavior)을 지정할 수 있다.
  • 이런 액터 모델을 구현한 라이브러리중 대표적으로 Akka가 있다.
    • Akka Actor Model은 동시성 및 분산 시스템을 개발하는데 고수준의 추상화를 제공해준다
      • Multi-threaded behavior without the use of low-level concurrency constructs like atomics or locks — relieving you from even thinking about memory visibility issues.
      • Transparent remote communication between systems and their components — relieving you from writing and maintaining difficult networking code.
      • A clustered, high-availability architecture that is elastic, scales in or out, on demand — enabling you to deliver a truly reactive system.

Example

Defining Actors and messages

  • 각 액터는 타입 T 를 받는 메시지 타입을 정의한다.
  • 이 예시에서는 세가지 메시지를 정의한다.
    • Greet: command sent to the Greeter actor to greet
    • Greeted: reply from the Greeter actor to confirm the greeting has happend
    • SayHello: command to the GreeterMain to start the gretting process
  • 액터의 메시지를 정의할 때 다음 사항들을 기억할 것
    • 메시지는 액터의 Public API 이므로, 매서드 이름을 semantic하게 잘 짓는게 좋다. 단순 데이터를 래핑하는 것일 뿐이더라도 액터 베이스 시스템을 디버깅하거나 알아보기 쉽게 해주기 때문
    • 메시지는 스레드간 공유되므로 immutable 해야한다.
    • It is a good practice to put an actor’s associated messages as static classes in the AbstractBehavior’s class. This makes it easier to understand what type of messages the actor expects and handles.
    • It is a good practice obtain an actor’s initial behavior via a static factory method

The Greeter actor

public class Greeter extends AbstractBehavior<Greeter.Greet> {

  // Greeter가 주고 받는 메시지 타입 정의

  // Greeter가 받는 Greet 메시지 타입
  // inner static class
  public static final class Greet {
    public final String whom;
    public final ActorRef<Greeted> replyTo; // 누구에게 받았는지

    public Greet(String whom, ActorRef<Greeted> replyTo) {
      this.whom = whom;
      this.replyTo = replyTo;
    }
  }

  // Greeter가 Greet받았음을 확인해줄 때 보내는 Greeted 메시지 타입
  public static final class Greeted {
    public final String whom;
    public final ActorRef<Greet> from;

    public Greeted(String whom, ActorRef<Greet> from) {
      this.whom = whom;
      this.from = from;
    }

  }

  // 메시지 Greet을 만드는 팩토리 매서드
  public static Behavior<Greet> create() {
    return Behaviors.setup(Greeter::new);
  }

  // private constructor
  private Greeter(ActorContext<Greet> context) {
    super(context);
  }

  // public API
  @Override
  public Receive<Greet> createReceive() {
    return newReceiveBuilder().onMessage(Greet.class, this::onGreet).build();
  }

  // 실제 행동
  private Behavior<Greet> onGreet(Greet command) {
    getContext().getLog().info("Hello {}!", command.whom);
    command.replyTo.tell(new Greeted(command.whom, getContext().getSelf()));
    return this;
  }
}

The Greeter bot actor

package $package$;

import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.*;

public class GreeterBot extends AbstractBehavior<Greeter.Greeted> {

    public static Behavior<Greeter.Greeted> create(int max) {
        return Behaviors.setup(context -> new GreeterBot(context, max));
    }

	// 얘는 상태를 가지고 있는데 concurrnecy guard ex) synchronized or AtomicInteger가 없음
	// 액터 인스턴스는 한번에 하나의 메시지만 처리하기 때문
    private final int max;
    private int greetingCounter;
		
	// constructor
    private GreeterBot(ActorContext<Greeter.Greeted> context, int max) {
        super(context);
        this.max = max;
    }

    @Override
    public Receive<Greeter.Greeted> createReceive() {
        return newReceiveBuilder().onMessage(Greeter.Greeted.class, this::onGreeted).build();
    }

    private Behavior<Greeter.Greeted> onGreeted(Greeter.Greeted message) {
        greetingCounter++;
        getContext().getLog().info("Greeting {} for {}", greetingCounter, message.whom);
        if (greetingCounter == max) {
            return Behaviors.stopped();
        } else {
            message.from.tell(new Greeter.Greet(message.whom, getContext().getSelf()));
            return this;
        }
    }
}

The Greeter main actor

  • 메인 액터는 GreeterGreeterBot이 인터렉션을 시작하게 만들어 준다.
package $package$;

import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.*;

public class GreeterMain extends AbstractBehavior<GreeterMain.SayHello> {

	// GreeterMain이 받는 메시지 타입
    public static class SayHello {
        public final String name;

        public SayHello(String name) {
            this.name = name;
        }
    }

    private final ActorRef<Greeter.Greet> greeter;

	// 가디언 액터 엔트리포인트
    public static Behavior<SayHello> create() {
        return Behaviors.setup(GreeterMain::new);
    }

    private GreeterMain(ActorContext<SayHello> context) {
        super(context);
		// 생성자에서 Greeter를 spawn함
        greeter = context.spawn(Greeter.create(), "greeter");
    }

    @Override
    public Receive<SayHello> createReceive() {
        return newReceiveBuilder().onMessage(SayHello.class, this::onSayHello).build();
    }

    private Behavior<SayHello> onSayHello(SayHello command) {
        //#create-actors
        ActorRef<Greeter.Greeted> replyTo =
                getContext().spawn(GreeterBot.create(3), command.name);
        greeter.tell(new Greeter.Greet(command.name, replyTo));
        //#create-actors
        return this;
    }
}
  • Akka에서는 액터 인스턴스를 new로 만들 수 없다.
  • 대신 팩토리 매서드인 spawn 으로 만들 수 있는데 이 매서드는 액터를 리턴하지 않고 reference를 준다(akka.actor.typed.ActorRef)

The Akka ActorSystem

  • ActorSystem 이 Akka의 엔트리포인트가 된다. 보통 어플리케이션당 하나의 ActorSystem 이 생성된다.(guardian actor 라고도 하는듯)
  • 이 예제에서 ActorSystem 의 가디언 액터는 GreeterMain 이다
    final ActorSystem<GreeterMain.SayHello> greeterMain = ActorSystem.create(GreeterMain.create(), "helloakka");

참고

0개의 댓글