mq的各種問題分析(順序,一致性,重複,丟失,堆積)

原文:mq的那些破事兒,你不好奇嗎? (qq.com)

前言

最近mq越來越火,很多公司在用,很多人在用,其重要性不言而喻。但是如果我讓你回答下面的這些問題:

  1. 我們爲什麼要用mq?
  2. 引入mq會多哪些問題?
  3. 如何解決這些問題?

你心中是否有答案了呢?本文將會一一爲你解答,這些看似平常卻很有意義的問題。

1 傳統模式有哪些痛點?

1.1 痛點1

有些複雜的業務系統,一次用戶請求可能會同步調用N個系統的接口,需要等待所有的接口都返回了,才能真正的獲取執行結果。

圖片這種同步接口調用的方式總耗時比較長,非常影響用戶的體驗,特別是在網絡不穩定的情況下,極容易出現接口超時問題。

1.2 痛點2

很多複雜的業務系統,一般都會拆分成多個子系統。我們在這裏以用戶下單爲例,請求會先通過訂單系統,然後分別調用:支付系統、庫存系統、積分系統 和 物流系統。圖片系統之間耦合性太高,如果調用的任何一個子系統出現異常,整個請求都會異常,對系統的穩定性非常不利。

1.3 痛點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都無法避免。

有哪些場景會出現重複的消息呢?

  1. 消息生產者產生了重複的消息
  2. kafka和rocketmq的offset被回調了
  3. 消息消費者確認失敗
  4. 消息消費者確認時超時了
  5. 業務系統主動發起重試

圖片如果重複消息不做正確的處理,會對業務造成很大的影響,產生重複的數據,或者導致數據異常,比如會員系統多開通了一個月的會員。

3.2 數據一致性問題

很多時候,如果mq的消費者業務處理異常的話,就會出現數據一致性問題。比如:一個完整的業務流程是,下單成功之後,送100個積分。下單寫庫了,但是消息消費者在送積分的時候失敗了,就會造成數據不一致的情況,即該業務流程的部分數據寫庫了,另外一部分沒有寫庫。

圖片如果下單和送積分在同一個事務中,要麼同時成功,要麼同時失敗,是不會出現數據一致性問題的。

但由於跨系統調用,爲了性能考慮,一般不會使用強一致性的方案,而改成達成最終一致性即可。

3.3 消息丟失問題

同樣消息丟失問題,也是mq中普遍存在的問題,不管你用哪種mq都無法避免。

有哪些場景會出現消息丟失問題呢?

  1. 消息生產者發生消息時,由於網絡原因,發生到mq失敗了。
  2. mq服務器持久化時,磁盤出現異常
  3. kafka和rocketmq的offset被回調時,略過了很多消息。
  4. 消息消費者剛讀取消息,已經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兩年踩過的一些非比尋常的坑》。

那麼消息堆積問題該如何解決呢?

這個要看消息是否需要保證順序。

如果不需要保證順序,可以讀取消息之後用多線程處理業務邏輯。

图片

這樣就能增加業務邏輯處理速度,解決消息堆積問題。但是線程池的核心線程數和最大線程數需要合理配置,不然可能會浪費系統資源。

如果需要保證順序,可以讀取消息之後,將消息按照一定的規則分發到多個隊列中,然後在隊列中用單線程處理。

图片
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章