티스토리 뷰

지난번 포스팅에서 StateMachine(상태머신)에 대해서 잠깐 언급을 했었는데요. 오늘은 StateMachine의 구조와 실제로 YARN(얀)에서 이를 어떻게 활용하는지 알아보겠습니다.


상태머신은 하둡2에서 새롭게 도입된 인터페이스이며, 유저들은 상태머신을 이용해 손쉽게 컴포넌트들의 상태를 갱신하고, 상태 변화에 따른 로직을 처리할 수 있습니다. 그렇다면 하둡1에서는 어떠한 방식으로 컴포넌트들의 상태를 관리했을까요? 코드1은 하둡1의 잡트래커가 태스크들의 상태를 갱신하는 메소드 코드의 일부입니다. 코드를 보면, 잡트래커가 태스크에서 발생할 수 있는 모든 상태를 체크하고, 각 상태에 따른 로직 처리를 담당하는 것을 확인할 수 있습니다. 기존 하둡1은 아래와 같은 방식으로, 마스터 역할을 하는 컴포넌트가 자신이 필요한 컴포넌트 혹은 이벤트들의 상태를 직접 체크해야만 했습니다. 위와 같은 방식은 상태 처리에 대한 코드를 더 복잡하게 만들고, 이에 따라 코드의 유지보수도 더 어렵게 만들었습니다.


public synchronized void updateTaskStatus(TaskInProgress tip,

                                           TaskStatus status) {


~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~


   if ((wasComplete || tip.wasKilled(taskid)) &&

       (status.getRunState() == TaskStatus.State.SUCCEEDED)) {

     status.setRunState(TaskStatus.State.KILLED);

   }

   

   if ((this.isComplete() || jobFailed || jobKilled) &&

       !tip.isCleanupAttempt(taskid)) {

     if (status.getRunState() == TaskStatus.State.FAILED_UNCLEAN) {

       status.setRunState(TaskStatus.State.FAILED);

     } else if (status.getRunState() == TaskStatus.State.KILLED_UNCLEAN) {

       status.setRunState(TaskStatus.State.KILLED);

     }

   }

   

   boolean change = tip.updateStatus(status);

   if (change) {

   ~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~


     TaskCompletionEvent taskEvent = null;

     if (state == TaskStatus.State.SUCCEEDED) {

~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~


     } else if (state == TaskStatus.State.COMMIT_PENDING) {

~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

     

코드1 JobTracker의 updateTaskStatus 메소드 코드


그렇다면 얀은 상태머신으로 어떻게 위와 같은 복잡한 상태 조작을 편리하게 변경했을까요? 얀은 상태머신와 상태머신 팩토리(StateMachineFactory)를 제공합니다. 상태머신은 현재 상태를 조회하고, 이벤트를 발생시키는 기능만을 제공합니다. 상태머신 팩토리는 상태머신의 상태에 따른 이벤트 목록을 관리하고, 이벤트 처리가 필요한 상태가 발생했을 때 실제로 로직을 처리할 컴포넌트에 이벤트 처리를 전달하게 됩니다.


그럼 얀은 내부적으로 어떠한 방식으로 상태머신을 적용하고 있을까요? 여러 컴포넌트들이 상태 머신을 사용하고 있는데, 그 중에서 RMNode가 상태머신을 관리하는 방법을 설명하겠습니다. RMNode는 리소스 매니저가 가용 가능한 노드 매니저들의 상태 정보를 조회하기 위한 인터페이스입니다. RMNode는 노드번호 조회, 호스트 이름 조회, HTTP 포트 조회, 랙 조회, 해당 노드가 보유하고 있는 컨테이너 목록 조회 등 다양한 메소드를 제공합니다. RMNodeImpl은 RMNode 인터페이스를 구현한 클래스이며, RMNodeImpl는 코드2와 같은 방식으로 상태머신 팩토리를 생성합니다.



private static final StateMachineFactory<RMNodeImpl,

                                          NodeState,

                                          RMNodeEventType,

                                          RMNodeEvent> stateMachineFactory

                = new StateMachineFactory<RMNodeImpl,

                                          NodeState,

                                          RMNodeEventType,

                                          RMNodeEvent>(NodeState.NEW)

코드2 RMNodeImpl의 상태머신 팩토리 선언


상태머신 팩토리를 선언할 때 첫번째 파라미터는 상태머신을 제어하는 객체의 유형을 의미하며, RMNodeImpl은 자기 자신이 상태머신을 제어하기 때문에 RMNodeImpl을 설정합니다. 두번째 파라미터는 엔티티의 상태이며, 이후 상태머신의 상태는 여기서 설정한 엔티티 클래스에서 제공하는 상태를 이용합니다. 세번째 파리미터는 처리할 이벤트 유형이며, 마지막 파리미터는 이벤트 객체를 나타냅니다. 위 코드에서는 세미콜론(;)으로 상태머신 팩토리 생성을 종료하고 있지 않은데, 바로 다음 라인부터 팩토리에 상태를 추가하기 때문입니다. 코드3은 상태머신 팩토리에 상태를 추가하는 코드의 일부입니다.


//Transitions from NEW state

.addTransition(NodeState.NEW, NodeState.RUNNING,

 RMNodeEventType.STARTED, new AddNodeTransition())


//Transitions from RUNNING state

.addTransition(NodeState.RUNNING,

 EnumSet.of(NodeState.RUNNING, NodeState.UNHEALTHY),

 RMNodeEventType.STATUS_UPDATE, new StatusUpdateWhenHealthyTransition())


코드3 RMNodeImpl의 상태머신 팩토리에 상태 추가


상태머신 팩토리는 관리할 상태를 추가하도록 addTransition 메소드를 제공합니다. addTransition의 첫번째 파라미터는 이전 상태, 두번 째 파라미터는 다음 상태 집합, 세번째 파라미터는 이전 상태에서 다음 상태로 변경될 때 처리할 이벤트 타입, 마지막은 상태 변환이 유효한지 확인하고, 다음 상태를 반환하는 클래스입니다. 이때 상태값들이 유효하지 않다면, 해당 클래스는 다른 상태를 반환할 수도 있습니다. 그래서 두번째 파라미터가 하나의 상태가 아니라, Set 유형으로 설정 가능한 것입니다. 예를 들어 코드11.3의 첫번째 라인은 이전 상태가 RUNNING, 다음 상태가 RUNNING과 UNHEALTHY입니다. 이때 상태 유효성 점검을 StatusUpdateWhenHealthyTransition이 담당하는데, 코드4는 StatusUpdateWhenHealthyTransition 코드의 일부입니다. 코드를 보면 중간에 원격 노드의 상태가 이상할 때 UNHEALTHY를 반환하고, 모든 유효성 점검이 통과됐을 때는 마지막에 RUNNING을 반환하는 것을 확인할 수 있습니다.


public static class StatusUpdateWhenHealthyTransition implements

 MultipleArcTransition<RMNodeImpl, RMNodeEvent, NodeState> {

@Override

public NodeState transition(RMNodeImpl rmNode, RMNodeEvent event) {


 RMNodeStatusEvent statusEvent = (RMNodeStatusEvent) event;


~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

 if (!remoteNodeHealthStatus.getIsNodeHealthy()) {

~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

   return NodeState.UNHEALTHY;

 }


~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~


 for (ContainerStatus remoteContainer : statusEvent.getContainers()) {

   ~~~~~~~~~~~~~~~~~~~~ 중략 ~~~~~~~~~~~~~~~~~~~~~~~~~~~

 }

 return NodeState.RUNNING;

 }

}


코드4 StatusUpdateWhenHealthyTransition 클래스


코드3에서 상태머신 팩토리에 추가하는 일부 코드만을 소개했지만, 실제로 RMNodeImpl은 상태머신의 상태 변경에 따라서 다양한 이벤트를 처리됩니다. 다음 그림은 RMNodeImpl에서 상태머신 팩토리에 등록한 모든 상태를 나타냅니다. 참고로 화살표 옆에 있는 설명이, 이전 상태에서 다음 상태로 전환될때 처리될 이벤트 타입입니다.



그림. RMNodeImpl StateMachine Diagram


RMNodeImpl의 경우, 상태머신이 단순한 편이지만, 주요 컴포넌트에서 사용하는 상태머신들은 훨씬 복잡한 상태와 이벤트로 처리됩니다. 얀은 위와 같은 상태머신을 도입해서, 상태 전환을 선언적으로 처리합니다. 또한 상태들간에 잘못된 전환이 발생하는 경우에도 상태머신 팩토리에 해당 상태를 추가함으로써, 자동으로 상태 전환 오류를 막아줍니다. 또한 상태의 전환에 이벤트 타입을 적용함으로써 다양한 이벤트를 처리할 수 있습니다. 앞서 설명드렸던 이벤트 모델과 잘 부합되는 아키텍처라고 볼 수 있습니다. 또한 디버그 기능이 향상되어서, 객체의 상태정보를 쉽게 확인할 수 있습니다.


참고로 제가 참여하고 있는 아파치 타조(http://tajo.incubator.apache.org/)도 얀의 이벤트 처리 모델인 AbstractService, CompositeService, 그리고 StateMachine을 도입해서, 각 데몬들의 상태를 효과적으로 관리하고 있습니다. 솔루션의 이벤트 처리 떄문에 고민하고 있다면, 얀의 이벤트 모델을 도입을 검토해보시길 추천드립니다.

저작자 표시 비영리 변경 금지
신고
댓글
댓글쓰기 폼