脈絡:我的應用程式正在使用訊息傳遞。若某則訊息的資料或請求沒能在特定時間之前被收到,它就沒用了,應該被忽略。
保證送達,但不保證多快#
訊息傳遞實際上保證訊息終究會被送到接收端,但它無法保證要花多久。
若連接收發雙方的網路斷了一週,那送出一則訊息就可能花上一週。訊息傳遞即使在參與者(寄件端、網路、接收端)都不可靠時仍然高度可靠,但在不可靠的環境下,訊息可能花非常久才傳輸完成。
而訊息的內容往往有實務上的有效期限:
發出股票報價請求的呼叫端,若一分鐘左右還沒收到答案,大概就沒興趣了。這意味著請求傳輸不該超過一分鐘,而且答案最好也很快傳回來——一則超過一兩分鐘的報價回覆,大概太舊而不可靠了。
問題在於:
- 寄件端送出訊息後若沒收到回覆,沒有辦法取消或收回這則訊息。
- 接收端當然可以檢查訊息何時送出、太舊就拒收,但不同寄件端在不同情境下對「多久算太久」的看法各不相同,接收端怎麼知道該拒收哪些?
我們需要的,是讓寄件端指定訊息壽命的方式。
解法#
一旦訊息的有效期過了而它仍未被消費,這則訊息就逾期。訊息系統的消費者會忽略逾期訊息——把它當作根本沒被送出過。
多數訊息系統實作會把逾期訊息改道送往 Dead Letter Channel,其他則單純丟棄;這通常可以設定。
一個好比喻#
Message Expiration 就像牛奶盒上的有效期限——過了那天就不該喝。若牛奶還擺在超市貨架上就過期了,店家該把它下架、不再販售;若你手上的牛奶過期了,就該把它倒掉。
同樣地:訊息逾期後,訊息系統就不該再遞送它;若接收端仍然收到了卻無法在逾期前處理完,它就該把訊息丟掉。
絕對時間與相對時間#
Message Expiration 是一個時間戳記(日期與時間),指定訊息會活多久、或何時逾期。設定方式有兩種:
- 絕對——指定訊息逾期的日期與時間
- 相對——指定訊息在逾期前該活多久;訊息系統會用送出時間把相對設定換算成絕對設定
訊息系統負責替不同時區的接收端調整時間戳記、處理日光節約時間的調整,以及任何其他可能讓兩個時鐘對不上的問題。
訊息逾期屬性有一個相關屬性——送出時間(sent time),指出訊息是何時送出的。
訊息的絕對逾期時間戳記必須晚於它的送出時間戳記,否則訊息會立刻逾期。為了避免這個問題,寄件端通常以相對方式指定逾期時間,由訊息系統計算:
逾期時間 = 送出時間 + 存活時間
各種情境下的注意事項#
- 訊息逾期時,訊息系統可能單純丟棄它,也可能把它移到 Dead Letter Channel。
- 若接收端發現自己手上握著一則逾期訊息,應該把它移到 Invalid Message Channel。
- 在 Publish-Subscribe Channel 中,每個訂閱者拿到自己的副本;同一則訊息的某些副本可能成功抵達訂閱者,另一些副本卻在被消費前就逾期了。
在 Request-Reply 中,替回覆訊息設逾期時間可能不太妙——若回覆逾期了,請求的寄件端永遠不會知道這個請求究竟有沒有被收到。
若真的要用回覆逾期,請求方就必須被設計成能處理「預期中的回覆永遠沒來」這種情況。
範例:JMS 與 .NET 的逾期設定
JMS 的 Time-To-Live 參數#
訊息逾期在 JMS 規格中稱為「message time-to-live」。JMS 訊息有預定義屬性 JMSExpiration,但寄件端不該透過 Message.setJMSExpiration(long) 設定它——JMS 供應者會在訊息送出時覆寫該設定。
正確作法是用 MessageProducer(QueueSender 或 TopicPublisher)設定它送出的所有訊息的逾時:
MessageProducer.setTimeToLive(long)也可以對個別訊息設定:
MessageProducer.send(Message message, int deliveryMode, int priority, long timeToLive)其中第四個參數是以毫秒為單位的存活時間。
time-to-live 是相對設定,指定訊息在送出之後多久應該逾期。
.NET 的 TimeToBeReceived 與 TimeToReachQueue#
.NET 的 Message 有兩個指定逾期的屬性:
- TimeToReachQueue——訊息必須在多久內抵達它的目的地佇列;抵達之後,訊息可能無限期地待在佇列裡
- TimeToBeReceived——訊息必須在多久內被接收端消費,這限制的是「傳輸到目的地佇列」加上「待在目的地佇列」的總時間