우선 순위 큐 패턴

Azure Service Bus

우선 순위가 높은 요청을 우선 순위가 낮은 요청보다 먼저 받아서 처리하도록 서비스로 전송된 요청의 우선 순위를 지정합니다. 이 패턴은 개별 클라이언트에 서로 다른 서비스 수준 보장을 제공하는 애플리케이션에서 유용합니다.

컨텍스트 및 문제점

애플리케이션은 특정 작업(예: 백그라운드 처리 수행 또는 다른 애플리케이션이나 서비스와의 통합)을 다른 서비스에 위임할 수 있습니다. 클라우드에서 메시지 큐는 일반적으로 작업을 백그라운드 처리에 위임하는 데 사용됩니다. 대부분의 경우 서비스에서 요청을 받는 순서는 중요하지 않습니다. 그러나 경우에 따라 특정 요청에 대해 우선 순위를 지정해야 합니다. 이러한 요청은 이전에 애플리케이션에서 보낸 우선 순위가 낮은 요청보다 일찍 처리해야 합니다.

솔루션

큐는 일반적으로 FIFO(선점) 구조이며, 소비자는 일반적으로 큐에 게시된 것과 동일한 순서로 메시지를 받습니다. 그러나 일부 메시지 큐에서는 우선 순위 메시지를 지원합니다. 메시지를 게시하는 애플리케이션은 우선 순위를 할당할 수 있습니다. 우선 순위가 더 높은 메시지가 우선 순위가 낮은 메시지보다 우선 순위가 낮은 메시지보다 우선 순위가 더 높은 메시지를 수신할 수 있도록 큐의 메시지가 자동으로 다시 정렬됩니다. 이 다이어그램은 프로세스를 보여 줍니다.

메시지 우선 순위를 지원하는 큐 메커니즘을 보여 주는 다이어그램

참고

대부분의 메시지 큐 구현은 여러 소비자를 지원합니다. ( 경쟁 소비자 패턴을 참조하세요.) 수요에 따라 소비자 프로세스 수를 확장 및 축소할 수 있습니다.

우선 순위 기반 메시지 큐를 지원하지 않는 시스템에서 적용할 수 있는 다른 솔루션은 각 우선 순위마다 별도의 큐를 유지하는 것입니다. 애플리케이션은 해당 큐에 메시지를 게시해야 합니다. 각 큐에는 별도의 소비자 풀이 있을 수 있습니다. 우선 순위가 높은 큐는 우선 순위가 낮은 큐보다 더 빠른 하드웨어에서 실행되는 더 큰 소비자 풀을 가질 수 있습니다. 이 다이어그램에서는 각 우선 순위에 대해 별도의 메시지 큐를 사용하는 방법을 보여 줍니다.

각 우선 순위에 대해 별도의 메시지 큐를 사용하는 방법을 보여 주는 다이어그램

이 전략의 변형은 우선 순위가 높은 큐에서 메시지를 먼저 확인하고 우선 순위가 낮은 큐에서 메시지를 가져오기 시작한 후에만 메시지를 확인하는 단일 소비자 풀을 구현하는 것입니다. 단일 소비자 프로세스 풀을 사용하는 솔루션(우선 순위가 다른 메시지를 지원하는 단일 큐 또는 각 우선 순위의 메시지를 처리하는 여러 큐가 있음)과 각 큐에 대해 별도의 풀이 있는 여러 큐를 사용하는 솔루션 간에는 몇 가지 의미 체계적 차이가 있습니다.

단일 풀 접근 방식에서 우선 순위가 높은 메시지는 우선 순위가 낮은 메시지보다 항상 수신되고 처리됩니다. 이론적으로 우선 순위가 낮은 메시지는 지속적으로 대체될 수 있으며 처리되지 않을 수 있습니다. 여러 풀 접근 방식에서 우선 순위가 낮은 메시지는 항상 처리되지만 우선 순위가 높은 메시지만큼 빠르게 처리되지는 않습니다(풀의 상대적 크기와 사용할 수 있는 리소스에 따라 다름).

우선 순위 큐 메커니즘을 사용하면 다음과 같은 이점이 있습니다.

  • 이를 통해 애플리케이션은 다양한 고객 그룹에 다양한 수준의 서비스를 제공하는 등 가용성 또는 성능의 우선 순위를 요구하는 비즈니스 요구 사항을 충족할 수 있습니다.

  • 운영 비용을 최소화할 수 있습니다. 단일 큐 방법을 사용하는 경우 필요한 경우 소비자 수를 축소할 수 있습니다. 우선 순위가 높은 메시지는 여전히 먼저 처리되며(더 느리게 처리될 수 있음), 우선 순위가 낮은 메시지는 더 오래 지연될 수 있습니다. 각 큐에 대해 별도의 소비자 풀을 사용하여 여러 메시지 큐 접근 방식을 구현하는 경우 우선 순위가 낮은 큐에 대한 소비자 풀을 줄일 수 있습니다. 해당 큐에서 메시지를 수신 대기하는 모든 소비자를 중지하여 우선 순위가 매우 낮은 큐에 대한 처리를 일시 중단할 수도 있습니다.

  • 여러 메시지 큐 방식을 사용하면 처리 요구 사항에 따라 메시지를 분할하여 애플리케이션 성능 및 확장성을 최대화할 수 있습니다. 예를 들어 중요한 작업의 우선 순위를 지정하여 즉시 실행되는 수신기에서 처리하고 덜 바쁜 시간에 실행되도록 예약된 수신기에서 덜 중요한 백그라운드 작업을 처리할 수 있습니다.

고려 사항

이 패턴을 구현하는 방법을 결정할 때 다음 사항을 고려합니다.

  • 솔루션의 컨텍스트에서 우선 순위를 정의합니다. 예를 들어 우선 순위가 높은 메시지를 10초 이내에 처리해야 하는 메시지로 정의할 수 있습니다. 우선 순위가 높은 항목을 처리하기 위한 요구 사항과 조건을 충족하기 위해 할당해야 하는 리소스를 식별합니다.

  • 우선 순위가 높은 모든 항목을 우선 순위가 낮은 항목보다 먼저 처리해야 하는지 여부를 결정합니다. 단일 소비자 풀에서 메시지를 처리하는 경우 우선 순위가 높은 메시지가 큐에 들어갈 경우 우선 순위가 낮은 메시지를 처리하는 작업을 선점하고 일시 중단할 수 있는 메커니즘을 제공해야 합니다.

  • 여러 큐 접근 방식에서 각 큐에 대한 전용 소비자 풀이 아닌 모든 큐에서 수신 대기하는 단일 소비자 프로세스 풀을 사용하는 경우 소비자는 우선 순위가 낮은 큐의 메시지보다 더 높은 우선 순위 큐의 메시지를 항상 처리하도록 하는 알고리즘을 적용해야 합니다.

  • 우선 순위가 높고 낮은 큐에서 처리 속도를 모니터링하여 해당 큐의 메시지가 예상 속도로 처리되도록 합니다.

  • 우선 순위가 낮은 메시지가 처리되도록 보장해야 하는 경우 여러 소비자 풀을 사용하여 여러 메시지 큐 접근 방식을 구현합니다. 또는 메시지 우선 순위를 지원하는 큐에서 나이가 들면서 큐에 대기 중인 메시지의 우선 순위를 동적으로 늘릴 수 있습니다. 그러나 이 방식은 이 기능을 제공하는 메시지 큐에 따라 달라집니다.

  • 메시지 우선 순위에 따라 별도의 큐를 사용하는 전략은 몇 가지 잘 정의된 우선 순위가 있는 시스템에 권장됩니다.

  • 시스템은 메시지 우선 순위를 논리적으로 결정할 수 있습니다. 예를 들어 우선 순위가 높고 낮은 명시적 메시지를 갖는 대신 메시지를 "유료 고객" 또는 "비급여 고객"으로 지정할 수 있습니다. 그러면 시스템에서 유료 고객의 메시지 처리에 더 많은 리소스를 할당할 수 있습니다.

  • 큐에서 메시지를 확인하는 것과 관련된 재무 및 처리 비용이 있을 수 있습니다. 예를 들어 일부 상용 메시징 시스템은 메시지가 게시되거나 검색될 때마다, 그리고 큐에서 메시지를 쿼리할 때마다 약간의 요금을 부과합니다. 이 비용은 여러 큐를 확인할 때 증가합니다.

  • 풀이 서비스하는 큐의 길이에 따라 소비자 풀의 크기를 동적으로 조정할 수 있습니다. 자세한 내용은 자동 크기 조정 지침을 참조하세요.

이 패턴을 사용해야 하는 경우

이 패턴은 다음과 같은 시나리오에서 유용합니다.

  • 시스템에서 우선 순위가 다른 여러 작업을 처리해야 합니다.

  • 다른 사용자 또는 테넌트는 다른 우선 순위로 제공되어야 합니다.

예제

Azure는 기본적으로 정렬을 통해 메시지의 자동 우선 순위를 지원하는 큐 메커니즘을 제공하지 않습니다. 그러나 Azure Service Bus 항목, 메시지 필터링을 제공하는 큐 메커니즘을 지원하는 Service Bus 구독 및 Azure를 대부분의 우선 순위 큐 구현에 이상적으로 만드는 다양한 유연한 기능을 제공합니다.

Azure 솔루션은 애플리케이션이 메시지를 큐에 게시하는 것처럼 메시지를 게시할 수 있는 Service Bus 토픽을 구현할 수 있습니다. 메시지에는 애플리케이션 정의 사용자 지정 속성 형식의 메타데이터가 포함될 수 있습니다. Service Bus 구독을 토픽과 연결할 수 있으며 구독은 해당 속성에 따라 메시지를 필터링할 수 있습니다. 애플리케이션이 토픽에 메시지를 보내면 메시지가 소비자가 읽을 수 있는 적절한 구독으로 전달됩니다. 소비자 프로세스는 메시지 큐에 사용하는 것과 동일한 의미 체계를 사용하여 구독에서 메시지를 검색할 수 있습니다. (구독은 논리적 큐입니다.) 이 다이어그램에서는 Service Bus 토픽 및 구독을 사용하여 우선 순위 큐를 구현하는 방법을 보여 줍니다.

Service Bus 토픽 및 구독을 사용하여 우선 순위 큐를 구현하는 방법을 보여 주는 다이어그램

앞의 다이어그램에서 애플리케이션은 여러 메시지를 만들고 각 메시지에서 라는 Priority 사용자 지정 속성을 할당합니다. Priority의 값은 또는 Low입니다High. 애플리케이션은 이러한 메시지를 토픽에 게시합니다. 토픽에는 속성에 따라 메시지를 필터링하는 두 개의 연결된 구독이 있습니다 Priority . 한 구독은 속성이 Priority 로 설정된 메시지를 허용합니다 High. 다른 하나는 속성이 Priority 로 설정된 메시지를 허용합니다 Low. 소비자 풀은 각 구독에서 메시지를 읽습니다. 우선 순위가 높은 구독에는 더 큰 풀이 있으며, 이러한 소비자는 우선 순위가 낮은 풀의 컴퓨터보다 사용 가능한 리소스가 더 많은 더 강력한 컴퓨터에서 실행 중일 수 있습니다.

이 예제에서는 우선 순위가 높고 낮은 메시지를 지정하는 데 특별한 것은 없습니다. 각 메시지의 속성으로 지정된 레이블일 뿐입니다. 특정 구독으로 메시지를 전송하는 데 사용됩니다. 추가 우선 순위가 필요한 경우 이러한 우선 순위를 처리하기 위해 더 많은 구독 및 소비자 프로세스 풀을 만드는 것이 비교적 쉽습니다.

GitHub의 PriorityQueue 솔루션은 이 방법을 기반으로 합니다. 이 솔루션에는 PriorityQueueConsumerHighPriorityQueueConsumerLow라는 이름의 Azure Function 프로젝트가 포함됩니다. 이러한 Azure Function 프로젝트는 트리거 및 바인딩을 통해 Service Bus와 통합됩니다. 에 정의된 ServiceBusTrigger 다른 구독에 연결하고 들어오는 메시지에 반응합니다.

public static class PriorityQueueConsumerHighFn
{
    [FunctionName("HighPriorityQueueConsumerFunction")]
    public static void Run(
      [ServiceBusTrigger("messages", "highPriority", Connection = "ServiceBusConnection")]string highPriorityMessage,
      ILogger log)
    {
        log.LogInformation($"C# ServiceBus topic trigger function processed message: {highPriorityMessage}");
    }
}

관리자는 Azure App Service 함수를 스케일 아웃할 수 있는 인스턴스 수를 구성할 수 있습니다. Azure Portal Scale Out 제한 적용 옵션을 구성하고 각 함수에 대한 최대 스케일 아웃 제한을 설정하여 이 작업을 수행할 수 있습니다. 일반적으로 함수보다 PriorityQueueConsumerLow 함수의 인스턴스가 PriorityQueueConsumerHigh 더 많이 있어야 합니다. 이 구성을 사용하면 우선 순위가 높은 메시지를 낮은 우선 순위 메시지보다 더 빨리 큐에서 읽을 수 있습니다.

또 다른 프로젝트인 PriorityQueueSender에는 30초마다 실행되도록 구성된 시간 트리거 Azure 함수가 포함되어 있습니다. 이 함수는 출력 바인딩을 통해 Service Bus와 통합되고 우선 순위가 낮은 메시지와 높은 우선 순위 메시지의 일괄 처리를 개체로 IAsyncCollector 보냅니다. 함수는 및 PriorityQueueConsumerLow 함수에서 사용하는 구독과 연결된 토픽에 PriorityQueueConsumerHigh 메시지를 게시할 때 다음과 같이 사용자 지정 속성을 사용하여 Priority 우선 순위를 지정합니다.

public static class PriorityQueueSenderFn
{
    [FunctionName("PriorityQueueSenderFunction")]
    public static async Task Run(
        [TimerTrigger("0,30 * * * * *")] TimerInfo myTimer,
        [ServiceBus("messages", Connection = "ServiceBusConnection")] IAsyncCollector<ServiceBusMessage> collector)
    {
        for (int i = 0; i < 10; i++)
        {
            var messageId = Guid.NewGuid().ToString();
            var lpMessage = new ServiceBusMessage() { MessageId = messageId };
            lpMessage.ApplicationProperties["Priority"] = Priority.Low;
            lpMessage.Body = BinaryData.FromString($"Low priority message with Id: {messageId}");
            await collector.AddAsync(lpMessage);

            messageId = Guid.NewGuid().ToString();
            var hpMessage = new ServiceBusMessage() { MessageId = messageId };
            hpMessage.ApplicationProperties["Priority"] = Priority.High;
            hpMessage.Body = BinaryData.FromString($"High priority message with Id: {messageId}");
            await collector.AddAsync(hpMessage);
        }
    }
}

다음 단계

이 패턴을 구현할 때 다음 리소스가 유용할 수 있습니다.

  • GitHub에서 이 패턴을 보여 주는 샘플입니다.

  • 비동기 메시징 입문서. 요청을 처리하는 소비자 서비스에서 해당 요청을 게시한 애플리케이션의 인스턴스에 응답을 보내야 할 수도 있습니다. 이 문서에서는 요청/응답 메시징을 구현하는 데 사용할 수 있는 전략에 대한 정보를 제공합니다.

  • 자동 크기 조정 지침. 큐의 길이에 따라 큐를 처리하는 소비자 프로세스 풀의 크기를 조정할 수 있습니다. 이 전략은 특히 우선 순위가 높은 메시지를 처리하는 풀의 성능을 향상시키는 데 도움이 될 수 있습니다.

다음 패턴은 이 패턴을 구현할 때 유용할 수 있습니다.

  • 경쟁 소비자 패턴 큐의 처리량을 늘리려면 동일한 큐에서 수신 대기하고 작업을 병렬로 처리하는 여러 소비자를 구현할 수 있습니다. 이러한 소비자는 메시지를 위해 경쟁하지만 하나만 각 메시지를 처리할 수 있어야 합니다. 이 문서에서는 이 방법을 구현할 때의 이점과 단점에 대한 자세한 정보를 제공합니다.

  • 제한 패턴. 큐를 사용하여 제한을 구현할 수 있습니다. 우선 순위 메시징을 사용하여 중요한 애플리케이션 또는 높은 가치의 고객이 실행하는 애플리케이션의 요청이 덜 중요한 애플리케이션의 요청보다 우선 순위가 지정되도록 할 수 있습니다.