前面有介紹過消息隊列的好處,通過理解這些好處,我們就能找到其應用場景。
事情大多都有兩面性,而消息隊列,當我們享受它的便捷時,也需要考慮到一些注意事項。
> 這里,舉一些常見的注意事項,使用過程中,因為項目背景與具體情況,還會延伸出更多需要注意的東西,那些需要我們自己在遇到時進行分析,并尋找解決方案,然后記錄下來,充實自己的知識。
# 復雜度
最簡單的架構可能只有 PHP+MYSQL ,此時,復雜度是低的。
我們遇到問題,需要考慮的可能性也會較少,需要檢查的部件也比較少。
而當我們開始使用消息隊列,我們自然而然需要考慮到消息隊列的相關信息,復雜度,自然提升了。
當然,這可能無法避免。
> 比如,生活中,我們使用電風扇,可以吹到涼風。但如果我們還需要制熱,就會購買空調。電風扇若是壞了,我們或許能自己修回去,但若是空調壞了,我們可能需要有一定專業知識,才能修理。這就是復雜度。
**當能力變強的同時,復雜度也會提升,這一點通常無法避免,除非,我們找到更多的方案,再分析方案間的利弊,最后決策**
# 可用性
因為生產者和消費者都依賴于消息隊列,比如訂單系統使用了消息隊列,那么,我們需要考慮到消息隊列的可用性,消息隊列一旦崩潰,訂單系統便不能正常運轉,而任何依賴于訂單系統的功能,也將無法提供服務。
> 雖然,消息隊列出現崩潰的概率相對較低,但在選型初期和設計架構的時候,還是要充分考慮,并盡可能設計好預備方案。
一般而言,常見的消息隊列系統,會通過分布式來提高可用性。
# 消息可靠性
在消息隊列使用過程中,我們可能會遇到這種情況:
- 生產者將消息發出,但消息隊列沒有接收到
這很可能是網絡原因,比如,生產者發出消息,但由于網絡或者消息隊列方面的一些原因,最終,消息沒有被存入指定的隊列中,此時,可能引起消息丟失。
> 一般我們會要求消息隊列返回一個確認信息,告訴生產者,消息放好了。如果,生產者遲遲未能接收到確認信息,則再次發送消息。以此來保證消息不被丟失。
- 消息隊列崩潰或服務器宕機,內存清空,數據丟失
當遇到這種情況,會導致其存儲在內存中的數據丟失,消息的可靠性就得不到保證。
> 一般我們會通過持久化來保證消息不丟失,不同的消息隊列系統,持久化的方式未必一樣,具體的使用,將會在后面章節詳細介紹。
- 消費者崩潰,無法正確處理消息
還有一種情況,當消費者從消息隊列中讀取出消息,消息狀態變成了 reserved 或者,干脆直接在隊列中被刪除,而此時,消費者卻在執行后面的程序時崩潰了,導致業務流程沒有順利執行,但消息已經被取出。
**這也是一種數據丟失的現象。**
> 一般此時,也需要消費者與消息隊列之間建立反饋機制,一定要當在消費者處理完消息之后,給消息隊列發送明確的刪除指令,才能刪除消息。
另外消息隊列這邊,可以建立 TTR( time-to-run , 超時重發)機制,如果隊列中處于 reserved 狀態的消息過了一定時間還未被刪除,可以將之丟回 ready 狀態,繼續被消費者讀取
# 順序消費
**首先,順序消費和順序保證,是兩回事**
順序保證是指同類型的消息被消費的先后順序,而順序消費,是指,當一個消息,需要被多個消費者按一定順序先后消費。
例如,當消息隊列中存放一個訂單信息時,我們有以下消費者:
- 發短信
- 發郵件
- 發 APP 推送
而我們必須先發短信、再發郵件、再發 APP 推送,不可提前,不可押后。
這就是一種順序消費的場景。
一般來講,有兩種比較常見的解決方案:
- 合并消費者
這種方式一般不推薦,但前期實現起來復雜度較低。
它意味著將多個消費者合并為一個消費者,再順序執行。
雖然可以實現邏輯,但本身卻降低了可擴展性,提高了耦合,這對未來維護項目而言,會增加隱性成本。
- 控制消費狀態
還有一種方案,我們在生產者中放入短信消息,當短信消息被處理完后,在消費者中往郵件隊列放入消息,以此類推。
> 但這也會引起問題,比如,當我們將短信--->郵件--->推送串聯好,但我們又要在短信和郵件之間,插入一個新的消費者,此時,我們除了要新增消費者以外,還需要修改短信消費者。
**這種設計就像是一個鏈表,頭是生產者,指向短信,短信指向郵件,此時中間插入一個,需要將短信原本指向郵件的指針,指向新的消費者,而新的消費者則指向郵件。**
# 消息重復
前面有說過,為了保證消息可靠性,我們可能會讓生產者重復發送消息(當遲遲沒有收到消息隊列反饋的時候)。
但是,這種情況,大多數時候是由網絡不穩定引起的。
也就是說,雖然消息隊列的反饋,可能遲到,但并不代表,消息已經丟失了。
此時,生產者再次往消息隊列發送消息,則消息隊列可能會收到兩條重復的消息。
> 這里舉個例子:比如消費者需要為用戶余額加 5 元錢,如果消息重復,就有可能為用戶加上好幾個 5 元。
我們一般通過保證消費者被重復執行的冪等性來避免消息重復帶來的問題。
**冪等性指:多次進行同樣的操作,執行結果都一致**
比如,你輸入的數據在不變的情況下,你的算法應保證處理結果是一致的,特別是在業務邏輯上。
為什么要通過保證消費者的冪等性來避免消息重復?
因為我們無法保證,網絡不出現擁堵,不出現故障。
所以,我們無法保證,生產者不重復往隊列中寫入消息。
因此,比較好的方案,是在消費者上面進行控制。
**比如,冪等性能夠保證,同樣一條消息,盡管被消費者讀取多次,但在現實中產生的影響,卻只有一次。**
至于如此保證冪等性,會是一個比較復雜的問題,我們需要根據項目的實際情況和實際架構,進行具體的設計。
# 一致性
因為消息隊列賦予了異步能力,所以,當我們生產者在調用的時候,并非業務邏輯已經成功執行完。
比如,我們在生產者中調用發短信,實際上,短信并未發出去,只是將要發短信的任務存儲到隊列中,等到被執行。
那么,此時給調用返回的結果,只是調用的結果,并不等于發送成功。
**所以,我們無法以調用結果來判定業務處理結果。**
這個就是暫時不一致性。
這個結果,暫時的,與你所知的不一致。
但,它最終會一致,也就是說,最終,消息隊列內的每一條消息都會被取出來消費。
這個就是最終一致性。