Actor로 설계된것은 원격으로 확장이 가능하기때문에 RemoteActor라는 객체는 존재하지 않습니다.
단지 로컬액터와 동일한 방식으로, 원격지 액터와 대화를 할수 있으며
직렬화,TCP 통신방식등은 몇가지 설정으로 변경가능하며 Akka에게 맞기면 됩니다.
사용준비조건
//메이븐 설정추가 <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-remote_2.11</artifactId> <version>${akka.version}</version> </dependency> //application.conf 설정 추가 akka { actor { provider = remote } remote { enabled-transports = ["akka.remote.netty.tcp"] netty.tcp { hostname = "0.0.0.0" port = 2552 } } } // 로컬로 설계된 메시지,직렬화 상속받기 public class SomeMessage implements Serializable { ....}
이미 작성된 AKKA 어플리케이션에 원격 메시지 기능을 부여하기위해서 3가지만 하면됩니다.
이것은 로컬에서의 액터사용법과 리모트에서의 사용법이 일치하다란 것입니다.
- 메이븐에 Remote추가 : netty를 비롯하여 원격통신에 필요한 몇가지 라이브러리가 포함됩니다.
- 원격포트 설정 : 하나의 통신 포트에 수십만개의 액터를 등록하여 관리할수 있습니다. 그리고 이것은 스레드가아닌 힙에관리되기때문에 가볍습니다.
- 로컬메시지 객체 직렬화하기 : 로컬에서만 작동가능한 메시지에 Serializable 상속을 받아, 단지 직렬화 가능한 객체를 전송을 하면됩니다.
컨셉
- 푸른색: 모든처리를 DB형님에게 알리고 묻는 전통적인 방식
- 붉은색: 피어투피어로 자신에게 일어난 일을 형제(A1 to A2) 또는 성격이 약간 다른 사촌간(B1 to A2) 에게도 묻고답하는것이 가능한구조
중앙집중형의 한계는 관리할 동생들이 너무많아지고, 지속적으로 추가됨에따라 전체에 발생하는 모든 트래픽처리를 감당하기 어렵다란 점입니다.
개선점으로 붉은색과 같이 TCP/IP 패킷설계를 통해 여러가지 실시간일들을 자기들끼리 처리할수 있는 루트를 뚫을수있고
이렇게 변환된 패킷처리에대해 고성능 작동이 가능하지만 리커넥트 문제를 비롯 구현및 관리가 어려워진다란 것입니다.
구현 단순화를 위해서 RestAPI로 전환할수도 있지만, 안정적으로 대량의 데이터를 고성능으로 전송하기에는 무리가 있습니다.
구현코드의 난이도를 올리지 않고 연결지향적이면서도 고성능통신을 할수 있는방법중 하나가
TCP 피어투피어방식을 사용하는 RemoteActor입니다.
일반적으로 재접속 처리 문제를 신경쓸필요가 없습니다. ( 연결복구기능이 기본 탑재되며, 필요하면 장애시 재전송룰 적용가능)
활용될수 있는 기능
- A1,A3에 일어난일을 A2에게 모두보고하여 로그를 집합할수 있습니다.
- B1에서 일어난 일을 A2에게 보고하고 DB형님이 안바쁠때 변경사항을 알려줄수 있습니다.(DB 호출량제어가능)
- 이미 존재하는 미들웨어에서 쌍반간 통신의 길을 뚫어줌으로, 관리요소추가(MQ시스템)없이 스스로 연락을 할수 있습니다.
- 라운드로빈/브로드캐스트등 특수한 라우티이용도가능하며, 클러스터화 전환도 용이합니다.
- 메시지기반으로 설계된 것은, RabbitMQ및 카프카와같은 외부 MQ 시스템과도 연동이 수월해집니다.
그럼 구현체가 얼마나 간단해졌는지 살펴보겠습니다.
서버 구현체
public class TestActor extends UntypedActor { private final LoggingAdapter log = Logging .getLogger(getContext().system(), "TestActor"); @Override public void onReceive(Object message) throws Exception { if(message instanceof String) { //String뿐만 아니라 모든 Java객체 통신가능 log.info("Incommessage {}", message); sender().tell("너의 메시지에 응답을함", ActorRef.noSender()); }else { log.info("Unhandle Message {}", message); } } } //service1 이름으로 액터의 엔드포인트를 생성했습니다. ActorRef testActor = system.actorOf(ext.props("testActor"),"service1"); //Service1이란 이름으로 TestActor를 생성함
사용측
//엔드포인트를 통한 액터선택자 ActorSelection testActorRemote = system.actorSelection("akka.tcp://app@localhost:2552/user/service1"); //리모트를 통한 전송 testActorRemote.tell("발사후망각-응답필요없음",ActorRef.noSender() ); //ActorRef에 수신녀석을 지정하여 어떠한 결과를 받을수도 있습니다. //응답이 동기적으로 필요한경우 CompletableFuture<Object> future1 = ask(testActorRemote, "응답하라 1979", 1000).toCompletableFuture(); //너의 메시지에 응답을함 이란 메시지를 받음 -이해를 돕기위해 동기처리로 전환 String result = String.valueOf(future1.get());
순수한 메시지 교환에서 RestAPI에 비해 성능은 비약적으로 상승한반면
푸시기능과 폴링기능 두가지 모두사용할수 있을뿐더러, 받을대상 큐(액터)를 다른놈으로 지정도 가능합니다. (포워드라고도 합니다.)
구현체는 Restapi방식과 비슷하거나 더 쉬워졌으며
JAVA의 비동기 객체(Future) 와 완벽하게 호환이 됩니다.
직렬화도 성능에 관련된 이슈이며 자바Object를 던지는 방법도 간단합니다.
SomeJavaObject someObj = new SomeJavaObject()
testActorRemote.tell(SomeJavaObject,null)
기본 직렬화를 사용하여 네트워크에 전달이 가능하며, 직렬화 라이브러리역시 선택이 가능합니다. (양측이 맞아야함)
그외 참고자료
- JAVA8-비동기처리 : https://doc.akka.io/docs/akka/2.5/futures.html#java-8-completionstage-and-completablefuture
- 리모트 액터 : https://doc.akka.io/docs/akka/2.5/remoting.html
- 고성능 직렬화 선택가능(설정없으면 기본으로 해줌) : https://doc.akka.io/docs/akka/2.5/serialization.html
원격메시지에대한 안정적인 공급/소비 문제를 해결하기위한 좋은 툴로 Kafka가 등장하였고 범용적으로 사용되고 있습니다.
하지만 모든 실시간 메시지를 중앙에서 분산관리하느냐? 로컬적인 메시지를 자기들끼리 알아서 처리하게하느냐?
목적성에 따라 장단점이 있는부분입니다.
리모트 액터로 Kafka와 같은 역활을 모두 대체시키는것은 권장되지 않으며,
모든 실시간 메시지를 Kafka를 통해서 하는것도 올바른 설계방법이 아닙니다.
액터로 설계된것은 Kafka와도 완벽하게 상호연동이 되기때문에 목적성에 맞게
잘구분하여 사용할 필요가 있습니다.
아래와 같은 차이가 있으며, RemoteActor와 Kafka 전송 난이도는 큰차이가 없으며
이기종통신일때 Kafka가 이점이 있으며, 같은 기종 Java객체 전달할때 RemoteActor가
조금더 설계의 이점이 있습니다.
실시간 메시지전송기는 추상화정도및 내장화가 되었냐에 따라 3가지로 구분이 가능합니다.
- netty를사 용하여 저수준 메시지전송 기능을 구현한다. ( 분산처리를 직접설계해야해서 어려움 )
- netty가 추상화된 RemoteActor를 사용하여 메시지 전송을 구현한다. (분산처리를 위해 AkkaCluster를 이용하고 내장형)
- kafka를 이용하여 메시지를 전송한다 ( 분산처리를 위해서 주키퍼의 클러스터 구성이 필요하며 외장형임)