스칼라
import akka.actor.{Actor, ActorSystem, Props} // 액터 정의 class HelloActor extends Actor { def receive: Receive = { case "hello" => sender() ! "world" case msg => println(s"Unknown message: $msg") } } // 메인 또는 테스트 앱 object HelloActorApp extends App { // 액터 시스템 생성 val system = ActorSystem("HelloSystem") // 액터 인스턴스 생성 val helloActor = system.actorOf(Props[HelloActor], name = "helloActor") // 응답을 기다릴 임시 액터 생성 import akka.pattern.ask import akka.util.Timeout import scala.concurrent.duration._ import scala.concurrent.Await import scala.concurrent.ExecutionContext.Implicits.global implicit val timeout: Timeout = Timeout(5.seconds) // 메시지 전송 및 응답 대기 val future = helloActor ? "hello" val result = Await.result(future, timeout.duration) println(s"응답: $result") // "world" 출력 // 종료 system.terminate() }
주요 설명:
"hello"
메시지를 받으면sender() ! "world"
로 응답을 보냄ask
패턴(?
)을 사용하여 응답을 Future로 받음Await.result
로 블로킹 방식으로 응답을 출력
자바
자바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를 이용합니다.
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: " + msg); }) .build(); } } public class HelloActorApp { public static void main(String[] args) throws Exception { ActorSystem system = ActorSystem.create("HelloSystem"); ActorRef helloActor = system.actorOf(HelloActor.props(), "helloActor"); // 메시지 보내고 응답 기다리기 Future<Object> future = Patterns.ask(helloActor, "hello", 5000); String result = (String) Await.result(future, Duration.create(5, TimeUnit.SECONDS)); System.out.println("응답: " + result); // "world" system.terminate(); } }
닷넷
public class HelloActor : ReceiveActor { public HelloActor() { Receive<string>(msg => { if (msg == "hello") { Sender.Tell("world"); } else { Console.WriteLine($"Unknown message: {msg}"); } }); } } class Program { static async Task Main(string[] args) { using var system = ActorSystem.Create("HelloSystem"); var helloActor = system.ActorOf<HelloActor>("helloActor"); // Ask 패턴으로 메시지 보내고 응답 받기 var response = await helloActor.Ask<string>("hello", TimeSpan.FromSeconds(5)); Console.WriteLine($"응답: {response}"); // "world" await system.Terminate(); } }
무거운 작업을 액터실행 중단없이 실행하고 결과를 나중에 수신받기
using Akka.Actor; using Akka.Pattern; using System; using System.Threading.Tasks; public class PipelinedActor : ReceiveActor { public PipelinedActor() { Receive<string>(msg => { if (msg == "hello") { Console.WriteLine($"[{Self.Path.Name}] 처리 시작: {msg}"); // 무거운 작업 (예: DB 조회, 외부 API 등) 을 비동기로 시작 var longTask = Task.Run(async () => { await Task.Delay(1000); // 1초 걸리는 비동기 작업 return "world (from async)"; }); // 작업 완료 시, 결과를 Sender에게 전달 longTask.PipeTo(Sender, Self); } else { Sender.Tell("unknown"); } }); } public static Props Props() => Akka.Actor.Props.Create(() => new PipelinedActor()); }
- FireAndgot 으로도 불리며, 유도미사일을 쏜후, 이동전략이 가능해짐에 따라 군사전략의 큰 획기전 변화의 모티브로한 컨셉
코틀린 AKKA 버전
class HelloActor : AbstractActor() { override fun createReceive(): Receive { return receiveBuilder() .matchEquals("hello") { sender.tell("world", self) } .matchAny { 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 이용시 액터모델이 스트림,클러스터로 확장되는 툴은없기때문에 직접 구현해야함)
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기반
// 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(언랭)
-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제어
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() } } } } } }
액터를 라우터를 구성후 순차 라운드로빈처리
+------------------+ | Router (RoundRobinPool) | +------------------+ / | \ HelloActor1 HelloActor2 HelloActor3
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 ...
테스트 가능한 다양한 액터 장치들
테스트 코드를 통해 작동방법을 설명하고 수행합니다. 액터모델을 통해 다양한 장치를 이용해 작동하는 유용한 샘플코드를 살펴볼수 있습니다.
최근 코틀린과 AKKA오픈버전은 Pekko를 조합하여 코프링내에서 액터모델 탑재를 시도하고 있습니다.
🎯 Akka의 분산처리 단순화 전략
1. ✅ 액터 모델 기반 격리 (Actor Isolation)
모든 상태와 처리를 액터 단위로 캡슐화
동시성과 병렬성을 락(lock) 없이 처리
분산에서도 동일한 추론 모델 사용 → 코드를 분산으로 쉽게 확장 가능
2. ✅ 클러스터링 지원 (Akka Cluster)
여러 노드를 하나의 논리적인 ActorSystem처럼 구성
노드 간 자동 등록, 탈퇴, 장애 감지, 재할당 등 제공
메시지를 보내는 방식은 단일 노드와 거의 동일
[Node1] ↔ [Node2] ↔ [Node3] │ │ │ HelloActor 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 프레임워크
Akka는 Scala와 Java에서 사용 가능한 강력한 액터 기반 프레임워크로, 분산 시스템과 동시성 처리를 위한 도구를 제공합니다.
적용 사례:
LinkedIn: Akka를 활용하여 실시간 데이터 처리를 개선하고, 대규모 메시징 시스템의 성능을 향상시켰습니다.
Lightbend: Akka의 개발사로, 자체 제품과 서비스에 Akka를 적극 활용하여 고성능 애플리케이션을 구축하고 있습니다.
2. Java: Akka 및 Spring Integration
Java 진영에서는 Akka 외에도 Spring Integration과 같은 프레임워크를 통해 액터 모델의 개념을 적용하고 있습니다.
적용 사례:
Credit Karma: Akka를 사용하여 마이크로서비스 아키텍처를 구축하고, 사용자에게 실시간 신용 점수 업데이트를 제공하고 있습니다.
Netflix: Spring Integration을 활용하여 마이크로서비스 간의 메시지 기반 통신을 구현하고, 서비스 간의 결합도를 낮추는 데 기여했습니다.
3. C#: Akka.NET
Akka.NET은 .NET 환경에서 Akka의 기능을 제공하는 프레임워크로, C#과 F#에서 사용 가능합니다.
적용 사례:
Petabridge: Akka.NET의 주요 개발사로, 분산 시스템 구축을 위한 도구와 서비스를 제공하며, 자체 프로젝트에 Akka.NET을 활용하고 있습니다.
SIL International: Akka.NET을 사용하여 언어 학습 애플리케이션의 성능과 확장성을 개선하였습니다.
4. Kotlin: 코루틴과 채널
Kotlin에서는 언어 자체의 **코루틴(coroutines)**과 **채널(channels)**을 활용하여 액터 모델을 구현할 수 있습니다.
적용 사례:
JetBrains: Kotlin의 개발사로, 내부 도구와 애플리케이션에서 코루틴과 채널을 활용하여 비동기 프로그래밍과 동시성 처리를 효율화하고 있습니다.
5. Python: Ray 프레임워크
Ray는 Python에서 분산 컴퓨팅을 위한 프레임워크로, 액터 모델을 지원하여 대규모 병렬 처리를 가능하게 합니다.
적용 사례:
OpenAI: Ray를 활용하여 대규모 머신 러닝 모델의 학습과 배포를 효율적으로 관리하고 있습니다.
Ant Group: 금융 서비스에서 Ray를 사용하여 실시간 데이터 분석과 위험 관리를 수행하고 있습니다.
6. Erlang: 내장 액터 시스템
Erlang은 언어 자체에 액터 모델이 내장되어 있어, 높은 동시성과 분산 시스템 구축에 적합합니다.
적용 사례:
WhatsApp: Erlang을 사용하여 수억 명의 사용자에게 안정적인 메시징 서비스를 제공하고 있습니다.
Ericsson: 통신 시스템의 신뢰성과 가용성을 높이기 위해 Erlang을 활용하고 있습니다.
7. Elixir: Erlang 기반의 액터 모델
Elixir는 Erlang VM 위에서 동작하는 언어로, 액터 모델을 활용하여 확장성과 유지보수성이 높은 애플리케이션을 개발할 수 있습니다.
적용 사례:
Discord: Elixir를 사용하여 대규모 실시간 채팅 시스템을 구축하고, 수백만 명의 동시 접속을 처리하고 있습니다.
Bleacher Report: Elixir를 활용하여 스포츠 뉴스와 관련 콘텐츠를 실시간으로 제공하는 플랫폼을 운영하고 있습니다.
이처럼 다양한 언어와 프레임워크에서 액터 모델이 구현되어, 여러 테크 기업에서 분산 시스템과 동시성 처리를 위한 핵심 도구로 활용되고 있습니다.
도큐먼트
🟦 Scala / Java – Akka
프레임워크: Akka
설명: JVM 기반의 대표적인 액터 프레임워크. 동시성, 분산, 스트리밍 처리 등에 최적화됨.
공식 문서:
🟥Kotlin – Kotlinx Coroutines + Channels
프레임워크:
kotlinx.coroutines
설명: Kotlin의 비동기 처리 라이브러리. Channel을 통해 액터 모델 구현 가능.
공식 문서:
🟩 C# / .NET – Akka.NET
프레임워크: Akka.NET
설명: Akka를 .NET 환경에 포팅한 분산 액터 시스템
공식 문서:
🟨 Python – Ray
프레임워크: Ray
설명: 대규모 분산처리와 액터 모델을 지원하는 Python 진영 프레임워크
공식 문서:
🟥 Erlang – OTP / Built-in Actors
프레임워크: Erlang/OTP
설명: 언어 자체에 액터 모델 내장, OTP로 supervisor, gen_server 등을 지원
공식 문서:
🟪 Elixir – OTP / GenServer
프레임워크: Elixir + OTP
설명: Erlang VM 위에서 작동, 액터 모델 구현에 친숙하고 생산성 높음
공식 문서:
🟦 Scala/Java – Apache Pekko
🔸 소개
Apache Pekko는 **Akka의 오픈소스 포크(fork)**입니다.
Lightbend가 Akka를 Business Source License(BSL)로 변경하면서, 커뮤니티는 Apache 2.0 기반인 Pekko로 갈라져 나왔습니다.
JVM 기반의 분산 액터 시스템이며 Akka와 거의 동일한 API를 사용합니다.
🔸 특징
완전한 오픈소스 (Apache 2.0)
Akka와 거의 호환되며, 기존 코드를 마이그레이션 가능
액터, 스트림, HTTP, 클러스터링 기능 포함
🔸 적용 분야
마이크로서비스 아키텍처
실시간 메시징/스트리밍
이벤트 소싱 기반 시스템
🔸 공식 문서
🟩 C# – Microsoft Orleans
🔸 소개
Orleans는 Microsoft가 개발한 .NET 기반의 Virtual Actor 시스템입니다.
복잡한 상태 관리, 분산 스케줄링, 클러스터링을 추상화하여 쉽게 확장 가능한 시스템을 구축할 수 있게 해줍니다.
일반적인 액터 모델과 달리, Orleans는 가상 액터(Virtual Actor) 개념을 채택해 수명 관리가 자동화됩니다.
🔸 특징
상태 기반의 액터 (Grain), 자동 활성화/비활성화
분산 캐시처럼 작동하면서도 동시성 처리 가능
Azure, Kubernetes 등 클라우드 친화적
🔸 적용 분야
Microsoft Xbox Live: 수억 명의 게이머 세션 관리
Halo 4 Backend: 대규모 게임 데이터와 이벤트 처리
재무, AI, IoT 백엔드 등에서 확장성 요구되는 분야