이장에서 추가적으로 알게되는것

  1. 단순한 문자열이 아닌 객체를 전송하는 방법 ( 자동 시리얼라이즈가 됩니다. )
  2. 액터를 스레드프로그래밍없이 확장하고 분배하는방법 ( 대표적인 라운드 로빈풀을 샘플로 사용하였으며,다양한 라우터를 활용할수 있습니다.)
  3. 동시성 처리를 위한 스레드 모델과 액터모델의 차이점

프린터 기능 요약

  • 출력 완료 소요되는 시간은 페이지에 따라 하드웨어의 성능에 영향받습니다.
  • 출력 완료에 상관없이 프린터 요청은 계속 받을수 있으며 , 순차적으로 프린팅을 합니다. ( 요청에대한 순차 처리)
  • 출력 요청응답은 출력 완료시간 의존없이 즉각 이루어 져야하며 , 대기가 없습니다. 


여기서 종류가 다른 제조업체의 프린터에게 명령을 구현하는게 목적이 아닌 ( 팩토리패턴을 설명하기위해 프린터 드라이브 생성모델이 자주 언급되곤 합니다.)

단순하게 메시지 전송관점에서 메시지 큐를 어떻게 다루어야할지에만 촛점을 두고 액터 설명을 진행하겠습니다.

위기능은 DB에 비동기 로그를 쌓는 용도 , Redis에 비동기적인 Write 기능수행등 다양하게 변종이 가능합니다.


프린터 액터 설계

  •    : 완료를 기달려야하는 요청
  •  : 완료를 기달릴 필요없는 요청
  • PrinterController : 프린터에 명령을 실행하는 API
  • PrinterActor : 실제 메시지를 받고 프린터 명령을 수행하는 액터


액터 설계는 메시지 중심적이어야하며, 메시지가 어떻게 흘러가고 처리가되는지에대한 플로우를 잘 정의해야합니다.

여기서 그려진 플로우는 군더더기 없이 코드와 일치를 시키는 것이 중요합니다. 

출력 요청 3이 프린터가 출력중이더라도 요청을 받을수 있고 대기가 없어야한다란것이

위 다이어그램이 표현하고자 하는 중요 포인트이며 다이어그램에서는 설명이 안되었지만

프린팅중이더라도 , 프린터 액터가 메시지를 받을수 있다란것은 수신 메시지 큐가 별도로 작동함을 의미합니다.


프린팅 요청 메시지 구현

    public class PrintPage
    {
        public int SeqNo { get; set; }
        
        public int DelayForPrint { get; set; }  //프리팅에 걸리는 시간 조작

        public string Content { get; set; }

        public override string ToString()
        {
            return $"SeqNo:{SeqNo} Content:{Content}";
        }
    }

출력 요청시, 액터에게 전달하는 메시지 정의이며 , API에서 요청받은 아규먼트를 그대로 동일하게 사용할수 있습니다.

액터를 통해 가상의 프린팅기능을 구현하고 있기때문에, 프린팅에 걸리는 시간조작 기능을 넣어두었습니다. 


프린터 액터 구현

public class PrinterActor : ReceiveActor
    {
        private readonly ILoggingAdapter logger = Context.GetLogger();
        private readonly string id;

        public PrinterActor()
        {
            id = Guid.NewGuid().ToString();
            logger.Info($"프린터 액터 생성:{id}");
            
			// 스위치문이 아닌, 메시지 Type에따른 분기처리를 생성자에서 셋팅하는것이
			// 패턴 매칭이라고도 불립니다.  ( ex> 어떠한 type에 특정 조건이 들어왔을때 )
			// 더 다양한 메시지 기능을 추가하고 싶을때 아래와같은 형택의 코드를 Type별로 추가하면 됩니다.
			// 아래 함수는 델리게이트에 등록되며,생성시마다 호출되는것이 아니고 메시지가 올때마다 호출됩니다.
            ReceiveAsync<PrintPage>(async page =>
            {
                logger.Debug($"프린터 요청 들어옴:{page}");
                await Task.Delay(page.DelayForPrint);
                logger.Debug($"페이지 출력 완료:{page}");
            });            
        }
    }

단순하게 PritPage를 받으면.. 요청시간만큼 지연을 시킨후 ( 프린팅에 걸리는 소요되는 시간을 조작 )

출력을 수행하게됩니다.

패턴 매칭이 아닌,스위치 문을 통한 분기하는 방식을 더 선호한다고 하면

UntypedActor를 이용하셔도 됩니다. ( https://getakka.net/articles/actors/untyped-actor-api.html )

패턴 매칭같은경우 비교적 최신 언어스펙에만 있으며 언어별로 사용방법이 다르기때문에

여러 언어로 스위칭이 잦고 ,변환되어야할경우 저 수준의 언어 스펙을 선호 하기도합니다. 


프린터 액터 컨트롤러

    [Route("api/[controller]")]
    [ApiController]
    public class PrinterController : Controller
    {
        private IActorRef printerActor;

        public PrinterController()
        {
            printerActor = AkkaLoad.ActorSelect("printer")
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody] PrintPage value)
        {
            // 프린팅을 요청한다.
            printerActor.Tell(value);
        }        
    }

닷넷 코어에서 API를 구현하는 일반적인 방법이며 닷넷 Object를 해당 액터에게 Tell만 하게되면 비동기적으로 프린터 명령예약이 되게 됩니다.

이 API는 블락이 없음으로 즉각 반환됩니다.


프린터 액터 객체 생성

AkkaLoad.RegisterActor(
	"printer", //여기서 지정한 네이밍은,편리하게 액터 참조를 얻어내기위해 커스텀하게 작성된 네이밍입니다.
	actorSystem.ActorOf(Props.Create<PrinterActor>()
			.WithRouter(new RoundRobinPool(1)),
			"printerActor" // 이 네이밍은 액터시스템내에 고유한 액터명입니다.(공식)
));

PrinterActor객체의 생성은 여기서 설정된 코드에의해 환경설정이 주입됩니다. 

프린터는 1대임으로 RoundRobinPool에 1대만 등록하였습니다. 여기서 2로 설정하면

두대의 프린터가 대기중인 상태로 2배의 성능을 가지게됩니다.(스레드 프로그래밍없이, 마치 스레드 풀을 이용하는 효과가 생기게됩니다.)

액터는 분산 처리를 위해  다양한 라우터를 지원합니다. :- 참고 : Routers

라우터에 연결된 하나의 경로를 라우티라고 하며 , 이 라우티에 대해 접근을 할수 있고 어떠한 방법으로 작업 분배를 시킬것인가 하는것은 라우터에 의해 정의가 됩니다.


여기서 한대의 프린터가 부족하다고 하면...  단순하게 RoundRobinPool의 옵션에 숫자만 추가를 하게되면

동시에 N 개를 처리하는 프린터의 구성이 가능하게 됩니다.


스레드 모델과 비교한 액터모델의 특성

지금까지 설명한 코드를 스레드 모델로  동일하게 구현한다고 가정해봅시다.

도메인 로직보다 다음과 같이 멀티스레드에 안전한 코드작성을 위해 더 많은 코드와 노력이 사용될것입니다.

  • 큐를 구현하고 큐를 사용할때 Lock/Unlock 에 신경써야함 , 잘못 사용될 경우 성능저하및 데드락의 위험
  • 병렬처리를 위해 스레드 개수를 늘리고 줄이고, 한다고하면 여러가지 스레딩 모델을 이해해야하며 적합한 스레드 모델을 사용해야함
  • 도메인 로직이 10프로라고 하면.. 스레드를 올바르게 사용하려는 노력의 코드가 90%가됨
  • 결국 이렇게 어렵게 구현된 코드는 , 분산환경에서 유연성을 잃게됨 ( 로컬에서도 제어하기 힘든 스레드를,원격에서 TCP를 이용하여 제어한다는것은 난이도 증가) 


추가 참고 문서 : https://getakka.net/articles/intro/what-problems-does-actor-model-solve.html


프린터 작동하기

출력 명령은 API를 통해 가능하며, 페이지 한장에 출력되는 시간을 3초(3000) 로 셋팅을 하였으며,  로그에서는 동시성 처리를 위한 몇가지 정보를 출력합니다. ( 코드 참고)


설정하기편 에서 Swagger과 Logger를 먼저 셋팅한 이유는....,

AKKA의 동시성 확인을 위해서는 ,   Swagger를 통해 명령 발생 , Logger를 통해 비동기 처결과 확인 이라는 비교적 단순한 테스트 기법을 사용하기 위함입니다.

성능에 있어서 요청시간 - 완료시간 - 처리한 스레드번호와 사용된 스레드 개수등의 파악 중요성은 액터 프로그래밍이던 멀티스레딩 프로그래밍이던 동일합니다.




  • No labels