Versions Compared

Key

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

Akka는 오픈 소스 툴킷으로, JVM 상의 동시성과 분산 애플리케이션을 단순화하는 런타임이다.
Akka는 동시성을 위한 여러 프로그래밍 모델을 지원하지만, Erlang으로부터 영향을 받아 actor 기반의 동시성이 두드러진다.
자바와 스칼라 언어 모두로 작성이 가능하다.

Akka가 .net 버전으로 포팅된것이 Akka.net이며
.net core 3.x 에서 작동 검증된 것만 정리하고 있습니다.

닷넷 코어에서 Akka.net 을 사용하기 위해 위 순서대로 셋팅을 하자, 심플한 액터 사용 코드가 포함되어 있습니다.

어플리케이션 로깅

Image Removed

위 순서대로 코드 셋팅이 성공하면 구동후 위와같은 로깅을 볼수 있습니다.

성공한 Git을 Pull받아 바로 실행하지 말고, 가급적 처음부터 셋팅해보길 권장합니다.

닷넷 코어 에서의 DI확장,Swagger,로깅등에 대한 설명은 생략합니다.

참고자료:

...

.NET Core 3.1 API 에 Akka.net을 탑재하여 기본적인 액터를 생성하여

메시지 전송을 해보겠습니다. 액터의 라이프 사이클관리는

Core DI 를 활용하여 단순하게 사용 할수도 있으나,  Akka는 Top-Level Architecture로 훨씬더 진보될수 있습니다.

시작 템플릿

Image Added

시작 템플릿은 .Net Core 3.1 API 를 사용하였으며, Docker 지원 옵션을 추천합니다. ( 도커 개발환경 지원)

샘플에서 프로젝트명은  AkkaNetCore 로 생성 하였습니다.

라이브러리 추가

Code Block
themeEmacs
<PackageReference Include="Akka" Version="1.3.17" />
<PackageReference Include="Akka.Cluster" Version="1.3.17" />
<PackageReference Include="Akka.Cluster.Tools" Version="1.3.17" />
<PackageReference Include="Akka.DI.Extensions.DependencyInjection" Version="1.3.2" />    
<PackageReference Include="Akka.Logger.NLog" Version="1.3.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.8.1" />
  • Akka : 베이스 이며 로컬 액터만 사용한다고 하면 이것만으로 충분하다.
  • Akka.Cluster : 클러스터는 리모트기능을 포함하고 있으며 , 작성된 액터를 원격으로 조직화할때 사용된다.
  • Akka.Cluster.Tools : 클러스터를 쉽게 구성할수 있는 툴들이 추가되어있다. ( ex>싱글톤 클러스터 )
  • Akka.DI.Extensions.DependencyInjection : 액터에 의존성 주입을 할수 있게한다.(옵션)
  • Akka.Logger.NLog : 액터 메시지 로그 어댑터가 Nlog를 활용할수 있게한다.
  • Nlog.Web.AspNetCore : Net Core에서 Nlog(로깅 툴)를 활용할수 있게한다.

누겟이나 프로젝트 편집을 통해 의존 라이브러리 추가가 가능합니다. NLog(https://nlog-project.org/) 셋팅은 여기서 제외하니,

해당 홈페이지를 통해 셋팅을 하거나 이 프로젝트에서 셋팅된 방법을 참고하세요

Akka Extensions

Akka System및 구성요소(액터,설정) 등을 편리하게 사용하기 위해 사용자 정의 셋팅 파일을 추가합니다.

이 파일은 닷넷 능력치에따라, 필자가 작성한 방법보다 더 편리한 클래스로 개선가능합니다. 

Code Block
themeEmacs
titleAkkaBoostrap - 액터시스템을 싱글톤으로 등록
collapsetrue
using System;
using Akka.Actor;
using Akka.Cluster.Tools.Singleton;
using Microsoft.Extensions.DependencyInjection;

namespace AkkaNetCore.Extensions
{
    public static class AkkaBoostrap
    {
        public static IServiceCollection AddAkka(this IServiceCollection services, ActorSystem actorSystem)
        {
            // Register ActorSystem
            services.AddSingleton<ActorSystem>((provider) => actorSystem );
            return services;
        }
        
        public static IActorRef BootstrapSingleton<T>(this ActorSystem system, string name, string role = null) where T : ActorBase
        {
            var props = ClusterSingletonManager.Props(
                singletonProps: Props.Create<T>(),
                settings: new ClusterSingletonManagerSettings(name, role, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(3)));

            return system.ActorOf(props, typeof(T).Name);
        }

        public static IActorRef BootstrapSingletonProxy(this ActorSystem system, string name, string role, string path, string proxyname)
        {
            var props = ClusterSingletonProxy.Props(
                singletonManagerPath: path,
                settings: new ClusterSingletonProxySettings(name, role, TimeSpan.FromSeconds(1), 100));

            return system.ActorOf(props, proxyname);
        }
    }
}
Code Block
themeEmacs
titleAkkaLoad - 설정로드및 액터 참조 객체관리
collapsetrue
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using Akka.Actor;
using Akka.Configuration;
using Microsoft.Extensions.Configuration;
using AkkaConfig = Akka.Configuration.Config;

namespace AkkaNetCore.Config
{
    public class AkkaLoad
    {
        public static ConcurrentDictionary<string, IActorRef> ActorList = new ConcurrentDictionary<string, IActorRef>();

        public static void RegisterActor(string name, IActorRef actorRef)
        {
            if (ActorList.ContainsKey(name)) throw new Exception("이미 등록된 액터입니다.");
            ActorList[name] = actorRef;
        }

        public static IActorRef ActorSelect(string name)
        {
            return ActorList[name];
        }

        public static AkkaConfig Load(string environment, IConfiguration configuration)
        {
            if(environment.ToLower()!= "production")
            {
                environment = "Development";
            }

            return LoadConfig(environment, "akka{0}.conf", configuration);
        }

        private static AkkaConfig LoadConfig(string environment, string configFile, IConfiguration configuration)
        {
            string akkaip = configuration.GetSection("akkaip").Value ?? "127.0.0.1";
            string akkaport = configuration.GetSection("akkaport").Value ?? "5100";
            string akkaseed = configuration.GetSection("akkaseed").Value ?? "127.0.0.1:5100";
            string roles = configuration.GetSection("roles").Value ?? "akkanet";

            var configFilePath = string.Format(configFile, environment.ToLower() != "production" ? string.Concat(".", environment) : "");
            if (File.Exists(configFilePath))
            {
                string config = File.ReadAllText(configFilePath, Encoding.UTF8)
                    .Replace("$akkaport", akkaport)
                                .Replace("$akkaip", akkaip)
                                .Replace("$akkaseed", akkaseed)
                                .Replace("$roles", roles);

                var akkaConfig = ConfigurationFactory.ParseString(config);

                Console.WriteLine($"=== AkkaConfig:{configFilePath}\r\n{akkaConfig}\r\n===");
                return akkaConfig;
            }
            return Akka.Configuration.Config.Empty;
        }
    }
}


Config File추가

Code Block
titleakka.conf / akka.Development.conf - 운영과 개발을 구분
akka {
	loggers = ["Akka.Logger.NLog.NLogLogger, Akka.Logger.NLog"]
	loglevel = debug
}

Akka에서 작동되는 설정은  Json과 유사한 형태로 설정이 되며, 우선 로깅이 되는 기본 설정으로 시작해보자

추가참고 : https://doc.akka.io/docs/akka/2.5/general/configuration.html


기본 액터추가

Code Block
themeEmacs
titleBasicActor - string을 전송받는 액터
using Akka.Actor;
using Akka.Event;

namespace AkkaNetCore.Actors.Study
{
    public class BasicActor : ReceiveActor
    {
        private readonly ILoggingAdapter logger = Context.GetLogger();

        public BasicActor()
        {
            ReceiveAsync<string>(async msg =>
            {
                logger.Info($"{msg} 를 전송받았습니다.");
            });
        }
    }
}

AkkaSystem 탑재가 잘 되었나 확은하는것은 액터를 생성하고 메시지를 보내보는것입니다.

액터 생성

Code Block
themeEmacs
### 액터 시스템 설정 : ConfigureServices 내에 추가
....
// *** Akka Service Setting            
var envName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var akkaConfig = AkkaLoad.Load(envName, Configuration);
var actorSystem = ActorSystem.Create(SystemNameForCluster, akkaConfig);
var provider = services.BuildServiceProvider();
actorSystem.UseServiceProvider(provider);
services.AddAkka(actorSystem);


### 액터 시스템 구동및 액터 생성 : Configure 내에 추가
app.ApplicationServices.GetService<ILogger>();
var actorSystem = app.ApplicationServices.GetService<ActorSystem>(); // start Akka.NET
ActorSystem = actorSystem;

//액터생성 오리지널
actorSystem.ActorOf(Props.Create<BasicActor>(),"basic2"

//액터생성 커스텀
AkkaLoad.RegisterActor(
	"basic",
	actorSystem.ActorOf(Props.Create<BasicActor>(),
	"basic"
));

액터 생성시 액터의 이름은 해당 시스템에서 유일해야합니다.

AkkaLoad에 의해 커스텀된 생성방법이며, 액터의 참조(IActorRef)를 쉽게 가져오기위해 Dictionary에 등록하는것이 전부입니다.

다양한 DI활용을 통해 ( https://getakka.net/articles/actors/dependency-injection.html) 생성방법을 단순화할수 있으나

액터 객체자체가 Top-level Architecture(https://getakka.net/articles/actors/dependency-injection.html) 로 라이프 사이클의 구조관리가

가능하니 가급적 닷넷 코어의 DI를 액터에 직접 연동시키는 것은 비 권장합니다.

(ex> 액터는 코드변경없이 설정만으로 라운드로빈설정을 하여 x배의 성능을 내는 확장이 가능합니다. 하지만 액터를 싱글톤으로 제약을 둔다고 하면

액터는 확장에 실패할것입니다. 현재 로컬에서 단 하나만 가진다고 하더라도 싱글톤으로 제약을 거는 설계는 바람직 하지 않습니다.

단하나만 존재해도 되는 객체는 액터 시스템이 유일하며, 단하나만 존재해야하는 액터일경우 단하나만 생성하면 됩니다. )


API 에 액터 연결하여 전송하기

Code Block
themeEmacs
[Route("api/[controller]")]
[ApiController]
public class ActorTestController : Controller
{
	private readonly IActorRef basicActor;

	public ActorTestController()
	{
		basicActor = AkkaLoad.ActorSelect("basic");            
	}

	[HttpPost("/Single/Basic/tell")]
	public void Printer(string message)
	{
		// basicActor 요청한다.
		basicActor.Tell(message);
	}
...

## 로그 샘플
[2020-03-06 22:12:43.7874] [Info] [PSMON-ASUS] [AKKA] [ACTOR] [AkkaNetCore.Actors.Study.BasicActor] [11] : 안녕하세요 를 전송받았습니다.

API→Actor 로 전달되는 샘플이며 , AkkaLoad 를 커스텀하게 구현하고 사용한 이유는 DI를 사용하지않고  액터의 참조를 간단하게 얻어내기 위함입니다.

액터가 로깅을 정상 출력한다고 하면 Akka.net 셋팅 성공입니다.