본문 바로가기

언어/Java

Asynchronous RESTful API 서버 - AKKA

개요

저는 평소에 비동기 작업을 선호합니다. 예전에 게임 서버 작업 할때는 그래도 비동기로 작업 할 많은 기회가 있었는데요, 최근에는 웹 API 서버 작업 위주로 진행하다 보니까 비동기 REST API 서버 작업 하기가 쉽지가 않네요. 일단 장단이 있겠지만 기존 방식을 더 선호 하시는 분도 계시고 그래서 어쩌다 보니 일반 Spring에 컨트롤러로 작업 할 때가 많네요. 물론 Spring 에서도 5부터 webflux 기능을 제공해서 비동기로 작업을 할 수는 있지만 저희 회사에서는 많이 활용되고 있지는 않네요.

예전에 우연히 좋은 기회가 있어서 임백준님의 AKKA 관련 강의를 들었던 적이 있습니다. C++로 boost asio로 소켓 서버 작업 하던 저에게는 좋은 충격으로 다가왔고 접하게 되었고, 틈틈히 회사에서 또는 토이 프로젝트에서도 활용하고 있습니다. 참 좋다고 생각하는데 아직 많은 Java 개발자 분들이 활용하시지는 못해서 조금 아쉬운 마음에 글을 적기 시작했습니다. 시중에 책이 몇권 있긴 하지만 그래도 아직은 Spring 관련 내용이 아직 더 많은 것 같습니다.

서론이 조금 길었네요. 처음이니 간단히 시작하겠습니다. (재미를 보장 하지 못해서 죄송합니다.)

Index

Akka 란

아카가 구현한 액터 모델(actor model)은 오래전인 1973년에 칼 휴이트가 제안한 수학적 모델을 기초로 삼고 있다. 멀티스레딩 환경에서 동시성 프로그램을 작성하는 일이 점점 어려워지고, 한 대의 컴퓨터가 사용하는 CPU 코어의 수가 빠른 속도로 늘어나는 요즘에, 아카는 동시성 코드를 작성하기 위한 직관적이고 편리한 프로그래밍 모델을 제공한다. - 임백준의 아카 시작하기 : Akka 개념 잡기

뭐 자세한 내용은 액터 모델을 보시면 좋을것 같습니다.

제 생각으로 간단히 설명을 하자면, 기존에 객체지향적 프로그래밍에서는 객체를 만들고 함수를 만들어요, 그러면 우리는 그 객체를 가지고 있거나 객체 참조를 메모리에 가지고 있다가 함수를 호출합니다. (아래 코드 참조)

public class Car {
    public void run() {
        // do somthing
    }
}

public class Application {
    public static void main(String[] args) {
        final Car car = new Car();
        car.run();
    }
}

이때 멀티 스레드 환경에서는 이슈가 있습니다. 동시에 같은 함수를 호출할때 문제가 있을수 있습니다. 우리는 LocalThread로 객체를 생생해서 각 스레드에서 따로 처리 할 수 있습니다. 같이 관리 되어야 하는 부분은 데이터 베이스 처리 부분으로 넘길수 있습니다. 아니면 그 객체를 Concurrent로 생성해서 동시에 하나만 접속하게 될 수 있게 처리 할수도 있습니다. 그러면 이 메소드가 처리가 끝날때 까지는 다른 스레드에서는 처리 할수 없게 되고 블럭이 될겁니다. 뭐 그때마다 처리 하는 부분은 비동기로 처리할 수도 있겠습니다.

그런데 말입니다, Akka는 이런 아이디어를 가지고 있습니다.

public class CarActor extand AbstractActor {
    @Override
      public Receive createReceive() {
        return receiveBuilder()
            .match(Run.class, run -> { //do somthing })
            .build();
      }
}

public class Run {}

public class Application {
    final ActorSystem system = ActorSystem.create();
    final ActorRef firstRef = system.actorOf(Props.create(CarActor.class, CarActor::new), "first-actor");
    firstRef.tell(new Run(), ActorRef.noSender());
}

어떤 Car 라는 액터를 만들어요, 그리고 직접 run 메소드를 직접 호출하는게 아니라, run을 실행하라고 메시지로 알려줍니다. 그리고 Car 라는 액터는 그 메시지를 받고 일을 처리합니다. 어떻게 보면 기존 방식이랑 다를게 없습니다. Akka에서 액터 생성시 메시지를 처리하는 큐가 있습니다. 동시성 문제가 있지만 메시지에 메시지를 넣는 부분만 해당되기 때문에 훨씬 더 효율적인 스레드 활용을 할 수 있습니다. 그리고 보낸 액터 주소를 같이 보낼수 있기 때문에 Car 액터는 일을 처리하고 다시 일을 시킨 액터에서 메시지를 보내서 그 액터는 Car 액터의 일을 처리 완료 후에 일을 이어서 처리 할수 있습니다.

물론 그냥 정말 잘 짜여진 코드에서는 직접 객체를 지정해서 호출하는 방식이 더 효율이 좋을 수도 있습니다. 하지만 대다수 경우에는 이런 방식이 더 효율적이고, 우리는 컨텐츠 코드에 집중 할 수 있다고 생각합니다.