클러스터 싱글톤 패턴은 akka.cluster.singleton.ClusterSingletonManager
에 의해 구현된다. 모든 클러스터 노드 또는 특정 역할이 태그된 노드 그룹 중에서 하나의 싱글톤 액터 인스턴스를 관리한다. ClusterSingletonManager
는 클러스터 내의 모든 노드 또는 지정된 역할이 있는 모든 노드에서 가능한 빨리 시작되어야 하는 액터이다. 실제 싱글톤 액터는 가장 오래된 노드에서 ClusterSingletonManager
에 의해 supply된 Props에서 하위 액터를 생성하여 시작된다. ClusterSingletonManager
는 최대 하나의 싱글톤 인스턴스가 임의의 시점에서 실행되도록 한다.
제공된 akkka.cluster.singleton.ClusterSingletonProxy
를 사용하여 싱글톤 액터에 액세스할 수 있으며, 이는 모든 메시지를 싱글톤의 현재 인스턴스로 라우팅할 것이다. 프록시는 클러스터에서 가장 오래된 노드를 추적하고 싱글톤의 actorSelection
의 akkka.actor.Identify
메시지를 명시적으로 전송하여 싱글톤의 ActorRef를 해결한다. 이는 싱글톤이 특정(구성 가능한) 시간 내에 응답하지 않으면 주기적으로 수행된다. 구현이 주어졌을 때, 예를 들어 노드가 클러스터를 떠날 때 ActorRef를 사용할 수 없는 기간이 있을 수 있다. 이러한 경우, 프록시는 싱글톤으로 전송된 메시지를 버퍼링한 다음 싱글톤이 최종적으로 사용 가능할 때 전달할 것이다. 버퍼가 가득 차면 ClusterSingletonProxy
는 프록시를 통해 새로운 메시지가 전송될 때 이전 메시지를 드롭할 것이다. 버퍼의 크기는 구성 가능하며 0의 버퍼 크기를 사용하여 비활성화할 수 있다.
우리가 외부 시스템으로 들어가는 하나의 진입점이 필요하다고 가정하자. JMS 소비자가 단 한 명이어야만 메시지가 순서대로 처리된다는 엄격한 요건을 갖춘 JMS 큐의 메시지를 받는 행위자, 그것은 아마도 사물을 설계하는 방식이 아니라 외부 시스템과 통합할 때 전형적인 실제 시나리오이다.
클러스터 싱글톤 액터를 만드는 방법을 설명하기 전에 싱글톤에서 사용할 메시지 클래스를 정의해 보겠다.
object PointToPointChannel {
case object UnregistrationOk extends CborSerializable
}
object Consumer {
case object End extends CborSerializable
case object GetCurrent extends CborSerializable
case object Ping extends CborSerializable
case object Pong extends CborSerializable
}
클러스터의 각 노드에서 ClusterSingletonManager
를 시작하고 싱글톤 액터(이 경우 JMS 큐 대기열 소비자)의 Props
를 제공해야 한다. 밑은 ClusterStingletonManager
를 Props
로 시작하는 경우이다.
system.actorOf(
ClusterSingletonManager.props(
singletonProps = Props(classOf[Consumer], queue, testActor),
terminationMessage = End,
settings = ClusterSingletonManagerSettings(system).withRole("worker")),
name = "consumer")
여기서는 싱글톤을 "worker" 역할이 태그된 노드로 제한하지만, 역할에 관계없이 모든 노드를 Role로 지정하지 않음으로써 사용할 수 있다.
실제로 싱글톤 Actor를 멈추기 전에 리소스를 닫을 수 있도록 애플리케이션 별 terminationMessage
를 사용한다. PoisonPill
은 actir를 멈추기만 하면 되는 완벽한 terminationMessage임에 유의해야 한다.
이 예제에서 싱글톤 액터가 종료 메시지를 처리하는 방법은 다음과 같다.
case End =>
queue ! UnregisterConsumer
case UnregistrationOk =>
stoppedBeforeUnregistration = false
context.stop(self)
case Ping =>
sender() ! Pong
위에 주어진 이름들로, 적절하게 구성된 프록시를 사용하여 어떤 클러스터 노드에서도 싱글톤에 대한 접근을 얻을 수 있다.
val proxy = system.actorOf(
ClusterSingletonProxy.props(
singletonManagerPath = "/user/consumer",
settings = ClusterSingletonProxySettings(system).withRole("worker")),
name = "consumerProxy")
잠재적으로 감독 대상이 될 수 있는 Actor는 두 가지이다. 위에서 생성된 소비자 Singleton은 다음과 같다:
/user/consumer
)/user/consumer/singleton
과 같은 사용자 actor클러스터 싱글톤 매니저 액터는 항상 실행 중이어야 하기 때문에 감독 전략이 변경되어서는 안 된다. 그러나 때때로 사용자 액터에 대한 감독을 추가하는 것이 유용하다. 이를 달성하기 위해 '진짜' 싱글톤 인스턴스를 생성하는 데 사용될 상위 감독 액터를 추가한다. 아래는 구현 예이다.
import akka.actor.{ Actor, Props, SupervisorStrategy }
class SupervisorActor(childProps: Props, override val supervisorStrategy: SupervisorStrategy) extends Actor {
val child = context.actorOf(childProps, "supervised-child")
def receive = {
case msg => child.forward(msg)
}
}
그리고 위의 액터는 아래의 예시처럼 사용할 수 있다.
import akka.actor.{ PoisonPill, Props }
import akka.cluster.singleton.{ ClusterSingletonManager, ClusterSingletonManagerSettings }
context.system.actorOf(
ClusterSingletonManager.props(
singletonProps = Props(classOf[SupervisorActor], props, supervisorStrategy),
terminationMessage = PoisonPill,
settings = ClusterSingletonManagerSettings(context.system)),
name = name)