數據庫學習筆記:消息隊列重複消費、順序消費、分佈式事務

消息隊列的消息重複消費

消息重複消費是使用消息隊列之後,必須考慮的一個問題,也是比較嚴重和常見的問題。比如有這樣的一個場景,用戶下單成功後我需要去一個活動頁面給他加GMV(銷售總額),最後根據他的GMV去給他發獎勵,這是電商活動很常見的玩法。

類似累計下單金額到哪個梯度給你返回什麼梯度的獎勵這樣。

我只能告訴你這樣的活動頁面100%是用異步去加的,不然一個用戶下一單就給他加一下,那就意味着對那張表就要操作一下,你考慮下雙十一當天多少次對這個表的操作?這數據庫或者緩存都頂不住吧。而且大家應該也有這樣的體會,你下單了馬上去看一些活動頁面,有時候馬上就有了,有時候缺延遲有很久,爲啥?這個速度取決於消息隊列的消費速度,消費慢堵塞了就遲點看到唄。下個單支付成功就發個消息出去,活動就監聽支付成功消息,監聽到訂單成功支付的消息,那我就去我活動GMV表裏給你加上去,聽到這裏大家可能覺得順理成章

但是我告訴大家一般消息隊列的使用,都是有重試機制的,就是說我下游的業務(監聽)發生異常了,我會拋出異常並且要求你重新發一次。我這個活動這裏發生錯誤,你要求重發肯定沒問題。但是大家仔細想一下問題在哪裏?

是的,不止你一個人監聽這個消息啊,還有別的服務也在監聽,失敗的重發是沒有問題的,成功的服務也在監聽,對於成功的服務,就會造成重複消費。看下面  

就好比上面的這樣,我們的積分系統處理失敗了,他這個系統肯定要求你重新發送一次消息,積分的系統重新接收並且處理成功了,但是別人的活動,優惠券等等服務(並沒有失敗)也監聽了這個消息呀,那不就可能出現活動系統給他加GMV加兩次,優惠券扣兩次這種情況麼?

真實的情況其實重試是很正常的,服務的網絡抖動開發人員代碼Bug,還有數據問題等都可能處理失敗要求重發的。

說白了就是多個下游服務監聽上游消息,一部分下游服務處理失敗要求上游服務重發消息,剩餘的那些成功的服務就產生了重複消費。

那在開發過程中是怎麼去保證的

一般我們叫這樣的處理叫接口冪等

冪等(idempotent、idempotence)是一個數學與計算機學概念,常見於抽象代數中。

在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。

冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。這些函數不會影響系統狀態,也不用擔心重複執行會對系統造成改變。

例如,“setTrue()”函數就是一個冪等函數,無論多次執行,其結果都是一樣的,更復雜的操作冪等保證是利用唯一交易號(流水號)實現.

通俗了講就是同樣的參數調用我這個接口,調用多少次結果都是一個,讓GMV對於同一個訂單號無論加多少次都和加一次的效果相同就可以了。但是如果不做冪等,你一個訂單調用多次錢不就加多次嘛,同理你退款調用多次錢也就減多次了。

大致處理流程如下:

那怎麼保證冪等

一般冪等,我會分場景去考慮,看是強校驗還是弱校驗,比如跟金錢相關的場景那就很關鍵呀,就做強校驗,別不是很重要的場景做弱校驗。

強校驗:

比如你監聽到用戶支付成功的消息,你監聽到了去加GMV就要調用加錢的接口,那加錢接口下面再調用一個加流水的接口,兩個放在一個事務裏,成功一起成功失敗一起失敗

每次消息過來都要拿着訂單號+業務場景這樣的唯一標識(比是天貓雙十一活動)去流水錶查,看看有沒有這條流水,有就直接return不要走下面的流程了,沒有就執行後面的邏輯。之所以用流水錶,是因爲涉及到金錢這樣的活動,有啥問題後面也可以去流水錶對賬,還有就是幫助開發人員定位問題。可以看看代碼

弱校驗:

一些不重要的場景,比如發短信,我就把這個id+場景唯一標識作爲Redis的key,放到緩存裏面,失效時間看場景,一定時間內的這個消息就去Redis判斷。

用KV就算消息丟了這樣的場景也沒關係,反正丟條無關痛癢的通知短信嘛。還有些弱校驗用token的,但是重要的場景一定要強校驗,這樣真正查問題的時候在磁盤持久化的數據才讓人放心。

 

消息順序消費的場景怎麼保證的

網上更多的都是介紹binlog的同步。一般都是同個業務場景下不同幾個操作的消息(要求順序)同時過去,本身順序是對的,但是你發出去的時候同時發出去了,消費的時候卻亂掉了,這樣就有問題了。

我之前做電商活動也是有這樣的例子,我們都知道數據量大的時候數據同步壓力還是很大的,有時候數據量大的表需要同步幾個億的數據。(並不是主從同步,主從延遲大會有問題,可能是從數據庫或者主數據庫同步到備庫),這種情況我們都是懟到隊列裏面去,然後慢慢消費的,那問題就來了呀,我們在數據庫同時對一個Id的數據進行了增、改、刪三個操作,但是你消息發過去消費的時候變成了改,刪、增,這樣數據就不對了。

本來一條數據最後應該刪掉了,變成了增加,這就出大問題

怎麼解決

簡單的說一下RocketMQ 裏面的一個簡單實現吧。生產者消費者一般需要保證順序消息的話,可能就是一個業務場景下的,比如訂單的創建、支付、發貨、收貨。

那這些東西是不是一個訂單號呢?一個訂單的肯定是一個訂單號的說,那簡單了呀。

一個topic下有多個隊列,爲了保證發送有序,RocketMQ提供了MessageQueueSelector隊列選擇機制,他有三種實現:

我們可使用Hash取模法,讓同一個訂單先發送到同一個隊列中,再使用同步發送這個訂單的消息(消息處理成功了再發下一個),只有同個訂單的創建消息發送成功,再發送支付消息。這樣,我們保證了發送有序。

RocketMQ的 topic 內的隊列機制,可以保證存儲滿足FIFO(First Input First Output 簡單說就是指先進先出),剩下的只需要消費者順序消費即可。RocketMQ僅保證順序發送,順序消費由消費者業務保證!!!

這裏很好理解,一個訂單你發送的時候放到一個隊列裏面去,同一訂單號Hash一下是不是還是一樣的結果,那肯定是一個消費者消費,那順序就保證了。真正的順序消費不同的中間件都有自己的不同實現。

Tip:一個隊列有序出去,一個消費者消費不就好了,我想說的是消費者是多線程的,你消息是有序的給他的,你能保證他是有序的處理的?還是一個消費成功了再發下一個穩妥。

分佈式事務

分佈式事務在現在遍地都是分佈式部署的系統中幾乎是必要的。

事務:

分佈式事務事務隔離級別ACID,那什麼是事務呢?

概念:

一般是指要做的或所做的事情。

在計算機術語中是指訪問並可能更新數據庫中各種數據項的一個程序執行單元(unit)。

事務通常由高級數據庫操縱語言或編程語言(如SQL,C++或Java)書寫的用戶程序用戶程序的執行所引起,並用形如begin transactionend transaction語句(或函數調用)來界定。

事務由事務開始(begin transaction)和事務結束(end transaction)之間執行的全體操作組成。

特性:

事務是恢復和併發控制的基本單位。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲ACID特性

原子性(atomicity):一個事務是一個不可分割的工作單位,事務中包括的操作要麼都做,要麼都不做。

一致性(consistency):事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。

隔離性(isolation):一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。

持久性(durability)持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

事務就是一系列操作,要麼同時成功,要麼同時失敗。然後會從事務的 ACID 特性(原子性、一致性、隔離性、持久性),事務就是爲了保證一系列操作可以正常執行,它必須同時滿足 ACID 特性。

那什麼是分佈式事務呢?

大家可以想一下,你下單流程可能涉及到10多個環節,你下單付錢都成功了,但是你優惠券扣減失敗了(公司會被薅羊毛),積分新增失敗了(用戶會不開心),但是分佈式事務就能保證這些在不同的服務都成功。

舉例方便一下理解的簡單的例子:

分佈式事務大概分爲:

  • 2pc(兩段式提交)

  • 3pc(三段式提交)

  • TCC(Try、Confirm、Cancel)

  • 最大努力通知

  • XA

  • 本地消息表(ebay研發出的)

  • 半消息/最終一致性(RocketMQ)

這裏介紹下2pc(兩段式),以及常用的半消息事務也就是最終一致性,目的是理解下分佈式事務裏面消息中間件的作用,別的事務都大同小異。

當然分佈式事務也都有種種弊端:例如長時間鎖定數據庫資源,導致系統的響應不快併發上不去。網絡抖動出現腦裂情況,導致事物參與者,不能很好地執行協調者的指令,導致數據不一致

單點故障:例如事務協調者,在某一時刻宕機,雖然可以通過選舉機制產生新的Leader,但是這過程中,必然出現問題,而TCC,只有強悍的技術團隊,才能支持開發,成本太高

2pc(兩段式提交) :

2pc(兩段式提交)可以說是分佈式事務的最開始的樣子了,就是通過消息中間件協調多個系統,在兩個系統操作事務的時候都鎖定資源但是不提交事務,等兩者都準備好了,告訴消息中間件,然後再分別提交事務。

但是問題所在?是的,如果A系統事務提交成功了,但是B系統在提交的時候網絡波動或者各種原因提交失敗了,其實還是會失敗的。

最終一致性

整個流程中,我們能保證是:

  • 業務主動方本地事務提交失敗,業務被動方不會收到消息的投遞。

  • 只要業務主動方本地事務執行成功,那麼消息服務一定會投遞消息給下游的業務被動方,並最終保證業務被動方一定能成功消費該消息(消費成功或失敗,即最終一定會有一個最終態)。

不過呢技術就是這樣,各種極端的情況我們都需要考慮,也很難有完美的方案,所以纔會有這麼多的方案三段式TCC最大努力通知等等分佈式事務方案,大家只需要知道爲啥要做,做了有啥好處,有啥壞處,在實際開發的時候都注意下就好好了,系統都是根據業務場景設計出來的,離開業務的技術沒有意義,離開技術的業務沒有底氣。還是那句話:沒有最完美的系統,只有最適合的系統。

 

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