《深入RabbitMQ》讀書筆記
- 在消息隊列中,事件通過消息總線發布到消費者應用程序,每個事件都執行自己特有的任務。但是,如果沒有一個標準化的消息格式,我們就難以預測特定的消息類型如何被序列化以及這些消息包含了哪些具體數據。
- 為了提高消息格式的復用性,AMQP協議的
Basic.Properties
數據結構提供了一種標準的消息格式,通過AMQP協議發布到RabbitMQ的每條消息都包含這一結構,這使得消費者應用程序可以進行自動反序列化消息,在處理消息之前驗證消息的來源以及類型等等。
Basic.Properties的屬性
- content-type 指定消息類型(mime-types)便于序列化/反序列化
- content-encoding 消息體使用某種特殊的方式進行壓縮或者編碼
- message-id 和 correlation-id 唯一標識消息和消息響應,用于實現消息跟蹤
- timestamp 減少消息大小,描述消息創建時間
- expiration 表明消息過期
- delivery-mode 在RabbitMQ中表明將消息寫入磁盤或者內存隊列
- app-id 和 user-id 幫助追蹤出現問題的消息發布者應用程序
- type 定義發布者和消費者之間的契約
- reply-to 實現響應消息的路由
- headers 映射表定義字有格式的屬性、實現RabbitMQ路由
本文中對于“契約”的定義:一種確定消息格式和內容的規范。通常用來描述API、對象和系統的預定義規范。契約規范中通常包含有關發送和接收消息的精確信息,例如數據類型、格式以及各種需要遵守的條件。
content-type
- 通過RabbitMQ發布的消息,我們很容易對它進行復用。例如:最初的消費者應用程序是使用Python編寫的,但不久之后,使用PHP、JAVA和C語言編寫的程序同樣成為的消息的消費者。
- 當消息格式中沒有對消息體內容的描述時,應用程序會傾向使用一種隱式契約,這種隱式契約天生容易出錯(例如,Python程序使用pickle序列化的數據無法在其他程序中反序列化),所以你的應用程序非常可能出現問題。
- 通過指定消息類型,程序員和消費者應用程序不需要猜測如何反序列化消息體中的數據,甚至根本就不需要執行反序列化操作。如果你在消費者代碼中使用了一個框架,在消費者代碼處理消息之前,通過框架對其進行預處理,消息體可以自動地被反序列化并加載到你所使用的編程語言的本地數據結構中。從而降低消費者應用程序代碼的復雜性
tips:
- 應該盡量使用標準的序列化格式例如JSON、Msgpack或XML。這些格式允許使用任何編程語言編寫任意的消費者應用程序,因為數據是以這些格式進行自我描述的,所以編寫潛在的消費者應用程序會很容易。并且在程序外部對消息解碼也更簡單。
- 通過cintent-type屬性指定序列化格式,可以更好地支持未來的消費者應用程序-----當消費者可以自動識別它們所支持的消息格式并選擇性地處理消息時,那就不必擔心在使用新的序列化格式并將其路由到相同的隊列時會發生什么情況。
通過gzip和 content-encoding屬性壓縮消息大小
- 默認情況下,通過AMQP發送的消息并不會被壓縮。
- 在處理像XML這種過于繁雜的標記語言,甚至在消息數量較大的場景下處理像JSON或YAML等輕量級數據格式時,你的發布者可以在發布消息之前壓縮消息,并在收到消息時進行解壓縮,就像我們使用gzip在服務器上壓縮網頁然后在瀏覽器端實時解壓這些網頁之后再進行展示一樣。
- 通過與content-type屬性相結合,content-encoding屬性使消費者應用程序能夠基于一種明確的契約與發布者進行交互。你可以編寫擴展性更強的代碼,確保代碼不會由于消息格式變更而導致意外錯誤。例如:在應用程序的生命周期中,你可能發現bzip2壓縮更加適合你的消息內容。如果你在編寫消費者應用程序來檢查content-encoding屬性,則可以拒絕那些不能解碼的消息,并把它們留在隊列中供其它支持這種解碼方式的消費者去消費。
使用message-id和correlation-id引用消息
- 在AMQP規范中,message-id和correlation-id是“應用級別”的屬性,并沒有提供正式的行為定義。這就意味著你可以利用它們實現任何目的,這兩個字段允許最多255個字節的UTF-8編碼數據,并以未壓縮的方式存儲在Basic.Properties數據結構中。
message-id
- 某些消息類型(如登錄事件)并不需要與其關聯的唯一標識,但是訂單類型的消息可能需要具備這個唯一標識。當消息流對系統中的各個組件進行耦合時,message-id屬性使得消息能夠在消息頭中攜帶數據從而唯一地識別該消息。
correlation-id
- 在AMQP規范中沒有關于correlation-id的正式定義,但是通過指定該消息的correlation-id為另一條消息關聯消息的message-id,可以指定該消息是另一個消息的響應。另一種用法是使用它來傳遞關聯消息的事務ID或其他類似。
創建事件:timtstamp屬性
- 與message-id和correlation-id一樣,timsstamp屬性也是“應用級別”的屬性。通過timestamp屬性來指定消息的創建事件,消費者可以評估消息投遞過程的性能、決定是否處理消息、丟棄消息、甚至對應用程序發布警報消息。
tips:
- 時間戳沒有上下文,因此建議在所有消息中使用統一的時區。
expiration:消息自動過期
- 如果消息沒有被消費,expiration概述RabbitMQ何時應該丟棄消息。expiration屬性在AMQP的規范定義中比較奇怪:“用于實現,但沒有正式的行為”。這意味著RabbitMQ可以提供任何它認為合理的實現方式。同時,expiration的格式是一個短字符串,最多允許255個字符,而代表時間單位的另一個屬性timstamp則是一個整數值。
- 由于規范中沒有給出明確說明,當使用不同的消息代理服務器甚至同一消息代理服務器的不同版本時,expiration可能會有不同的含義。想要利用expiretion屬性來實現RabbitMQ消息的自動過期,必須把一個UNIX時間戳存儲為字符串。
- 使用expiration屬性時,如果把一個已經過期的消息發布到服務器,那么這條消息不會被路由到任何隊列,而是直接被丟棄。
tips:
- RabbitMQ3.0以上才支持expiration屬性。
使用delvery-mode平衡速度和安全性
- delivery-mode屬性是一個字節字段,像消息代理服務器表明在將消息投遞到任何正在等待的消費者之前,你希望先將它持久化到磁盤上。delivery-mode屬性有兩個可能的值:1 代表非持久化消息,2 代表持久化消息。
- 消息的持久性與隊列的持久性(durable)
- 隊列的持久性(durable)告訴RabbitMQ該隊列在重新啟動RabbitMQ服務器或集群之后是否仍然有效。
- 只有消息的delivery-mode為2時,才會向RabbitMQ指定消息是否應該被持久化。
- 一個隊列可能包含持久化和為持久化的消息
使用app-id和user-id驗證消息來源
- app-id和user-id屬性提供了關于消息的另一層信息,并且有很多潛在的用途
app-id
- app-id屬性在AMQP規范中定義為“短字符串”,最多允許255個UTF8字符,如果應用程序采用的時以帶版本的API為中心的設計,那么在生成消息時可以使用app-id傳遞特定的版本號,在處理消息之前檢查app-id允許應用程序丟棄那些來源不明或者不受支持的消息
- app-id的另一個屬性是收集統計數據。例如,如果你使用消息來傳遞登錄事件,則可以將app-id設置為觸發登錄事件的平臺和應用程序版本。在一個需要同時支持web端、桌面端和移動端應用的環境中,如果希望跟蹤并統計各個平臺的登錄數據,使用這種方式我們甚至不需要檢查消息體。
- 如果一個新的消息發布者錯誤地使用了與現有發布者應用程序相同的Exchange和routing_key時,通過app-id可以更容易地追蹤惡意消息的來源
user-id
- 在需要驗證用戶身份時,可以使用user-id屬性來標識已登錄的用戶。但大多數情況下,并不推薦這種做法。RabbitMQ會根據發布消息的RabbitMQ用戶信息檢擦每條已發布消息的user-id屬性值,如果這兩個值不匹配,那么該消息會被拒絕。
使用type屬性獲取明細
- AMQP規范的0-9-1版本將type屬性定義為“消息類型名稱”,它用來描述消息中的內容。
- 像JSON和XML這樣的自描述格式被一些人認為太冗長了。它們可能在網絡傳輸或者內存存儲上帶來不必要的開銷,序列化和反序列化相較一些語言也比較慢。當消息體沒有以自描述格式進行序列化(例如Apache Thrift、ProtoBuf這樣的序列化格式),這些二進制編碼的消息格式不是自描述的,需要依賴外部定義的文件來進行序列化和反序列化。這時,可以通過type屬性指定記錄類型或者外部定義文件,如果無法正確訪問處理消息所需的.thrift或.proto文件,消費者就能夠拒絕這些消息。
使用reply-to實現動態工作流
- AMQP規范中,reply-to屬性被指定用于應用程序,但是沒有規定的行為,他還有一個附加說明:使用reply-to可以構架一個用來回復消息的私有響應隊列。
- 盡管在AMQP規范中沒有說明私有響應隊列的確切定義,但是該屬性可以在最初發布消息的相同Exchange中攜帶特定的隊列名稱或者routing_key,這些隊列名稱或者routing_key可以用于回復消息。
使用消息頭定義頭屬性
- headers是一個鍵值對映射表,允許用戶自定義任何的key/value。鍵可以是ASCII或者Unicode字符串,最大長度為255個字符。值可以是任何有效的AMQP值類型。
- headers屬性允許添加任何你想要的數據到消息頭中。除此之外,它還具有另一個獨特的功能:RabbitMQ可以根據headers表中填充值來進行消息的路由,而不需要依賴于routing_key
優先級屬性
- 截至3.5.0版本,RabbitMQ已經按照AMQP規范實現了priority字段,它的取值范圍是一個介于0~9之間的整數,用于指定隊列中消息的優先級。
- 如果首先發布一條優先級為9的消息,隨后再發布一條優先級為0的消息,則新連接的消費者將會先接收到優先級為0的消息
tips:
- RabbitMQ將priority字段實現為無符號字節,所以優先級可以是0到255之間的任意值,但最好將取值范圍限制在0到9之間以保證規范性。
不能使用的屬性: cluster-id/reserved
- cluster-id屬性是AMQP 0-8中定義的,但隨后被刪除,RabbitMQ從未實現過關于改屬性的任何行為
。 - AMQP 0-9-1將cluster-id屬性重新命名為reserved,并聲明它必須為空,雖然RabbitMQ目前沒有根據規范要求它是空的,但是最好規避這個屬性。
總結
屬性 | 類型 | 用途 | 使用建議或特殊用法 |
---|---|---|---|
app-id | short-string | 應用程序 | 用于發布消息的應用程序 |
content-encoding | short-string | 應用程序 | 指定消息體是否以某種特殊方式編碼,如zlib、deflate或Base64 |
content-type | short-string | 應用程序 | 使用mime-types指定消息體的類型 |
correlation-id | short-string | 應用程序 | 如果消息引用了某個其他消息或具有唯一標識的項目,那么correlation-id可以用來指定這種引用關系 |
delivery-mode | octet | RabbitMQ | 值為1告訴RabbitMQ可以將消息保存在內存中;值為2表示它也應該被寫入磁盤 |
expiration | short-string | RabbitMQ | 用文本字符串表示的紀元時間或者UNIX時間戳,表示消息的過期時間 |
headers | table | 應用程序/RabbitMQ | 一個自由格式的鍵值表,可以使用它來添加消息相關的附加元數據;RabbitMQ也可以基于它進行路由 |
message-id | short-string | 應用程序 | 唯一的標識符,例如在應用程序中可以使用uuid來標識消息 |
priority | octet | RabbitMQ | 隊列中標識消息的優先順序 |
timestamp | timestamp | 應用程序 | 用文本字符串表示的紀元時間或者UNIX時間戳,表示消息的創建時間 |
type | short-string | 應用程序 | 一個文本字符串,用于表示應用程序中描述消息或有效負載的類型 |
user-id | short-string | 應用程序/RabbitMQ | 一個自由格式的字符串,如果啟用該屬性,RabbitMQ會驗證當前連接的用戶,若不匹配則丟棄消息 |