Priority-Queue模式

Priority Queue模式

優先級隊列模式:將請求根據優先級進行分流,令高優先級的請求能夠比低優先級的請求更快的接收和處理。優先級隊列模式對於針對需要根據不同的客戶端來實現不同的服務級別的情況下十分實用。

問題

應用可能會將某些指定的任務代理到一些其他的服務或應用上面。比如,會存在需要執行一些後臺處理,或者與其他應用或者服務集成的情況。在雲環境中,通常使用消息隊列來代理任務和後臺的處理。在很多時候,服務所接收的請求的順序並不是十分重要的。然而,在一些特殊的場景下,是需要對特殊的一些請求進行優先處理的。這些請求需要比其它請求能夠更優先的來處理。

解決方案

隊列通常來都是屬於FIFO的結構,消費者的消費順序,通常來說和收到信息的順序也是一樣的。然而,有些消息隊列是支持優先級處理的。應用在發送消息的時候,會將消息附加一個優先級,然後再添加到消息隊列中,這樣高優先級的消息會比低優先級的信息優先處理。圖1說明了優先級消息隊列的處理方式。
這裏寫圖片描述

圖1
使用支持消息優先級的隊列機制

很多的消息隊列實現都是支持多個消費者的(詳見Competing-Consumers模式,並且消費者可以根據需求向上或者向下擴展的。

在那些不支持優先級消息隊列的系統中,一種可選擇的方案就是在爲不同的優先級額外維護一個單獨的隊列。每個隊列擁有各自的消費者。高優先級的隊列擁有更多的消費者,並且比低優先級的隊列享有更好的硬件。圖2展示了該種實現。

這裏寫圖片描述

圖2
爲每一種優先級單獨維護一條優先級隊列

該策略的另一個變種是僅有一個消費者的集合,但是消費者會優先檢查高優先級的隊列,當高優先級的消息隊列存在消息的時候,優先消費高優先級的消息,然後再去消費低優先級的消息。使用一個消費者集合消費多個隊列和每一個消費者單獨消費一個隊列在語義上還是有所不同的。

在僅僅使用一個消費者集合的方法中,高優先級的消息會總是比低優先級的消息優先接收和處理的。理論上來說,很低優先級的消息可能會一直被推遲無法執行的。但是在多個消費者集合的方法中,低優先級的消息仍然是可以消費掉的。只是處理的速度要比高優先級隊列的速度要慢(具體的速度區別,要取決於消費池的大小和可用資源的差別)。

使用優先級隊列技術可以帶來以下好處:

  • 優先級隊列可以令應用根據業務上的需求來根據可用性和性能來定製優先級。比如需要領服務爲指定的客戶提供不同的服務級別的情況下。
  • 優先級隊列可以有效減少操作性消耗。在單一隊列方法中,開發者在必要的情況下,可以削減消費者的數量。高優先級的消息將仍然優先處理(儘管處理的更慢),而低優先級的消息可能推遲更久。如果開發者實現了多個消費者隊列的方法並每個隊列使用單獨的消費者,開發者可以減少低優先級隊列的消費者數量,甚至暫時掛起很低優先級的隊列,爲高優先級隊列讓出更多的資源。
  • 多消息隊列的方法可以幫助最大化應用的性能,而且可以較爲容易的通過分區進行橫向擴展。舉例來說,一些關鍵的任務,可以配置高優先級,由接受者立刻處理,而不那麼重要的任務,可以由接受者在不太繁忙的情況下運行。

實現優先級隊列需要考慮的一些問題

在考慮實現優先級隊列的時候需要考慮如下問題:

  • 爲解決方案定義不同的優先權。舉個例子,高優先級可能意味着消息應該在10秒內處理。根據需求來處理高不同先級的消息,併爲不同優先級的情況分配合適的資源。
  • 實現優先級隊列需要決定是否所有高優先級的請求必須在低優先級的請求之前執行。如果消息都是由一個消費池處理的話,可能需要實現一些機制來搶佔執行,以便在高優先級消息可用的情況下,並延遲當前對低優先級任務的處理,優先處理高優先級的任務。
  • 在使用多個隊列的方法時,如果沒有針對每個隊列使用單獨的消費池,而是使用的是一個消費池來監聽所有的消息隊列的話,那麼消費者必須實現算法來保證消費者總是優先處理高優先級的隊列,而不是低優先級的隊列。
  • 實現優先級隊列的時候還需要監控低優先級和高優先級隊列的處理消息的速度,來保證隊列中消息的處理達到開發者預期的速度。
  • 如果開發者需要保證低優先級的任務也需要執行的話,可以考慮使用多個消息隊列,每個消息隊列都有自己的消費池的方法。當然,如果一個隊列支持消息的優化,能夠根據消息存在的時間動態的調整消息的優先級的話,也是可以的。但是這種情況下,更多的依賴的是消息隊列本身的特性了。
  • 當在系統已經定義了少量的優先級的情況下,爲每個優先級,使用單獨的隊列較爲合適。
  • 消息的優先級可能是由系統的邏輯來決定的。舉個例子,相對於明確的高優先級和低優先級的消息,很多系統可能會根據業務的情況將消息定義爲“付費客戶”或者“非付費客戶”。開發者的系統可以根據情況來爲付費用戶分配更多的資源,而爲非付費用戶提供較少的資源。
  • 查詢和處理隊列中的消息可能會帶來一些額外的費用(有些商業消息系統會在每次發送和接收消息的時候收取少量的費用)。如果使用多個多個隊列的話,這些代價也會隨之增加。
  • 實現優先級隊列模式的時候,可以考慮根據隊列的長度來動態調整消費池的大小。想了解更多的信息,可以參考Autoscaling Guidance.

何時使用優先級隊列模式

優先級隊列模式十分適合以下場景:

  • 當系統必須針對不同的任務使用不同優先級來處理的時候,可以考慮使用優先級模式。
  • 當系統的不同的用戶需要提供不同級別的服務的時候,可以考慮使用優先級模式。

使用舉例

Windows Azure並不提供那種能夠通過排序自動調整消息優先級的隊列機制。但是,Windows Azure是提供Windows Azure Service Bus主題和訂閱的,Windows Azure的服務總線提供隊列服務,同時還支持對消息進行過濾等很多靈活的能力,可以很好的支持優先級隊列的實現。

Windows Azure的解決方案可以將實現一個服務總線,指定一個主題,來支持應用以隊列的方式發送消息。消息可以包含應用所自定義的格式的元數據。服務總線的訂閱可以根據其主題相關聯,訂閱可以根據他們自己的一些屬性對消息進行過濾。當應用將消息發送到某個主題時,消息會根據消費者的訂閱進行轉發到指定的消費者。消費者通過訂閱來獲取消息,就如同從消息隊列中獲取消息是一樣。

圖3展示了一個使用了Windows Azure Service Bus主題和訂閱的解決方案

這裏寫圖片描述

圖3
基於Windows Azure Service Bus的主題和訂閱的優先級隊列實現

在圖3中,應用創建了一些消息,併爲每個消息指定了一個優先級屬性,爲High或者Low。應用將這些消息發送給某個主題。該主題包含兩個相關的訂閱,通過檢查消息的優先級屬性來過濾消息。一個消息訂閱用來接收優先級屬性爲High的消息,而另一個消息訂閱用來接收優先級屬性爲Low的消息。消費池會讀取每一個訂閱中的消息。高優先級的訂閱配置了更大的消費池,這些消費者可以運行在性能更加,使用了更多資源的集羣上,而低優先級的消費者運行在性能稍差的集羣上。
需要注意的是,在該例子中,我們並沒有爲高優先級或者低優先級做特別的設計。它們僅僅是以標籤存在於每個消息中的屬性之中,並且只是用來將消息轉發到對應的訂閱源的。如果需要增加額外的需求,開發者也可以相對輕鬆的定製其他的訂閱和消費池。

本文也包含了一個可用的PriorityQueue解決方案。該方案中包含了2個優先級角色,分別是PriorityQueue.HighPriorityQueue.Low。他們都繼承於類PriorityWorkerRole,其中OnStart()方法中包含了一些函數用來連接到特定的訂閱源。優先級爲PriorityQueue.HighPriorityQueue.Low的消費者連接的是不同的訂閱源。管理員可以爲每個優先級的消費池進行自定義配置。一般情況下,在PriorityQueue.High的消費池中的消費者要更多些。

PriorityWorkerRole類中的Run()方法將ProcessMessage(BrokeredMessage message)作爲回調,消費者(PriorityWorkerRole)收到了消息的時候,進行回調。下面的代碼展示了Run()方法和ProcessMessage(BrokeredMessage message)QueueManager類,則定義在PriorityQueue.Shared項目中,爲使用Windows Azure Service Bus隊列提供一些輔助方法。

public class PriorityWorkerRole : RoleEntryPoint
{
    private QueueManager queueManager;
    ...
    public override void Run()
    {
        // Start listening for messages on the subscription.
        var subscriptionName = CloudConfigurationManager.GetSetting("SubscriptionName");
        this.queueManager.ReceiveMessages(subscriptionName, this.ProcessMessage);
        ...;
    }
    ...
    protected virtual async Task ProcessMessage(BrokeredMessage message)
    {
        // Simulating processing.
        await Task.Delay(TimeSpan.FromSeconds(2));
    }
}

優先級爲PriorityQueue.HighPriorityQueue.Low消費者都覆蓋了PriorityWorkerRole中的ProcessMessage(BrokeredMessage message)方法。下面的代碼是PriorityQueue.High消費者的一個例子。

protected override async Task ProcessMessage(BrokeredMessage message)
{
    // Simulate message processing for High priority messages.
    await base.ProcessMessage(message);
    Trace.TraceInformation("High priority message processed by " +
        RoleEnvironment.CurrentRoleInstance.Id + " MessageId: " + message.MessageId);
}

當應用將消息發送到指定的主題的時候,PriorityQueue.HighPriorityQueue.Low都會訂閱對應主題的消息,應用可以通過指定消息的屬性來配置對應的優先級,消費者然後根據優先級來進行過濾獲取對應的消息。QueueManager中定義的SendBatchAsync()方法來分批的將消息發送給配置的主題。

// Send a low priority batch.
var lowMessages = new List<BrokeredMessage>();
for (int i = 0; i < 10; i++)
{
    var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
    message.Properties["Priority"] = Priority.Low;
    lowMessages.Add(message);
}
this.queueManager.SendBatchAsync(lowMessages).Wait();
...
// Send a high priority batch.
var highMessages = new List<BrokeredMessage>();
for (int i = 0; i < 10; i++)
{
    var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
    message.Properties["Priority"] = Priority.High;
    highMessages.Add(message);
}
this.queueManager.SendBatchAsync(highMessages).Wait();

相關的其他模式

在考慮實現優先級隊列模式的時候,也可以同時參考如下文章:

  • Asynchronous Messaging Primer。消費者服務處理請求需要將依賴信息發送給產生消息的應用實例的。Asynchronous Messaging Primer中描述了更多的關於實現基於request/response消息策略的信息。
  • Competing Consumers模式。爲了增加隊列的吞吐,是很有可能有多個消費者監聽同一個隊列,並且並行的執行任務的。這些消費者會處理消息,但是每個消費者只能處理一個消息。Competing-Consumers模式中描述了關於實現多個消費者的利弊之間的取捨的信息。
  • Throttling模式。開發者可以通過使用隊列來實現節流。優先級消息可以用來確保那些來自於關鍵應用的請求,能夠比那些沒有那麼重要的應用優先處理。
  • Autoscaling Guidance。令消費池的大小隨着隊列的長度進行自動彈性擴展來處理較多的請求。該策略可以幫助增強性能,尤其是當隊列過長時,及時增加高優先級的消費池大小,可以極大的提高吞吐。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章