Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  • "hello" 메시지를 받으면 sender() ! "world"로 응답을 보냄

  • ask 패턴(?)을 사용하여 응답을 Future로 받음

  • Await.result로 블로킹 방식으로 응답을 출력



자바

Info

자바7의 java.util.concurrent.Future에 익숙하다면 scala.concurrent.Future가 자바 클래스를 감싼것으로 생각할수도 있지만

실제로는 그렇지 않다. java.util.concurrent.Future 클래스는 폴링을 필요로하며 결과를 얻기위해 블로킹 get 메서드를 사용해야만

한다. 하지만 스칼라의 퓨쳐는 블로킹이나 풀링을 사용하지 않고 함수결과를 조합할수 있으며

JAVA8의 CompletetableFuture<T>가 오히려 여기서 설명하는 퓨쳐와 유사하다.


Erik Meijer의 한마디~

"이보게, 브라이언 괴츠, C#,파이선,자바스크립트는 물론 심지어 PHP도 async, await를 지원하고 있다네. 그런 기능이 없는 언어는 자바일뿐이야.

람다를 이용해서 콜백함수를 사용하면 된다고? 천만에 콜백은 최악이야. 도움이 안된다고. 자바 9 버전에 담으려고 하는 걸 다 내려놓고 지금당장

asymc, await부터 넣으라고. 그래야 모두가 행복해질수 있어"


제대로된 비동기 동시성처리는  Java8부터 지원하기 시작했으며 Java언어 순수스펙에서는  await를 프로그래밍 모델을 지원하지 않기때문에 액터모델의

Ask패턴은 비동기처리 완료인 CompelteableFuture를 이용합니다. 

Code Block
themeEmacs
public class HelloActor extends AbstractActor {

    // 액터 생성 팩토리
    public static Props props() 
Code Block
themeEmacs
public class HelloActor extends AbstractActor {

    // 액터 생성 팩토리
    public static Props props() {
        return Props.create(HelloActor.class);
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .matchEquals("hello", msg -> {
                getSender().tell("world", getSelf());
            })
            .matchAny(msg -> {
                System.out.println("Unknown message: " + msgreturn Props.create(HelloActor.class);
    }

    @Override
    })
public Receive createReceive() {
        return .buildreceiveBuilder();
    }
}

public class HelloActorApp {
    public static void main(String[] args) throws Exception.matchEquals("hello", msg -> {
        ActorSystem system = ActorSystem.create("HelloSystem");

        ActorRef helloActor = system.actorOf(HelloActor.props(), "helloActor"getSender().tell("world", getSelf());

        // 메시지 보내고 응답 기다리기})
        Future<Object>  future = Patterns.askmatchAny(helloActor, "hello", 5000);msg -> {
        String result = (String) Await.result(future, Duration.create(5, TimeUnit.SECONDS));

        System.out.println("응답Unknown message: " + resultmsg); // "world"


            })
            system.terminatebuild();
    }
}

닷넷

Code Block
themeEmacs
public class HelloActor : ReceiveActor
public class HelloActorApp {
    public static void HelloActormain(String[] args)
 throws Exception  {
        Receive<string>(msg =>ActorSystem system = ActorSystem.create("HelloSystem");

        {
ActorRef helloActor = system.actorOf(HelloActor.props(), "helloActor");

        if (msg == "hello")
// 메시지 보내고 응답 기다리기
        Future<Object> future = Patterns.ask(helloActor,  {"hello", 5000);
        String result = (String) Await.result(future,    Sender.Tell("world"Duration.create(5, TimeUnit.SECONDS));

            }System.out.println("응답: " + result); // "world"

        system.terminate();
    }
}



닷넷

Code Block
themeEmacs
public class HelloActor : ReceiveActor
{
else
        public HelloActor()
    {
        Receive<string>(msg =>
       Console.WriteLine($"Unknown message: {msg}"); {
            }
if (msg       });== "hello")
    }
}

class Program
{
    static async Task Main(string[] args) {
    {
        using var system = ActorSystemSender.CreateTell("HelloSystemworld");

          var helloActor = system.ActorOf<HelloActor>("helloActor");

}
            else
 // Ask 패턴으로 메시지 보내고 응답 받기
     {
   var response = await helloActor.Ask<string>("hello", TimeSpan.FromSeconds(5));

        Console.WriteLine($"응답Unknown message: {responsemsg}");
 // "world"

          await system.Terminate(); }
        }
}

코틀린 AKKA 버전

Code Block
themeEmacs
class HelloActor : AbstractActor() {

    override fun createReceive(): Receive {
);
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        using return receiveBuilder()var system = ActorSystem.Create("HelloSystem");

        var helloActor =  system.matchEqualsActorOf<HelloActor>("hellohelloActor") {
    ;

        // Ask 패턴으로 메시지 보내고 응답 받기
        var response = await senderhelloActor.tellAsk<string>("worldhello", self)
TimeSpan.FromSeconds(5));

        Console.WriteLine($"응답: {response}");   }// "world"

        await system.Terminate();
    }
}


무거운 작업을 액터실행 중단없이 실행하고 결과를 나중에 수신받기

Code Block
themeEmacs
using .matchAny {
     Akka.Actor;
using Akka.Pattern;
using System;
using System.Threading.Tasks;

public class PipelinedActor : ReceiveActor
{
    public PipelinedActor()
    {
  println("Unknown message: $it")
      Receive<string>(msg =>
        }{
            if .build((msg == "hello")
    }

      companion object {
        fun props(): Props = Props.create(HelloActor::class.java)
    }
}

fun main() {
        Console.WriteLine($"[{Self.Path.Name}] 처리 시작: {msg}");

         val system = ActorSystem.create("HelloSystem")
    val// helloActor무거운 =작업 system.actorOf(HelloActor.props(), "helloActor")

(예: DB 조회, 외부 API 등) 을 비동기로 시작
      val future = Patterns.ask(helloActor, "hello", 5000)
        valvar resultlongTask = AwaitTask.resultRun(future,async Duration.create(5, TimeUnit.SECONDS)) as String

() =>
            println("응답: $result") // world 출력{

       system.terminate()
}

코틀린 액터 코어버전

코틀린은 액터모델을 채택했기때문에 서드파티툴없이 액터모델 이용가능합니다. 

(akka,pekko  이용시 액터모델이 스트림,클러스터로 확장되는 툴은없기때문에 직접 구현해야함) 

Code Block
themeEmacs
import kotlinx.coroutines.channels.actor

// 메시지 정의
sealed class Message
data class Greet(val replyTo: CompletableDeferred<String>) : Message
object Unknown : Message

 await Task.Delay(1000); // 액터 함수
fun CoroutineScope.helloActor() = actor<Message> {
1초 걸리는 비동기 작업
         for (msg in channel) {
       return when"world (msg) {
from async)";
             is Greet -> msg.replyTo.complete("world") });

            else -> println("Unknown message received: $msg")
     // 작업 완료 시, 결과를 Sender에게 전달
   }
    }
}

fun main() = runBlocking {
    val actor = helloActor()

 longTask.PipeTo(Sender, Self);
     val response = CompletableDeferred<String>()
    actor.send(Greet(response))

}
     println("응답: ${response.await()}") // 출력: world

    actor.close()
}

파이썬 Ray기반

Code Block
themeEmacs
// hello_actor.py
import ray

# Ray 초기화
ray.init()

# 액터 클래스 정의
@ray.remote
class HelloActor:
    def say(self, msg):
       else
            {
             if msg == Sender.Tell("hellounknown":);
            return "world"}
        else:});
    }

    public static Props Props() return f"Unknown message: {msg}"

// main.py
import ray
from hello_actor import HelloActor

ray.init(ignore_reinit_error=True)  # 이미 실행 중일 경우 무시

# 액터 생성
actor = HelloActor.remote()

# 메시지 전송 및 응답 받기
response = ray.get(actor.say.remote("hello"))
print(f"응답: {response}")  # 출력: world

# 종료
ray.shutdown()

Erlang(언랭) 

=> Akka.Actor.Props.Create(() => new PipelinedActor());
}

  • FireAndgot 으로도 불리며, 유도미사일을 쏜후, 이동전략이 가능해짐에 따라 군사전략의 큰 획기전 변화의 모티브로한 컨셉


코틀린 AKKA 버전

Code Block
themeEmacs
class HelloActor : AbstractActor() {

    override fun createReceive(): Receive {
        return receiveBuilder()
            .matchEquals("hello") {
                sender.tell("world", self)
            }
     
Code Block
themeEmacs
-module(hello_actor).
-export([start/0, loop/0]).

start() ->
    spawn(fun loop/0).

loop() ->
    receive
       .matchAny {From, "hello"} ->

              From ! "world",
            loop();
        {From, Msg} ->
            From ! {error, "Unknown message: " ++ Msg},
   println("Unknown message: $it")
            }
            .build()
    }

    companion object {
        fun props(): Props = Props.create(HelloActor::class.java)
    }
}

fun main() {
    val system = ActorSystem.create("HelloSystem")
    val helloActor = system.actorOf(HelloActor.props(), "helloActor")

    val future = Patterns.ask(helloActor, "hello", 5000)
    val result = Await.result(future, Duration.create(5, TimeUnit.SECONDS)) as String

    println("응답: $result") // world 출력

    system.terminate()
}



코틀린 액터 코어버전

코틀린은 액터모델을 채택했기때문에 서드파티툴없이 액터모델 이용가능합니다. 

(akka,pekko  이용시 액터모델이 스트림,클러스터로 확장되는 툴은없기때문에 직접 구현해야함) 

Code Block
themeEmacs
import kotlinx.coroutines.channels.actor

// 메시지 정의
sealed class Message
data class Greet(val replyTo: CompletableDeferred<String>) : Message
object Unknown : Message

// 액터 함수
fun CoroutineScope.helloActor() = actor<Message> {
    for (msg in channel) {
        when (msg) {
            is Greet -> msg.replyTo.complete("world")
            else -> println("Unknown message received: $msg")
        }
    }
}

fun main() = runBlocking {
    val actor = helloActor()

    val response = CompletableDeferred<String>()
    actor.send(Greet(response))

    println("응답: ${response.await()}") // 출력: world

    actor.close()
}



파이썬 Ray기반

Code Block
themeEmacs
// hello_actor.py
import ray

# Ray 초기화
ray.init()

# 액터 클래스 정의
@ray.remote
class HelloActor:
    def say(self, msg):
        if msg == "hello":
            return "world"
        else:
            return f"Unknown message: {msg}"

// main.py
import ray
from hello_actor import HelloActor

ray.init(ignore_reinit_error=True)  # 이미 실행 중일 경우 무시

# 액터 생성
actor = HelloActor.remote()

# 메시지 전송 및 응답 받기
response = ray.get(actor.say.remote("hello"))
print(f"응답: {response}")  # 출력: world

# 종료
ray.shutdown()



Erlang(언랭) 

Code Block
themeEmacs
-module(hello_actor).
-export([start/0, loop/0]).

start() ->
    spawn(fun loop/0).

loop() ->
    receive
        {From, "hello"} ->
            From ! "world",
            loop();
        {From, Msg} ->
            From ! {error, "Unknown message: " ++ Msg},
            loop()
    end.


1> c(hello_actor). % 컴파일
2> Pid = hello_actor:start().  % 액터 시작
3> Pid ! {self(), "hello"}.    % 메시지 전송
4> flush().                    % 응답 확인

Shell output:
Shell got "world"

장점 (Erlang식 액터 모델 특징)

  • Lightweight 프로세스 (수십만 개 생성 가능)

  • 모든 액터는 메시지 기반으로 통신

  • 고장 격리 (let it crash 철학)

  • OTP 사용 시 supervision, 상태 관리, 분산까지 매우 쉬움



AKKA의 다양한 장치를 이용 액터기능을 확장하기

코틀린 버전 AkkaStream을 활용해 BackPresure를 포함 TPS제어

Code Block
themeEmacs
class HelloActorStreamTest {

    companion object {
        private val system = ActorSystem.create("TestSystem")
        private val materializer = ActorMaterializer.create(system)

        @JvmStatic
        @AfterClass
        fun teardown() {
            TestKit.shutdownActorSystem(system)
        }
    }

    @Test
    fun `should throttle and respond with world`() {
        object : TestKit(system) {
            init {
                val helloActor = system.actorOf(HelloActor.props())

                // Source.queue + throttle + ask pattern
                val (queue, done) = Source.queue<String>(10, OverflowStrategy.backpressure())
                    .throttle(3, JDuration.ofSeconds(1)) // 초당 3건
                    .mapAsync(1) { msg ->
                        akka.pattern.Patterns.ask(helloActor, msg, 1000).thenApply { it as String }
                    }
                    .toMat(Sink.foreach { response ->
                        println("응답 수신: $response")
                        expectMsgEquals("world")  // 테스트 검증
                    }, Keep.both())
                    .run(materializer)

                // 메시지 전송 (10건)
                for (i in 1..10) {
                    queue.offer("hello")
                }

                // 일부 응답 대기
                within(Duration.create(5, "seconds")) {
                    // 최소 3건은 들어왔는지 확인
                    for (i in 1..3) {
                        expectNoMessage()
                    }
                }
            }
        }
    }
}


액터를 라우터를 구성후 순차 라운드로빈처리

Code Block
themeEmacs
                    +------------------+
                    |   Router (RoundRobinPool)   |
                    +------------------+
                    /        |        \
          HelloActor1  HelloActor2  HelloActor3

Code Block
themeEmacs
fun main() {
    val system = ActorSystem.create("RoundRobinSystem")

    // RoundRobinPool로 HelloActor 3개 구성
    val router = system.actorOf(
        RoundRobinPool(3).props(HelloActor.props()),
        "helloRouter"
    )

    println("=== 메시지 전송 시작 ===")
    for (i in 1..9) {
        val future = Patterns.ask(router, "hello $i", 1000)
        val result = Await.result(future, Duration.create(1, TimeUnit.SECONDS))
        println("응답 $i: $result")
    }

    system.terminate()
}

// Output
[helloRouter/$a] received: hello 1
응답 1: world
[helloRouter/$b] received: hello 2
응답 2: world
[helloRouter/$c] received: hello 3
응답 3: world
[helloRouter/$a] received: hello 4
응답 4: world
...



테스트 가능한 다양한 액터 장치들

Info

테스트 코드를 통해 작동방법을 설명하고 수행합니다. 액터모델을 통해 다양한 장치를 이용해 작동하는 유용한 샘플코드를 살펴볼수 있습니다.

최근 코틀린과 AKKA오픈버전은 Pekko를 조합하여 코프링내에서 액터모델 탑재를 시도하고 있습니다.


Image Added


🎯 Akka의 분산처리 단순화 전략

1. ✅ 액터 모델 기반 격리 (Actor Isolation)

  • 모든 상태와 처리를 액터 단위로 캡슐화

  • 동시성과 병렬성을 락(lock) 없이 처리

  • 분산에서도 동일한 추론 모델 사용 → 코드를 분산으로 쉽게 확장 가능

...

2. ✅ 클러스터링 지원 (Akka Cluster)

  • 여러 노드를 하나의 논리적인 ActorSystem처럼 구성

  • 노드 간 자동 등록, 탈퇴, 장애 감지, 재할당 등 제공

  • 메시지를 보내는 방식은 단일 노드와 거의 동일

Code Block
themeEmacs
[Node1]  ↔  [Node2]  ↔  [Node3]
   │         loop()
    end.


1> c(hello_actor). % 컴파일
2> Pid = hello_actor:start().  % 액터 시작
3> PidHelloActor ! {self(), "hello"}.    % 메시지 전송
4> flush().                    % 응답 확인

Shell output:
Shell got "world"

장점 (Erlang식 액터 모델 특징)

...

Lightweight 프로세스 (수십만 개 생성 가능)

...

모든 액터는 메시지 기반으로 통신

...

고장 격리 (let it crash 철학)

HelloActor HelloActor

3. ✅ Cluster Sharding

  • 수천~수백만 개의 액터를 자동으로 클러스터 노드에 분산 배치

  • 액터 위치를 몰라도 메시지 전송 가능 (shardRegion ! msg)

  • 데이터/처리를 분산하고, 장애 복구 시 자동 재배치

...

4. ✅ Cluster Singleton

  • 클러스터 전체에서 단 하나의 액터 인스턴스만 유지

  • 중요한 상태나 리더 역할을 담당하는 서비스 구성에 사용

...

5. ✅ 메시지 기반 통신 (Location Transparency)

  • actorSelection이나 ActorRef를 통해 위치를 몰라도 메시지 전송 가능

  • 지역(local)과 원격(remote)의 코드 차이를 최소화

...

6. ✅ Supervision & Fault Tolerance

  • 각 액터가 자식 액터의 실패를 감시 (let it crash)

  • 장애 격리와 자동 복구, 특히 분산 환경에서 안정성 확보에 강함

...

7. ✅ Persistence + Event Sourcing (선택적)

  • 상태를 이벤트 로그로 저장하여 분산 시스템의 상태 일관성 확보

  • 스냅샷, 이벤트 재생 등으로 빠른 복원 가능

...

🧠 한 줄 요약

"Akka는 복잡한 분산 처리 시스템을 액터라는 추상화된 단위로 나누고, 이들을 클러스터로 자동 연결하여 단일 시스템처럼 작동하게 만든다."

...



액터프로그래밍 활용사례

1. Scala: Akka 프레임워크

...


추가참고자료