本文介紹如何使用隊列實現按順序發送和消費訊息。
背景資訊
輕量訊息佇列(原 MNS)提供的隊列(Queue)主要的特點是高可靠、高可用、高並發。每個隊列的資料都會被持久化三份到阿里雲的飛天分布式平台。其中每個隊列至少有兩台伺服器向外提供服務,同時每台伺服器都支援高並發訪問。這些分布式特性導致了輕量訊息佇列(原 MNS)的隊列無法像傳統單機隊列嚴格保證訊息FIFO,只能做到基本有序。
隊列如果同時有多個訊息寄件者(Sender),由於並發和網路延遲等問題,根本無法獲知訊息的實際發送順序和訊息到達伺服器端的真實順序。同理,當有多個接收者並發接收訊息時,其真正的處理順序也不可獲知。
綜上所述,只有一個寄件者(一個進程,可以是多個線程)和一個接收者時,訊息順序才有意義,也只有在這種情況下才能感知和記錄訊息的真實發送和接收順序。
解決方案
基於上述假設,同時為了滿足部分使用者對於訊息消費順序性的要求,設計了以下方案,確保訊息按照使用者發送順序被接收和消費。
訊息在發送端進行染色,加上SeqId(例如:#num#)。
訊息在接收端進行還原,並根據SeqId排序後返回給上層,同時對於已經接收的訊息會有後台線程保證訊息不會被重複消費。
為了避免因為寄件者或者接收者失敗導致SeqId丟失。SeqId會被持久儲存到本地磁碟檔案,或者其他儲存和資料庫,例如OSS、OTS或RDS。

注意事項
本文的主要目的是展示順序訊息的解決方案,不建議不加測試直接用於生產環境。
正常情況下,發送端和接收端的SeqId應該和隊列中的訊息(染色)匹配。當出現刪除隊列重新建立等操作時,請注意磁碟檔案中的SeqId是否和隊列中的真實情況相符,同時建議不要往染色的訊息佇列裡發送非染色訊息。
隊列的訊息有效期間設定過短或者每條訊息的實際處理結果都有可能對訊息有序性造成影響,在您的程式中需要對這些情況所導致的亂序現象進行處理。
範例程式碼
本文以Python版的方案實現為例,下載地址:有序隊列Python範例程式碼(依賴MNS Python SDK)。其中,主要提供了OrderedQueueWrapper類(ordered_queue.py檔案),可以將普通隊列封裝成有序隊列。
OrderedQueueWrapper提供SendMessageInOrder()和ReceiveMessageInOrder()方法:
發送時對訊息進行染色。
接收時還原訊息,並且按順序返回給接收者。
另外,send_message_in_order.py和receive_message_in_order.py提供寄件者和接收者使用OrderedQueueWrapper的程式樣本。
send_message_in_order.py:
#init orderedQueue
seqIdConfig = {"localFileName":"/tmp/mns_send_message_seq_id"} # 指定持久化發送SeqId的磁碟檔案。
seqIdPS = LocalDiskStorage(seqIdConfig)
orderedQueue = OrderedQueueWrapper(myQueue, sendSeqIdPersistStorage = seqIdPS)
orderedQueue.SendMessageInOrder(message)
receive_message_in_order.py:
#init orderedQueue
seqIdConfig = {"localFileName":"/tmp/mns_receive_message_seq_id"} # 指定持久化接收SeqId的磁碟檔案。
seqIdPS = LocalDiskStorage(seqIdConfig)
orderedQueue = OrderedQueueWrapper(myQueue, receiveSeqIdPersistStorage = seqIdPS)
recv_msg = orderedQueue.ReceiveMessageInOrder(wait_seconds)運行方法
配置send_message_in_order.py和receive_message_in_order.py中的配置項g_endpoint、g_accessKeyId、g_accessKeySecret以及g_testQueueName。
運行send_message_in_order.py。
python send_message_in_order.py運行receive_message_in_order.py。
python receive_message_in_order.py發送程式會發送20條訊息,接收程式會按順序消費這20條訊息。

運行ordered_queue.py的測試結果對比普通隊列,運行命令如下:
python ordered_queue.pyordered_queue.py需配置Endpoint和AccessKey。
運行結果
運行結果如下:
非嚴格有序

整體有序,部分相鄰訊息無序,說明單個隊列有多個伺服器在同時服務。
嚴格有序
