《深入RabbitMQ》讀書(shū)筆記
- 在消息隊(duì)列中晶框,事件通過(guò)消息總線發(fā)布到消費(fèi)者應(yīng)用程序拐纱,每個(gè)事件都執(zhí)行自己特有的任務(wù)于毙。但是皮迟,如果沒(méi)有一個(gè)標(biāo)準(zhǔn)化的消息格式搬泥,我們就難以預(yù)測(cè)特定的消息類(lèi)型如何被序列化以及這些消息包含了哪些具體數(shù)據(jù)。
- 為了提高消息格式的復(fù)用性伏尼,AMQP協(xié)議的
Basic.Properties
數(shù)據(jù)結(jié)構(gòu)提供了一種標(biāo)準(zhǔn)的消息格式忿檩,通過(guò)AMQP協(xié)議發(fā)布到RabbitMQ的每條消息都包含這一結(jié)構(gòu),這使得消費(fèi)者應(yīng)用程序可以進(jìn)行自動(dòng)反序列化消息爆阶,在處理消息之前驗(yàn)證消息的來(lái)源以及類(lèi)型等等燥透。
Basic.Properties的屬性
- content-type 指定消息類(lèi)型(mime-types)便于序列化/反序列化
- content-encoding 消息體使用某種特殊的方式進(jìn)行壓縮或者編碼
- message-id 和 correlation-id 唯一標(biāo)識(shí)消息和消息響應(yīng),用于實(shí)現(xiàn)消息跟蹤
- timestamp 減少消息大小辨图,描述消息創(chuàng)建時(shí)間
- expiration 表明消息過(guò)期
- delivery-mode 在RabbitMQ中表明將消息寫(xiě)入磁盤(pán)或者內(nèi)存隊(duì)列
- app-id 和 user-id 幫助追蹤出現(xiàn)問(wèn)題的消息發(fā)布者應(yīng)用程序
- type 定義發(fā)布者和消費(fèi)者之間的契約
- reply-to 實(shí)現(xiàn)響應(yīng)消息的路由
- headers 映射表定義字有格式的屬性兽掰、實(shí)現(xiàn)RabbitMQ路由
本文中對(duì)于“契約”的定義:一種確定消息格式和內(nèi)容的規(guī)范。通常用來(lái)描述API徒役、對(duì)象和系統(tǒng)的預(yù)定義規(guī)范孽尽。契約規(guī)范中通常包含有關(guān)發(fā)送和接收消息的精確信息,例如數(shù)據(jù)類(lèi)型忧勿、格式以及各種需要遵守的條件杉女。
content-type
- 通過(guò)RabbitMQ發(fā)布的消息,我們很容易對(duì)它進(jìn)行復(fù)用鸳吸。例如:最初的消費(fèi)者應(yīng)用程序是使用Python編寫(xiě)的熏挎,但不久之后,使用PHP晌砾、JAVA和C語(yǔ)言編寫(xiě)的程序同樣成為的消息的消費(fèi)者坎拐。
- 當(dāng)消息格式中沒(méi)有對(duì)消息體內(nèi)容的描述時(shí),應(yīng)用程序會(huì)傾向使用一種隱式契約养匈,這種隱式契約天生容易出錯(cuò)(例如哼勇,Python程序使用pickle序列化的數(shù)據(jù)無(wú)法在其他程序中反序列化),所以你的應(yīng)用程序非撑缓酰可能出現(xiàn)問(wèn)題积担。
- 通過(guò)指定消息類(lèi)型,程序員和消費(fèi)者應(yīng)用程序不需要猜測(cè)如何反序列化消息體中的數(shù)據(jù)猬仁,甚至根本就不需要執(zhí)行反序列化操作帝璧。如果你在消費(fèi)者代碼中使用了一個(gè)框架先誉,在消費(fèi)者代碼處理消息之前,通過(guò)框架對(duì)其進(jìn)行預(yù)處理的烁,消息體可以自動(dòng)地被反序列化并加載到你所使用的編程語(yǔ)言的本地?cái)?shù)據(jù)結(jié)構(gòu)中褐耳。從而降低消費(fèi)者應(yīng)用程序代碼的復(fù)雜性
tips:
- 應(yīng)該盡量使用標(biāo)準(zhǔn)的序列化格式例如JSON、Msgpack或XML渴庆。這些格式允許使用任何編程語(yǔ)言編寫(xiě)任意的消費(fèi)者應(yīng)用程序漱病,因?yàn)閿?shù)據(jù)是以這些格式進(jìn)行自我描述的,所以編寫(xiě)潛在的消費(fèi)者應(yīng)用程序會(huì)很容易把曼。并且在程序外部對(duì)消息解碼也更簡(jiǎn)單杨帽。
- 通過(guò)cintent-type屬性指定序列化格式,可以更好地支持未來(lái)的消費(fèi)者應(yīng)用程序-----當(dāng)消費(fèi)者可以自動(dòng)識(shí)別它們所支持的消息格式并選擇性地處理消息時(shí)嗤军,那就不必?fù)?dān)心在使用新的序列化格式并將其路由到相同的隊(duì)列時(shí)會(huì)發(fā)生什么情況注盈。
通過(guò)gzip和 content-encoding屬性壓縮消息大小
- 默認(rèn)情況下,通過(guò)AMQP發(fā)送的消息并不會(huì)被壓縮叙赚。
- 在處理像XML這種過(guò)于繁雜的標(biāo)記語(yǔ)言老客,甚至在消息數(shù)量較大的場(chǎng)景下處理像JSON或YAML等輕量級(jí)數(shù)據(jù)格式時(shí),你的發(fā)布者可以在發(fā)布消息之前壓縮消息震叮,并在收到消息時(shí)進(jìn)行解壓縮胧砰,就像我們使用gzip在服務(wù)器上壓縮網(wǎng)頁(yè)然后在瀏覽器端實(shí)時(shí)解壓這些網(wǎng)頁(yè)之后再進(jìn)行展示一樣。
- 通過(guò)與content-type屬性相結(jié)合苇瓣,content-encoding屬性使消費(fèi)者應(yīng)用程序能夠基于一種明確的契約與發(fā)布者進(jìn)行交互尉间。你可以編寫(xiě)擴(kuò)展性更強(qiáng)的代碼,確保代碼不會(huì)由于消息格式變更而導(dǎo)致意外錯(cuò)誤击罪。例如:在應(yīng)用程序的生命周期中哲嘲,你可能發(fā)現(xiàn)bzip2壓縮更加適合你的消息內(nèi)容。如果你在編寫(xiě)消費(fèi)者應(yīng)用程序來(lái)檢查content-encoding屬性媳禁,則可以拒絕那些不能解碼的消息眠副,并把它們留在隊(duì)列中供其它支持這種解碼方式的消費(fèi)者去消費(fèi)。
使用message-id和correlation-id引用消息
- 在AMQP規(guī)范中竣稽,message-id和correlation-id是“應(yīng)用級(jí)別”的屬性囱怕,并沒(méi)有提供正式的行為定義。這就意味著你可以利用它們實(shí)現(xiàn)任何目的毫别,這兩個(gè)字段允許最多255個(gè)字節(jié)的UTF-8編碼數(shù)據(jù)娃弓,并以未壓縮的方式存儲(chǔ)在Basic.Properties數(shù)據(jù)結(jié)構(gòu)中。
message-id
- 某些消息類(lèi)型(如登錄事件)并不需要與其關(guān)聯(lián)的唯一標(biāo)識(shí)拧烦,但是訂單類(lèi)型的消息可能需要具備這個(gè)唯一標(biāo)識(shí)忘闻。當(dāng)消息流對(duì)系統(tǒng)中的各個(gè)組件進(jìn)行耦合時(shí),message-id屬性使得消息能夠在消息頭中攜帶數(shù)據(jù)從而唯一地識(shí)別該消息恋博。
correlation-id
- 在AMQP規(guī)范中沒(méi)有關(guān)于correlation-id的正式定義齐佳,但是通過(guò)指定該消息的correlation-id為另一條消息關(guān)聯(lián)消息的message-id,可以指定該消息是另一個(gè)消息的響應(yīng)债沮。另一種用法是使用它來(lái)傳遞關(guān)聯(lián)消息的事務(wù)ID或其他類(lèi)似炼吴。
創(chuàng)建事件:timtstamp屬性
- 與message-id和correlation-id一樣,timsstamp屬性也是“應(yīng)用級(jí)別”的屬性疫衩。通過(guò)timestamp屬性來(lái)指定消息的創(chuàng)建事件硅蹦,消費(fèi)者可以評(píng)估消息投遞過(guò)程的性能、決定是否處理消息闷煤、丟棄消息童芹、甚至對(duì)應(yīng)用程序發(fā)布警報(bào)消息。
tips:
- 時(shí)間戳沒(méi)有上下文鲤拿,因此建議在所有消息中使用統(tǒng)一的時(shí)區(qū)假褪。
expiration:消息自動(dòng)過(guò)期
- 如果消息沒(méi)有被消費(fèi),expiration概述RabbitMQ何時(shí)應(yīng)該丟棄消息近顷。expiration屬性在AMQP的規(guī)范定義中比較奇怪:“用于實(shí)現(xiàn)生音,但沒(méi)有正式的行為”。這意味著RabbitMQ可以提供任何它認(rèn)為合理的實(shí)現(xiàn)方式窒升。同時(shí)缀遍,expiration的格式是一個(gè)短字符串,最多允許255個(gè)字符饱须,而代表時(shí)間單位的另一個(gè)屬性timstamp則是一個(gè)整數(shù)值域醇。
- 由于規(guī)范中沒(méi)有給出明確說(shuō)明,當(dāng)使用不同的消息代理服務(wù)器甚至同一消息代理服務(wù)器的不同版本時(shí)蓉媳,expiration可能會(huì)有不同的含義歹苦。想要利用expiretion屬性來(lái)實(shí)現(xiàn)RabbitMQ消息的自動(dòng)過(guò)期,必須把一個(gè)UNIX時(shí)間戳存儲(chǔ)為字符串督怜。
- 使用expiration屬性時(shí)殴瘦,如果把一個(gè)已經(jīng)過(guò)期的消息發(fā)布到服務(wù)器,那么這條消息不會(huì)被路由到任何隊(duì)列号杠,而是直接被丟棄蚪腋。
tips:
- RabbitMQ3.0以上才支持expiration屬性。
使用delvery-mode平衡速度和安全性
- delivery-mode屬性是一個(gè)字節(jié)字段姨蟋,像消息代理服務(wù)器表明在將消息投遞到任何正在等待的消費(fèi)者之前屉凯,你希望先將它持久化到磁盤(pán)上。delivery-mode屬性有兩個(gè)可能的值:1 代表非持久化消息眼溶,2 代表持久化消息悠砚。
- 消息的持久性與隊(duì)列的持久性(durable)
- 隊(duì)列的持久性(durable)告訴RabbitMQ該隊(duì)列在重新啟動(dòng)RabbitMQ服務(wù)器或集群之后是否仍然有效。
- 只有消息的delivery-mode為2時(shí)堂飞,才會(huì)向RabbitMQ指定消息是否應(yīng)該被持久化灌旧。
- 一個(gè)隊(duì)列可能包含持久化和為持久化的消息
使用app-id和user-id驗(yàn)證消息來(lái)源
- app-id和user-id屬性提供了關(guān)于消息的另一層信息绑咱,并且有很多潛在的用途
app-id
- app-id屬性在AMQP規(guī)范中定義為“短字符串”,最多允許255個(gè)UTF8字符枢泰,如果應(yīng)用程序采用的時(shí)以帶版本的API為中心的設(shè)計(jì)描融,那么在生成消息時(shí)可以使用app-id傳遞特定的版本號(hào),在處理消息之前檢查app-id允許應(yīng)用程序丟棄那些來(lái)源不明或者不受支持的消息
- app-id的另一個(gè)屬性是收集統(tǒng)計(jì)數(shù)據(jù)衡蚂。例如窿克,如果你使用消息來(lái)傳遞登錄事件,則可以將app-id設(shè)置為觸發(fā)登錄事件的平臺(tái)和應(yīng)用程序版本毛甲。在一個(gè)需要同時(shí)支持web端年叮、桌面端和移動(dòng)端應(yīng)用的環(huán)境中,如果希望跟蹤并統(tǒng)計(jì)各個(gè)平臺(tái)的登錄數(shù)據(jù)玻募,使用這種方式我們甚至不需要檢查消息體只损。
- 如果一個(gè)新的消息發(fā)布者錯(cuò)誤地使用了與現(xiàn)有發(fā)布者應(yīng)用程序相同的Exchange和routing_key時(shí),通過(guò)app-id可以更容易地追蹤惡意消息的來(lái)源
user-id
- 在需要驗(yàn)證用戶(hù)身份時(shí)补箍,可以使用user-id屬性來(lái)標(biāo)識(shí)已登錄的用戶(hù)改执。但大多數(shù)情況下,并不推薦這種做法坑雅。RabbitMQ會(huì)根據(jù)發(fā)布消息的RabbitMQ用戶(hù)信息檢擦每條已發(fā)布消息的user-id屬性值辈挂,如果這兩個(gè)值不匹配,那么該消息會(huì)被拒絕裹粤。
使用type屬性獲取明細(xì)
- AMQP規(guī)范的0-9-1版本將type屬性定義為“消息類(lèi)型名稱(chēng)”终蒂,它用來(lái)描述消息中的內(nèi)容。
- 像JSON和XML這樣的自描述格式被一些人認(rèn)為太冗長(zhǎng)了遥诉。它們可能在網(wǎng)絡(luò)傳輸或者內(nèi)存存儲(chǔ)上帶來(lái)不必要的開(kāi)銷(xiāo)拇泣,序列化和反序列化相較一些語(yǔ)言也比較慢。當(dāng)消息體沒(méi)有以自描述格式進(jìn)行序列化(例如Apache Thrift矮锈、ProtoBuf這樣的序列化格式)霉翔,這些二進(jìn)制編碼的消息格式不是自描述的,需要依賴(lài)外部定義的文件來(lái)進(jìn)行序列化和反序列化苞笨。這時(shí)债朵,可以通過(guò)type屬性指定記錄類(lèi)型或者外部定義文件,如果無(wú)法正確訪問(wèn)處理消息所需的.thrift或.proto文件瀑凝,消費(fèi)者就能夠拒絕這些消息序芦。
使用reply-to實(shí)現(xiàn)動(dòng)態(tài)工作流
- AMQP規(guī)范中,reply-to屬性被指定用于應(yīng)用程序粤咪,但是沒(méi)有規(guī)定的行為谚中,他還有一個(gè)附加說(shuō)明:使用reply-to可以構(gòu)架一個(gè)用來(lái)回復(fù)消息的私有響應(yīng)隊(duì)列。
- 盡管在AMQP規(guī)范中沒(méi)有說(shuō)明私有響應(yīng)隊(duì)列的確切定義,但是該屬性可以在最初發(fā)布消息的相同Exchange中攜帶特定的隊(duì)列名稱(chēng)或者routing_key宪塔,這些隊(duì)列名稱(chēng)或者routing_key可以用于回復(fù)消息磁奖。
使用消息頭定義頭屬性
- headers是一個(gè)鍵值對(duì)映射表,允許用戶(hù)自定義任何的key/value蝌麸。鍵可以是ASCII或者Unicode字符串点寥,最大長(zhǎng)度為255個(gè)字符艾疟。值可以是任何有效的AMQP值類(lèi)型来吩。
- headers屬性允許添加任何你想要的數(shù)據(jù)到消息頭中。除此之外蔽莱,它還具有另一個(gè)獨(dú)特的功能:RabbitMQ可以根據(jù)headers表中填充值來(lái)進(jìn)行消息的路由弟疆,而不需要依賴(lài)于routing_key
優(yōu)先級(jí)屬性
- 截至3.5.0版本,RabbitMQ已經(jīng)按照AMQP規(guī)范實(shí)現(xiàn)了priority字段盗冷,它的取值范圍是一個(gè)介于0~9之間的整數(shù)怠苔,用于指定隊(duì)列中消息的優(yōu)先級(jí)。
- 如果首先發(fā)布一條優(yōu)先級(jí)為9的消息仪糖,隨后再發(fā)布一條優(yōu)先級(jí)為0的消息柑司,則新連接的消費(fèi)者將會(huì)先接收到優(yōu)先級(jí)為0的消息
tips:
- RabbitMQ將priority字段實(shí)現(xiàn)為無(wú)符號(hào)字節(jié),所以?xún)?yōu)先級(jí)可以是0到255之間的任意值锅劝,但最好將取值范圍限制在0到9之間以保證規(guī)范性攒驰。
不能使用的屬性: cluster-id/reserved
- cluster-id屬性是AMQP 0-8中定義的,但隨后被刪除故爵,RabbitMQ從未實(shí)現(xiàn)過(guò)關(guān)于改屬性的任何行為
玻粪。 - AMQP 0-9-1將cluster-id屬性重新命名為reserved,并聲明它必須為空诬垂,雖然RabbitMQ目前沒(méi)有根據(jù)規(guī)范要求它是空的劲室,但是最好規(guī)避這個(gè)屬性。
總結(jié)
屬性 | 類(lèi)型 | 用途 | 使用建議或特殊用法 |
---|---|---|---|
app-id | short-string | 應(yīng)用程序 | 用于發(fā)布消息的應(yīng)用程序 |
content-encoding | short-string | 應(yīng)用程序 | 指定消息體是否以某種特殊方式編碼结窘,如zlib很洋、deflate或Base64 |
content-type | short-string | 應(yīng)用程序 | 使用mime-types指定消息體的類(lèi)型 |
correlation-id | short-string | 應(yīng)用程序 | 如果消息引用了某個(gè)其他消息或具有唯一標(biāo)識(shí)的項(xiàng)目,那么correlation-id可以用來(lái)指定這種引用關(guān)系 |
delivery-mode | octet | RabbitMQ | 值為1告訴RabbitMQ可以將消息保存在內(nèi)存中隧枫;值為2表示它也應(yīng)該被寫(xiě)入磁盤(pán) |
expiration | short-string | RabbitMQ | 用文本字符串表示的紀(jì)元時(shí)間或者UNIX時(shí)間戳喉磁,表示消息的過(guò)期時(shí)間 |
headers | table | 應(yīng)用程序/RabbitMQ | 一個(gè)自由格式的鍵值表,可以使用它來(lái)添加消息相關(guān)的附加元數(shù)據(jù)悠垛;RabbitMQ也可以基于它進(jìn)行路由 |
message-id | short-string | 應(yīng)用程序 | 唯一的標(biāo)識(shí)符线定,例如在應(yīng)用程序中可以使用uuid來(lái)標(biāo)識(shí)消息 |
priority | octet | RabbitMQ | 隊(duì)列中標(biāo)識(shí)消息的優(yōu)先順序 |
timestamp | timestamp | 應(yīng)用程序 | 用文本字符串表示的紀(jì)元時(shí)間或者UNIX時(shí)間戳,表示消息的創(chuàng)建時(shí)間 |
type | short-string | 應(yīng)用程序 | 一個(gè)文本字符串确买,用于表示應(yīng)用程序中描述消息或有效負(fù)載的類(lèi)型 |
user-id | short-string | 應(yīng)用程序/RabbitMQ | 一個(gè)自由格式的字符串斤讥,如果啟用該屬性锅减,RabbitMQ會(huì)驗(yàn)證當(dāng)前連接的用戶(hù),若不匹配則丟棄消息 |