주요글: 도커 시작하기
반응형
Akka가 관심을 끄는 이유는 사실 액터 모델 자체보다는 리모트 노드에 위치한 액터를 마치 로컬에 위치한 액터처럼 사용할 수 있다는 것이었다. Scala 언어가 자체적으로 액터를 제공하고 있지만, Akka의 액터는 이 액터 모델을 리모트까지 확장했기 때문에, Akka를 사용하면 한 노드에서의 병행 처리 뿐만 아니라 다수 노드에서의 병행 처리까지 쉽게 구현할 수 있다.

리모트 액터를 사용하기 위한 과정

리모트 액터를 사용하려면 다음의 과정을 거치면 된다.
  1. 리모트 서버를 만든다. 리모트 서버는 리모트로 제공될 액터를 관리하며, 클라이언트는 리모트 서버에 연결해서 리모트로 제공되는 액터를 사용하게 된다.
  2. 리모트 서버에 액터 등록하기 (클라이언트에서 액터 등록하기, 서버에서 액터 등록하기)

[주의]
클라이언트와 서버는 모두 액터에서 사용되는 클래스를 갖고 있어야 한다. 이후 버전에서는 클라이언트와 서버간의 코드 제공 기능이 포함될 거라고 한다.


단계1, 리모드 액터를 실행할 리모트 서버 만들기

액터를 외부 노드에 제공하고 싶다면, 먼저 클라이언트와의 연결을 처리할 서버를 생성해 주어야 한다. 서버는 다음의 코드를 이용해서 리모트 액터를 실행할 서버를 생성할 수 있다.

Actors.remote().start("0.0.0.0", 2552); // 모든 호스트에 대해 2552 포트로 들어오는 요청 처리


Actors.remote().start("localhost", 2553); // 로컬 호스트의 2553 포트로 들어오는 요청 처리


Actors.remote().start(); // 설정 파일에 있는 기본 값 사용 (설정 파일 없을 시 기본값은 "localhost", 2552)


start() 메서드에서 호스트 값으로 "localhost"를 지정하면, 로컬호스트로 들어오는 요청에 대해서만 처리할 수 있기 때문에, 실 환경에서는 의미가 없다. 실 환경에서는 "0.0.0.0"이나 "192.168.0.1"과 같이 전체 허용 또는 특정 호스트를 지정해 주는 것이 좋다.

단계2, 리모트 서버에 액터 생성하기

다음의 두 가지 방법을 이용해서 리모트 서버에서 액터를 실행할 수 있다.3
  • 클라이언트에서 생성/관리: 리모트 노드에 있는 액터를 클라이언트에서 관리해야 할 때 사용 (액터 모니터링, 액터 수퍼바이징 등)
  • 서버에서 생성/관리: 클라이언트에 액터 서비스만 제공하고 서버에서 액터에 대한 관리를 할 때 주로 사용한다.

클라이언트에서 원격지 서버에 액터 생성하기

다음의 코드를 사용하면 클라이언트에서 리모트 서버에 액터를 생성하고 관리할 수 있다.

ActorRef actor1 = Actors.remote().actorOf(MyActor.class, "172.20.1.11", 2552);
actor1.start();
actor1.sendOneWay("hello");
actor1.stop();

Actors.remote().actorOf() 메서드는 리모트 서버에 MyActor 타입의 액터를 생성한다. 클라이언트에서 액터를 생성한 경우 로컬 액터를 사용하듯이 start() 메서드를 이용해서 액터를 시작하고 stop() 메서드를 이용해서 액터를 종료할 수 있다.

클라이언트에서 리모트 서버에 액터를 생성할 때 주의할 점은 호스트와 포트가 "localhost"와 2552 이면, 리모트 액터가 아닌 로컬 액터로 생성해서 실행된다는 점이다.

서버에서 액터 생성해서 등록하기

서버에서 액터를 생성해서 클라이언트에 제공할 수도 있다. 서버에서 액터를 등록할 때에는 다음과 같이 register() 메서드를 사용하면 된다.

Actors.remote().start("0.0.0.0", 2552);
// MyActor를 리모트 액터로 등록, 식별값은 "hello-service"
Actors.remote().register("hello-service", Actors.actorOf(MyActor.class));

register() 메서드를 사용하면 액터는 자동으로 시작된다.

클라이언트는 액터의 식별값을 이용해서 리모트 액터에 대한 레퍼런스를 구할 수 있으며, 이 레퍼런스를 이용해서 리모트 액터에 메시지를 전달할 수 있다. actorFor() 메서드를 사용하면 리모트 노드에서 생성한 액터에 접근할 수 있다.

// 192.168.1.11:2553 포트로 실행중인 리모트 서버에 등록된 "hello-service" 액터 접근
ActorRef actor = Actors.remote().actorFor("hello-service", "192.168.1.11", 2553);
actor.sendOneWay("테스트!!!"); // 리모트 액터에 메시지 전달


리모트 액터에서 클라이언트에 응답하기

리모트 액터에서 클라이언트에 응답하는 방법은 앞서 'Akka 첫 번째, Akka를 이용한 Concurrent 프로그래밍 시작하기' 에서 살펴봤던 것과 동일하다.

로컬 액터에서 리모트 액터로 메시지를 전송하면, 리모트 액터는 getContext().getSender()를 이용해서 로컬 액터에 메시지를 전달할 수 있다. (즉, 리모트 액터 입장에서는 로컬 액터가 리모트 액터가 되는 것이다.) 리모트 액터에서 로컬 액터에 메시지를 전송할 때에도 결국 네트워크를 통해서 보내기 때문에, 클라이언트도 리모트 서버를 실행해야 리모트 액터에서 로컬 액터에 메시지를 전송할 수 있게 된다.

---- 클라이언트 코드

// 리모트 액터에서 로컬 액터에 메시지 보낼 때 사용할 서버 실행
Actors.remote().start("192.168.4.4", 2552);

ActorRef localActor = Actors.actorOf(LocalActor.class);
localActor.start();

ActorRef actor = Actors.remote().actorFor("hello-service", "192.168.4.3", 2552);
actor.sendOneWay("테스트!!!", localActor); // 로컬 액터를 sender로 지정

---- 리모트 액터 코드
public class MyActor extends UntypedActor {
   
    @Override
    public void onReceive(Object msg) throws Exception {
        if (getContext().getSender().isDefined()) {
            ActorRef sender = getContext().getSender().get(); // 클라이언트의 LocalActor가 sender
            sender.sendOneWay(msg); // 192.168.4.4:2552 로 메시지 전송
        }
    }
}

클라이언트 코드에서 리모트 서버를 실행하지 않으면, 리모트 액터가 로컬 액터에 메시지를 전달할 수 없게 된다. 즉, 서로 다른 노드에 있는 액터들 간에 메시지를 주고 받기 위해서는 각 노드마다 리모트 서버를 실행시켜 주어야 한다.

비신뢰 모드(UntrustedMode)로 리모트 서버 실행하기

리모트 서버를 비신뢰 모드로 실행하게 되면, 클라이언트에서 액터를 생성할 수 없게 된다. 리모트 서버를 비신뢰 모드로 실행하려면 설정 파일에 다음과 같이 untrusted-mode 값을 on으로 설정해 주면 된다.

akka {
    remote {
        server {
            untrusted-mode = on  # 기본 값은 off
        }
    }
}

비신뢰 모드로 실행하면 클라이언트에서 리모트 액터에 대해 다음의 메서드에 대한 호출이 제한된다.
  • start(), stop(), link(), unlink(), spawnLink() 등

리모트 서버와 클라이언트 종료 처리

아래의 클라이언트 코드를 실행하면 JVM이 종료되지 않고 실행된 채로 남아 있는다. 이유는 Akka가 내부적으로 리모트 서버와의 연결 처리를 위해 사용하는 쓰레드가 죽지 않기 때문이다.

public class Client {

    public static void main(String[] args) {
        ActorRef actor = Actors.remote().actorFor("hello-service", "172.20.4.64", 2553);
        actor.sendOneWay("테스트!!!");
        // JVM 종료되지 않음
    }
}

shutdown() 메서드를 이용해서 리모트 서버와의 연결을 종료시키고 관련된 모든 쓰레드를 함께 종료시켜주므로, 클라이언트 코드에서 리모트 액터에 대한 사용이 끝나면 shutdown() 메서드를 호출해서 JVM을 종료처리할 수 있다.

ActorRef actor = Actors.remote().actorFor("hello-service", "172.20.4.64", 2553);
// actor 사용
Actors.remote().shutdown(); // 프로그램 종료시 반드시 실행해 주어야 함

리모트 서버에서도 마찬가지로, 어플리케이션을 종료처리할 때 shutdown() 메서드를 호출해 주어야 관련된 쓰레드가 모두 정리되어 JVM이 종료하게 된다.

Actors.remote().start("0.0.0.0", 2553);
...
Actors.remote().shutdown(); // 프로그램 종료시 반드시 실행해 주어야 함


이벤트 처리

클라이언트와 서버는 액터를 이용해서 클라이언트의 연결/해제 등의 이벤트를 수시할 수 있다. 리모트 기능과 관련된 이벤트를 처리하고 싶다면 다음과 같이 이벤트를 수신할 액터를 Actors.remote().addListener() 메서드를 이용해서 이벤트 리스너로 등록해주면 된다.

ActoreRef listener = Actors.actorOf(ListenerActor.class);
listener.start();

Actors.remote().addListener(listener);

리스너로 사용되는 액터에는 리모트 기능과 관련된 이벤트 객체가 메시지로 전달되며, 액터는 이벤트 타입에 따라서 알맞은 작업을 수행하면 된다.

public class ListenerActor extends UntypedActor {

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof RemoteServerStarted) {
            ...
        }
    }

}

주요 이벤트 클래스는 다음과 같다.
  • 서버측 이벤트
    • RemoteServerStarted
    • RemoteServerShutdown
    • RemoteServerClientConnected
    • RemoteServerClientDisconnected
    • RemoteServerClientClosed
    • RemoteServerWriteFailed
  • 클라이언트측 이벤트
    • RemoteClientConnected
    • RemoteClientDisconnected
    • RemoteClientStarted
    • RemoteClientShutdown
    • RemoteClientError
    • RemoteClientWriteFailed

참고자료

+ Recent posts