消息队列---消息模型及使用场景
消息隊列
??消息對列是一個存放消息的容器,當我們需要消息的時候就從消息隊列中取出消息使用。消息隊列是分布式系統中重要的組件,使用消息隊列的目的是為了通過異步處理提高系統的性能和削峰值,降低系統的耦合性。目前使用較多的消息隊列有ActiveMQ,RabbitMQ,Kafka,RocketMQ。
1.消息模型
點對點
??消息生產者向消息隊列中發送一個消息之后,只能被一個消費者消費一次。
發布訂閱
??消息生產者向頻道發送一個消息之后,多個消費者可以從該頻道訂閱到這條消息并消費。
發布訂閱模式和觀察者模式有以下不同:
- 觀察者模式中,觀察者和主題都知道對方的存在;而在發布者訂閱者模式中,生產者與消費者不知道對方的存在,他們之間通過頻道進行通信。
- 觀察者模式是同步的,當事件觸發時,主題會調用觀察者的方法,然后等待方法的返回;而發布者訂閱者模式是異步的,生產者向頻道發送一條消息之后,就不需要關心消費者什么時候來訂閱這個消息,可以立即返回。
2.使用場景
異步處理
??生產者將消息發送給消息隊列之后,不需要同步等待消息接收者處理完畢,而是立即返回進行其它操作。消息接收者從消息隊列中訂閱消息后進行異步處理。
??例如在注冊流程中通常需要發送驗證郵件來確保注冊用戶的合法性,可以使用消息隊列使發送驗證郵件的操作異步處理,用戶在填完注冊信息之后就可以完成注冊,而將發送郵件這一消息發送到消息隊列。
??只有在業務流程允許異步處理的情況下才能這么做,例如上面的注冊流程中,如果要求用戶對驗證郵件進行點擊后才能完成注冊的話,就不能使用消息隊列。
流量削峰
??我們先來看下傳統的服務器接受處理請求的流程
??如上圖,在不使用消息隊列服務器的時候,用戶的請求都直接訪問數據庫,在高并發的情況下數據庫的壓力劇增,不僅使得響應很慢,還可能因此掛掉數據庫,導致用戶頁面直接報錯。
??我們看加入消息隊列后的接受處理請求流程有什么變化
??如上圖,在使用消息隊列后,即使在高并發的情況下用戶的請求數據發送給消息隊列之后立即返回,再由消息隊列的消費者進程從消息隊列中獲取數據,異步寫入數據庫。由于消息隊列服務器處理消息速度比數據庫要快很多,因此響應速度得到大幅改善。
??因此我們可以得到消息隊列有很好的削峰作用,通過異步處理,將短時間高并發產生的事務消息存儲在消息隊列中,從而削去高峰期的并發事務。如在某些電商平臺的一些秒殺活動中,合理使用消息隊列可以抵御活動剛開始大量請求涌入對系統的沖擊。
??因為用戶請求數據寫入消息隊列之后就立即返回給用戶了,但是請求數據在后續的業務校驗、寫數據庫等操作中可能失敗。因此使用消息隊列進行異步處理之后,需要適當修改業務流程進行配合,比如用戶在提交訂單之后,訂單數據寫入消息隊列,不能立即返回用戶訂單提交成功,需要在消息隊列的訂單消費者進程真正處理完該訂單之后,甚至出庫后,再通過電子郵件或短信通知用戶訂單成功,以免交易糾紛。這就類似我們平時手機訂火車票等。
系統解耦
??傳統的系統數據傳輸模式
??如上圖,主系統和其他系統的耦合性太強,都是直接調用,稍微有一點改動或者新增模塊,雙方都得改代碼,過于麻煩。
??加入消息隊列后的變化
??如上圖,如果模塊之間不存在直接調用,那么新增模塊或者修改模塊就對其他模塊影響較小,這樣系統的可擴展性無疑更好。
??消息隊列是利用發布者訂閱模式工作,消息發送者(生產者)發布消息,一個或多個消費者訂閱消息。從上圖可以看出消息發送者和消息接收者之間沒有直接耦合,消息發送者將消息發送至分布式消息隊列即結束對消息的處理,消息接收者從分布式消息隊列中獲取該消息后進行后續處理,并不需要知道該消息從何而來。對新增業務,只要對該類消息感興趣,就可以訂閱該消息,對原有的系統和業務沒有任何的影響。從而實現網站業務的可擴展性設計。
??另外為了避免消息隊列服務器宕機造成消息的丟失,會將成功發送到消息隊列的消息存儲在消息生產者服務器上,等消息真正被消費者服務器處理后才會刪除消息。在消息隊列服務器宕機后,生產者服務器會選擇分布式消息隊列集群中的其他服務器發布消息。
使用消息隊列帶來的問題
- 可用性降低:在加入MQ之前,不用考慮MQ服務器掛掉的情況,引入后就需要去考慮,所以可用性降低。
- 復雜性提高:加入MQ之后,你需要保證消息沒有被重復消費,處理消息丟失的情況,保證消息傳遞的順序性等問題。
- 數據一致性:消息隊列帶來的異步確實可以提高系統的響應速度,但是,如果消息的真正消費者沒有正確消費消息,這樣就會導致數據不一致的問題。
解決方案
1.對于可用性問題
??實際項目中發送MQ消息,如果不使用集群,其中MQ機器出現故障宕機,那么MQ消息就不能發送,系統就會崩潰,所以我們需要集群MQ。各種消息中間間的集群方式不同。下面以ActiveMQ的集群為例(Zookeeper+ActiveMQ)如圖:
??服務器向Zookeeper注冊時,Zookeeper會分配序列號,我們認為序列號小的那個就是主服務器,序列號大的那個就是備用服務器。
??當我們的客戶端(Web server)需要訪問服務時,需要連接Zookeeper,獲得指定目錄下的臨時節點列表,也就是已經注冊的服務器信息,獲得序列號小的那臺主服務器的地址,進行后續的訪問操作,以達到總是訪問主服務器的目的。當主服務器發生故障,Zookeeper從指定目錄下刪除對應的臨時節點,同時通知關心這一變化的所有客戶端,高效的傳播這一信息,當下一個請求來的時候,還是連接Zookeeper,但是此時其實是訪問備用MQ服務器。
2.對于復雜性問題
(1)如何保證消息不被重復消費
??要回答這個問題, 必須要知道為什么會出現消息被重復消費,大多都是因為網絡不通導致,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將消息分發給其他的消費者。解決該問題有下面三種思路:
- 如果消息是做數據庫的插入操作,給這個消息做一個唯一的主鍵,那么就算出現重復消費的情況,就會導致主鍵沖突,避免數據庫出現臟數據。
- 如果這個消息是做redis的set操作,那么不用解決,因為無論set幾次結果都是一樣的。set操作本來就是冪等操作。
- 如果上面兩種情況都不行,那么準備一個第三方來做消費記錄,以redis為例,給消息分配一個全局id,只要消費過該消息,將<id,message>以k-v的形式寫入redis。那么消費者開始消費前,先去redis中查看有沒有消費記錄即可。
(2)如何保證消息的可靠傳輸
??保證消息的可靠傳輸就是防止生產者弄丟數據,消息隊列弄丟數據,消費者弄丟數據而已。
??消息隊列一般都會持久化到磁盤,生產者數據丟失的話MQ事務會進行回滾,可以嘗試重新發送。消費者丟失的話一般都是采用了自動確認消息模式導致消費信息被刪,只要改為手動的就好,也就是說消費者消費完之后,調用一個MQ的確認方法就可以。
(3)如何保證從消息隊列里拿到的數據按順序執行
??通過算法,將需要保持先后順序的消息放到同一個消息隊列中,然后只用一個消費者去消費該隊列。
- rabbitMQ:拆分多個queue,每個queue一個consumer,就是多一些queue而已。或者就是一個queue對應一個consumer,然后這個consumer內部用內存隊列做排隊,然后分發給底層不同的worker來處理
- kafka:一個topic,一個partition,一個consumer,內部單線程消費,寫n個內存queue,然后N個線程分別消費一個內存queue即可。
(4)數據是通過push還是pull方式給到消費段,各自有什么利弊
push模型實時性能好,但是因為狀態維護等問題,難以應用到消息中間件的實踐中,因為
在Broker端需要維護consumer的狀態,不好適用Broker去支持大量的consumer的場景。
consumer的消費速度是不一致的,Broker進行推送難以處理不同consumer的狀況
Broker無法處理consumer無法讀取消息的情況,因為不知道consumer的宕機是短暫的還是永久的
另外推送消息(量可能會很大)也會加重consumer的負載或者壓垮consumer
pull模式實現起來相對簡單一點,但是實時性取決于輪訓的頻率,在對實時性要求高的場景不適合使用。
轉載于:https://www.cnblogs.com/yjxyy/p/11147027.html
總結
以上是生活随笔為你收集整理的消息队列---消息模型及使用场景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python实现二叉树的遍历
- 下一篇: oracle函数 INITCAP(c1)