Page History
...
Tip |
---|
. |
...
닷넷 코어에서 Akka.net 을 사용하기 위해 위 순서대로 셋팅을 하자, 심플한 액터 사용 코드가 포함되어 있습니다.
어플리케이션 로깅
위 순서대로 코드 셋팅이 성공하면 구동후 위와같은 로깅을 볼수 있습니다.
성공한 Git을 Pull받아 바로 실행하지 말고, 가급적 처음부터 셋팅해보길 권장합니다.
닷넷 코어 에서의 DI확장,Swagger,로깅등에 대한 설명은 생략합니다.
참고자료:
...
NET Core 3.1 API 에 Akka.net을 탑재하여 기본적인 액터를 생성하여 메시지 전송을 해보겠습니다. 액터의 라이프 사이클관리는 Core DI 를 활용하여 단순하게 사용 할수도 있으나, Akka는 Top-Level Architecture로 훨씬더 진보될수 있습니다. |
시작 템플릿
시작 템플릿은 .Net Core 3.1 API 를 사용하였으며, Docker 지원 옵션을 추천합니다. ( 도커 개발환경 지원)
샘플에서 프로젝트명은 AkkaNetCore 로 생성 하였습니다.
라이브러리 추가
Code Block | ||
---|---|---|
| ||
<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 | ||||||
---|---|---|---|---|---|---|
| ||||||
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 | ||||||
---|---|---|---|---|---|---|
| ||||||
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 | ||
---|---|---|
| ||
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 | ||||
---|---|---|---|---|
| ||||
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 | ||
---|---|---|
| ||
### 액터 시스템 설정 : 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 | ||
---|---|---|
| ||
[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 셋팅 성공입니다.