分布式事務、重復消費、順序消費


前言

消息隊列在互聯網技術存儲方面使用如此廣泛,幾乎所有的后端技術面試官都要在消息隊列的使用和原理方面對小伙伴們進行360°的刁難。

 

撈一下

上一期,簡單的介紹了一下消息隊列的基礎知識,里面有消息隊列的應用場景,以及使用之后可能帶來的問題,但是上期沒對怎么解決這些問題做回答,因為要控制篇幅嘛(明明是自己覺得MQ寫不了多少期,要多懟一期出來!渣男)

咳咳,我們言歸正傳,沒看的朋友去看一下,有助於這期的閱讀:

 

面試開始

一個風度翩翩,穿着格子襯衣的中年男子,拿着一個滿是划痕的mac向你走來,看着錚亮的頭,心想着肯定是尼瑪頂級架構師吧!但是我們看過暖男敖丙的系列,腹有詩書氣自華,虛都不虛。

沒錯小伙子還是我,上次話說一半你就溜了,這次我非得好好的問問你。

 

我信你個鬼,我們開始吧,上次說到了消息隊列的消息重復消費,你能跟我介紹這是怎么樣子的場景么?

消息重復消費是使用消息隊列之后,必須考慮的一個問題,也是比較嚴重和常見的問題,但凡用到了消息隊列,我第一時間考慮的就是重復消費的問題。

就比如有這樣的一個場景,用戶下單成功后我需要去一個活動頁面給他加GMV(銷售總額),最后根據他的GMV去給他發獎勵,這是電商活動很常見的玩法。

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

我只能告訴你這樣的活動頁面10000%是用異步去加的(別問我為什么,因為這個活動的后端是敖丙我做的😂),不然你想,你一個用戶下一單就給他加一下,那就意味着對那張表就要操作一下,你考慮下雙十一當天多少次對這個表的操作?這數據庫或者緩存都頂不住吧。

而且大家應該也有這樣的體會,你下單了馬上去看一些活動頁面,有時候馬上就有了,有時候卻延遲有很久,為啥?這個速度取決於消息隊列的消費速度,消費慢堵塞了就遲點看到唄。

你下個單支付成功你就發個消息出去,我們上面那個活動的開發人員就監聽你的支付成功消息,我監聽到你這個訂單成功支付的消息,那我就去我活動GMV表里給你加上去,聽到這里大家可能覺得順理成章

但是我告訴大家一般消息隊列的使用,我們都是有重試機制的,就是說我下游的業務發生異常了,我會拋出異常並且要求你重新發一次

我這個活動這里發生錯誤,你要求重發肯定沒問題。但是大家仔細想一下問題在哪里?

是的,不止你一個人監聽這個消息啊,還有別的服務也在監聽,他們也會失敗啊,他一失敗他也要求重發,但是你這里其實是成功的,重發了,你的錢不就加了兩次了?

對不對???是不是這個道理???

還不理解?看下面 

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

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

嗯小伙子分析得很仔細嘛,那你在開發過程中是怎么去保證的呀?

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

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

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

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

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

通俗了講就是你同樣的參數調用我這個接口,調用多少次結果都是一個,你加GMV同一個訂單號你加一次是多少錢,你加N次都還是多少錢。

但是如果不做冪等,你一個訂單調用多次錢不就加多次嘛,同理你退款調用多次錢也就減多次了。

大致處理流程如下:

那怎么保證呢?

一般這么回答的:

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

強校驗:

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

每次消息過來都要拿着訂單號+業務場景這樣的唯一標識(比如天貓雙十一活動)去流水表查,看看有沒有這條流水,有就直接return不要走下面的流程了,沒有就執行后面的邏輯。

之所以用流水表,是因為涉及到金錢這樣的活動,有啥問題后面也可以去流水表對賬,還有就是幫助開發人員定位問題。

有的小伙伴可能還是有點懵,然后人才交流群的小伙伴也說有些例子可以放一點偽代碼,那這期開始能用代碼將的我也寫點。

 

弱校驗:

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

用KV就算消息丟了可能這樣的場景也沒關系,反正丟條無關痛癢的通知短信嘛(你敢說你沒驗證碼短信丟失的情況?)。

還有很多公司的弱校驗用token啊什么的,反正花樣很多,但是重要的場景一定要強校驗,真正查問題的時候沒有在磁盤持久化的數據,心里還是空空的,就像你和女朋友分開的時候的心里狀態一樣。(我單身的怎么知道這種感覺?猜的)

你們有接觸過消息順序消費這樣的場景么?你怎么保證的?

沒有!over!

 

Tip:但是說實話順序消費這里很難介紹,我上周到這周問了很多身邊的師兄開發過程中這樣的場景不多,我跟三歪也討論了幾次,網上更多的都是介紹binlog的同步,好像更多的場景就沒了。

一般都是同個業務場景下不同幾個操作的消息同時過去,本身順序是對的,但是你發出去的時候同時發出去了,消費的時候卻亂掉了,這樣就有問題了。

我之前做電商活動也是有這樣的例子,我們都知道數據量大的時候數據同步壓力還是很大的,有時候數據量大的表需要同步幾個億的數據。(並不是主從同步,主從延遲大的話會有問題,可能是從數據庫或者主數據庫同步到備庫

這種情況我們都是懟到隊列里面去,然后慢慢消費的,那問題就來了呀,我們在數據庫同時對一個Id的數據進行了增、改、刪三個操作,但是你消息發過去消費的時候變成了改,刪、增,這樣數據就不對了。

本來一條數據應該刪掉了,結果在你那卻還在,這不是出大問題

兩者的結果是不是完全不一樣了 

那你怎么解決呢?

我簡單的說一下我們使用的RocketMQ里面的一個簡單實現吧。

Tip:為啥用RocketMQ舉例呢,這玩意是阿里開源的,我問了下身邊的朋友很多公司都有使用,所以讀者大概率是這個的話我就用這個舉例吧,具體的細節我后面會在RocketMQKafka各自章節說到。

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

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

一個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多個環節,你下單付錢都成功了,但是你優惠券扣減失敗了,積分新增失敗了,前者公司會被薅羊毛,后者用戶會不開心,但是這些都在不同的服務怎么保證大家都成功呢

聰明,分布式事務,你看你都會搶答了!

Tip:真實的應用場景可能比我介紹的場景復雜數倍,我只是為了舉例方便一下大家理解所以用了很簡單的例子。

我接觸和了解到的分布式事務大概分為:

  • 2pc(兩段式提交)
  • 3pc(三段式提交)
  • TCC(Try、Confirm、Cancel)
  • 最大努力通知
  • XA
  • 本地消息表(ebay研發出的)
  • 半消息/最終一致性(RocketMQ)

這里我就介紹下最簡單的2pc(兩段式),以及大家以后可能比較常用的半消息事務也就是最終一致性,目的是讓大家理解下分布式事務里面消息中間件的作用,別的事務都大同小異,都有很多優點。

當然也都有種種弊端

例如長時間鎖定數據庫資源,導致系統的響應不快並發上不去

網絡抖動出現腦裂情況,導致事物參與者,不能很好地執行協調者的指令,導致數據不一致

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

不多BB了,我們開始介紹這個兩個事物吧。

2pc(兩段式提交) :

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

但是我不知道大家看到問題所在沒有?

是的你可能已經發現了,如果A系統事務提交成功了,但是B系統在提交的時候網絡波動或者各種原因提交失敗了,其實還是會失敗的。

最終一致性

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

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

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

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

還是那句話:沒有最完美的系統,只有最適合的系統。

面試結束

小伙子看不出來啊,還是有點東西的嘛,這幾個點都回答的不錯,明天你能跟我聊一下RocketMQ么?

花了這么多時間,不確定他寫不寫的完,心疼他。好想給他點贊啊,消息回溯也在單獨介紹消息中間件的時候介紹吧,這章篇幅有點長了。

總結

這章其實我寫的時間比之前的秒殺還要久,因為順序消息這個場景我不知道怎么講出來大家容易懂一點,最后就參考了網上的,順序消息的實際應用場景沒別的那么廣泛,跟3y也聊了好幾次,最后定了這個binlog的場景。

總之就是這期創作源泉有點枯竭,這章是真的難寫,包括分布式事務在實際開發過程中也是很復雜的環節,需要用的時候光是做設計都要很久,反正我的流程圖長得一匹。

我每次都想着寫得通俗易懂一點,這篇即使是這樣我覺得還是不夠通俗易懂,但是消息的場景就是這樣,還有大家加我也不要一上來就問我很多扣細節的點,自己多點思考我覺得可能幫助比我告訴你答案好很多吧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM