前言
最近,消息隊列(Message Queue ,簡稱 MQ)越來越火。很多公司在用,很多人在用,其重要性不言而喻。
如果讓你回答下面這些問題,你的心中是否有答案了呢?
為什么要用 MQ?
引入 MQ 會多出哪些問題?
如何解決這些問題?
本文將會一一為你解答,這些看似平常卻很有意義的問題。
1. 傳統模式有哪些痛點
痛點 1
有些復雜的業務系統,一次用戶請求可能會同步調用 N 個系統的接口,需要等待所有的接口都返回才能真正的獲取執行結果。
這種同步接口調用的方式總耗時比較長,非常影響用戶體驗。特別是在網絡不穩定的情況下,極容易出現接口超時問題。
痛點 2
很多復雜的業務系統,一般都會拆分成多個子系統。以用戶下單為例,請求會先通過訂單系統,然后分別調用支付系統、庫存系統、積分系統和物流系統。
系統之間耦合性太高,如果調用的任何一個子系統出現異常,整個請求都會異常。對系統的穩定性非常不利。
痛點 3
為了吸引用戶,有時候會搞一些活動,比如秒殺等。
如果用戶少還好,不會影響系統的穩定性。但如果用戶突增,一時間所有的請求都到數據庫,可能會導致數據庫無法承受這么大的壓力,響應變慢或者直接掛掉。
對于這種突然出現的請求峰值,無法保證系統的穩定性。
2. 為什么要用 MQ
對于上面傳統模式的三類問題,使用 MQ 就能輕松解決。
2.1 異步
對于痛點 1 同步接口調用導致響應時間長的問題。使用 MQ 之后,將同步調用改成異步調用,能夠顯著減少系統響應時間。
系統 A 作為消息的生產者,在完成本職工作后就能直接返回結果了。無需等待消息消費者的返回,它們最終會獨立完成所有的業務功能。
這樣能避免總耗時比較長,從而影響用戶的體驗的問題。
2.2 解耦
對于痛點 2 子系統間耦合性太大的問題,使用 MQ 之后,只需要依賴于 MQ。避免了各個子系統間的強依賴問題。
訂單系統作為消息生產者,保證它自己沒有異常即可,不會受到支付系統等業務子系統的異常影響。并且各個消費者業務子系統之間,也互不影響。
這樣就把之前復雜的業務子系統的依賴關系,轉換為只依賴于 MQ 的簡單依賴,從而顯著的降低了系統間的耦合度。
2.3 削峰
對于痛點 3,由于突然出現的請求峰值導致系統不穩定的問題。使用 MQ 后,能夠起到削峰的作用。
訂單系統接收到用戶請求之后,將請求直接發送到 MQ;
然后,訂單消費者從 MQ 中消費消息,做寫庫操作;
當出現請求峰值的情況,由于消費者的消費能力有限,會按照自己的節奏來消費消息。多余請求不處理,保留在 MQ 的隊列中,不會對系統的穩定性造成影響。
3. 引入 MQ 會多出哪些問題
引入 MQ 后讓子系統間耦合性降低了,異步處理機制減少了系統的響應時間。同時能夠有效的應對請求峰值問題,提升了系統的穩定性。
但是,引入 MQ 的同時也會帶來一些問題。
3.1 重復消費問題
重復消費問題可以說是 MQ 中普遍存在的問題,不管你用哪種 MQ 都無法避免。
有哪些場景會出現重復的消息呢?
消息生產者產生了重復的消息;
Kafka 和 RocketMQ 的 offset 被回調了;
消息消費者確認失敗;
消息消費者確認時超時;
業務系統主動發起重試。
如果重復消息不做正確的處理,會對業務造成很大的影響,產生重復數據或者導致數據異常,比如會員系統多開通了一個月的會員等。
3.2 數據一致性問題
很多時候,如果 MQ 的消費者業務處理異常,就會出現數據一致性問題。
舉個例子,一個完整的業務流程是,下單成功之后送 100 個積分。下單寫庫成功了,但是消息消費者在送積分的時候失敗了。這樣就會造成數據不一致的情況,即該業務流程的部分數據寫庫了,另外一部分沒有寫庫。
如果下單和送積分在同一個事務中,要么同時成功,要么同時失敗。這樣不會出現數據一致性問題的。
但由于跨系統調用,為了性能考慮一般不會使用強一致性的方案,而改成達成最終一致性即可。
3.3 消息丟失問題
同樣消息丟失問題,也是 MQ 中普遍存在的問題,不管你用哪種 MQ 都無法避免。
有哪些場景會出現消息丟失問題呢?
生產者產生消息時,由于網絡原因發送到 MQ 失敗了;
MQ 服務器持久化,存儲磁盤時出現異常;
Kafka 和 RocketMQ 的 offset 被回調時,略過了很多消息;
消費者剛讀取消息,已經 ACK 確認,但業務還沒處理完,服務就被重啟了。
導致消息丟失問題的原因挺多的,生產者、MQ 服務器、消費者都有可能產生問題。我在這里就不一一列舉了。
最終的結果會導致消費者無法正確的處理消息,而導致數據不一致的情況。
3.4 消息順序問題
有些業務數據是有狀態的,比如訂單有下單、支付、完成、退貨等狀態。如果訂單數據作為消息體,就會涉及順序問題了。
例如消費者收到同一個訂單的兩條消息。第一條消息的狀態是下單,第二條消息的狀態是支付,這是沒問題的。
但如果第一條消息的狀態是支付,第二條消息的狀態是下單就會有問題了。沒有下單就先支付了?
消息順序問題是一個非常棘手的問題,比如:
Kafka 同一個 partition 中能保證順序,但是不同的 partition 無法保證順序;
RabbitMQ的同一個 queue 能夠保證順序,但是如果多個消費者同一個 queue 也會有順序問題。
如果消費者使用多線程消費消息,也無法保證順序。
如果消費消息時同一個訂單的多條消息中,中間的一條消息出現異常情況,順序將會被打亂。
還有如果生產者發送到 MQ 中的路由規則,跟消費者不一樣,也無法保證順序。
3.5 消息堆積
如果消息消費者讀取消息的速度,能夠跟上消息生產者的節奏,那么整套 MQ 機制就能發揮最大作用。
但是很多時候,由于某些批處理或者其他原因,導致消費速度小于生產速度。這樣會直接導致消息堆積問題,從而影響業務功能。
這里以下單開通會員為例,如果消息出現堆積會導致用戶下單之后,很久之后才能變成會員。這種情況肯定會引起大量用戶投訴。
3.6 系統復雜度提升
這里說的系統復雜度和系統耦合性是不一樣的。
假設以前只有系統 A、系統 B 和系統 C 三個系統,引入 MQ 之后,除了需要關注前面三個系統之外,還需要關注 MQ 服務。需要關注的點越多,系統的復雜度越高。
MQ 的機制需要生產者、MQ 服務器、消費者。有一定的學習成本,需要額外部署 MQ 服務器。
有些 MQ 功能非常強大、用法有點復雜,例如 RocketMQ。如果使用不好,會出現很多問題。有些問題,不像接口調用那么容易排查,從而導致系統的復雜度提升了。
4. 如何解決這些問題?
MQ 是一種趨勢,總體來說對我們的系統是利大于弊的,難道因為它會出現一些問題,我們就不用它了?那么我們要如何解決這些問題呢?
4.1 重復消息問題
不管是由于生產者產生的重復消息,還是由于消費者導致的重復消息,我們都可以在消費者中解決這個問題。
這就要求消費者在做業務處理時,要做冪等設計。如果有不知道如何設計的朋友,可以參考“高并發下如何保證接口的冪等性?”,里面介紹得非常詳細。
在這里我推薦增加一張消費消息表,來解決 MQ 的這類問題。
消費消息表中,使用 messageId 做唯一索引。在處理業務邏輯之前,先根據 messageId 查詢一下該消息有沒有處理過。如果已經處理過了則直接返回成功,如果沒有處理過,則繼續做業務處理。
4.2 數據一致性問題
我們都知道數據一致性分為:
強一致性
弱一致性
最終一致性
而 MQ 為了性能考慮使用的是最終一致性,那么必定會出現數據不一致的問題。這類問題大概率是因為消費者讀取消息后,業務邏輯處理失敗導致的。這時候可以增加重試機制。
重試分為同步重試和異步重試。
有些消息量比較小的業務場景,可以采用同步重試。在消費消息時如果處理失敗,立刻重試 3-5 次,如果還是失敗則寫入到記錄表中。
但如果消息量比較大,則不建議使用這種方式。因為如果出現網絡異常,可能會導致大量的消息不斷重試,影響消息讀取速度造成消息堆積。
消息量比較大的業務場景,建議采用異步重試。在消費者處理失敗之后,立刻寫入重試表,有個 job 專門定時重試。
還有一種做法:如果消費失敗,自己給同一個 topic 發一條消息。在后面的某個時間點,自己又會消費到那條消息,起到了重試的效果。如果對消息順序要求不高的場景,可以使用這種方式。
4.3 消息丟失問題
不管你是否承認,有時候消息真的會丟。即使這種概率非常小,也會對業務有影響。生產者、MQ 服務器、消費者都有可能會導致消息丟失的問題。為了解決這個問題,我們可以增加一張消息發送表。
當生產者發完消息之后,會往該表中寫入一條數據,狀態 status 標記為待確認;
如果消費者讀取消息之后,調用生產者的 API 更新該消息的 status 為已確認;
有個 job 每隔一段時間檢查一次消息發送表,如果5分鐘(這個時間可以根據實際情況來定)后還有狀態是待確認的消息,則認為該消息已經丟失了,重新發條消息。
這樣不管是由于生產者、MQ 服務器、還是消費者導致的消息丟失問題,job 都會重新發消息。
4.4 消息順序問題
消息順序問題是一種常見問題。我們以 Kafka 消費訂單消息為例,訂單有下單、支付、完成、退貨等狀態。這些狀態是有先后順序的,如果順序錯了會導致業務異常。
解決這類問題之前,我們需要先確認:消費者是否真的需要知道中間狀態,只知道最終狀態行不行?
其實很多時候,我真的需要知道的是最終狀態。這時可以把流程優化一下:
這種方式可以解決大部分的消息順序問題。
但如果真的有需要保證消息順序的需求,那么可以將訂單號路由到不同的 partition。同一個訂單號的消息,每次到發到同一個 partition。
4.5 消息堆積
如果消費者消費消息的速度小于生產者生產消息的速度,將會出現消息堆積問題。其實這類問題產生的原因很多。如果想要進一步了解,可以看看“我用kafka兩年踩過的一些非比尋常的坑”。
那么消息堆積問題該如何解決呢?這個要看消息是否需要保證順序。
如果不需要保證順序,可以讀取消息之后用多線程處理業務邏輯。
這樣就能增加業務邏輯處理速度,解決消息堆積問題。但是線程池的核心線程數和最大線程數需要合理配置,不然可能會浪費系統資源。
如果需要保證順序,可以讀取消息之后將消息按照一定的規則分發到多個隊列中,然后在隊列中用單線程處理。
責任編輯:haq
-
服務器
+關注
關注
12文章
9308瀏覽量
86071 -
消息
+關注
關注
0文章
29瀏覽量
12901
原文標題:面霸篇:MQ 的 5 大問題詳解
文章出處:【微信號:LinuxHub,微信公眾號:Linux愛好者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
JavaWeb消息隊列使用指南
探索字節隊列的魔法:多類型支持、函數重載與線程安全
![探索字節<b class='flag-5'>隊列</b>的魔法:多類型支持、函數重載與線程安全](https://file1.elecfans.com/web2/M00/C4/8A/wKgZomX0EhWACv8DAAAUet8ikhs451.png)
為什么同一個隊列引用的全局變量,運行在兩個子vi中發現隊列數據丟失了
在進入OPA847放大器之前加了LC高通濾波,請問這樣會引入過多噪聲嗎?
CS1237與CS1238有效數據位為什么會不同?
嵌入式環形隊列與消息隊列的實現原理
示波器探頭在測試的時候會引入什么負載效應
玩轉RT-Thread之消息隊列的應用
![玩轉RT-Thread之消息<b class='flag-5'>隊列</b>的應用](https://file.elecfans.com/web2/M00/20/B3/pYYBAGGfNNmAK-PZAAJsGM5Cgk0227.jpg)
嵌入式實時操作系統中的隊列管理與應用
![嵌入式實時操作系統中的<b class='flag-5'>隊列</b>管理與應用](https://file1.elecfans.com/web2/M00/DE/85/wKgZomYwj-CAG24mAAApc73SDuQ445.png)
Freertos隊列項里的字節長度是否可以獲取?
freertos隊列錯亂是什么原因導致的?
5.5G,多出來的0.5G又是啥?為什么不直接邁向6G時代?
MCU專屬隊列功能模塊之QueueForMcu應用
![MCU專屬<b class='flag-5'>隊列</b>功能模塊之QueueForMcu應用](https://file1.elecfans.com/web2/M00/C6/02/wKgaomX6XC-AHm7RAAAU4puuo8M220.png)
評論