解決分布式事務(wù),Seata真香!
背景
大家好,今天給大家分享一個在 2022 年出去面試 Java 幾乎必問的一個技術(shù),那就是 seata。
什么??你才看了第一句話心里有閃現(xiàn)了無數(shù)個問號?因為沒聽說過 seata 這個東西?
沒關(guān)系,為了避免兄弟們出去面試被問到 seata 的時候,一臉蒙圈,我們今天就把這個東西給大家講明白。
既然要給大家講什么是 seata,那就得先說一下這個東西的定位,這東西就是現(xiàn)在很火的 Spring Cloud Alibaba 里的一個組件,是專門幫助我們解決分布式事務(wù)問題的,也就是說,seata 是一個分布式事務(wù)框架。
什么是分布式事務(wù)
那可能很多小伙伴很蒙圈了,什么是分布式事務(wù)?好吧,為了保證大家能繼續(xù)看下去,我們先說一下什么是分布式事務(wù)這個問題。
舉個最簡單的例子,假設(shè)現(xiàn)在你負(fù)責(zé)了一個訂單系統(tǒng),一個庫存系統(tǒng),一個營銷系統(tǒng),然后呢,當(dāng)你的訂單系統(tǒng)收到用戶一個請求要創(chuàng)建訂單的時候,這個時候你得做三件事情。
第一,調(diào)用庫存系統(tǒng)的接口鎖定庫存,第二,調(diào)用調(diào)用營銷系統(tǒng)的接口鎖定優(yōu)惠券,第三,你訂單系統(tǒng)自己得在 MySQL 里插入一系列訂單的數(shù)據(jù)。
比如下圖 1 所示:
那么現(xiàn)在問題來了,你訂單系統(tǒng)有自己的訂單數(shù)據(jù)庫,可以去插入訂單數(shù)據(jù),那庫存系統(tǒng)是不是也應(yīng)該有自己的庫存數(shù)據(jù)庫,去鎖定庫存數(shù)據(jù)?
營銷系統(tǒng)是不是應(yīng)該有自己的營銷數(shù)據(jù)庫,去鎖定優(yōu)惠券?當(dāng)然是了!每個人都有自己的數(shù)據(jù)庫,這一個都不能少。
如下圖 2 所示:
那現(xiàn)在問題又來了,既然一次創(chuàng)建訂單的請求,要涉及到訂單、庫存、營銷三個系統(tǒng),分別操作各自自己的三個數(shù)據(jù)庫,才能完成這次請求。
那是不是可能會出現(xiàn)這么一種情況,首先呢,你先調(diào)用庫存系統(tǒng),鎖定了庫存了,O 了。
接著呢,你又調(diào)用了營銷系統(tǒng),鎖定了優(yōu)惠券,也 O 了。最后呢,當(dāng)你訂單系統(tǒng)要往自己的訂單數(shù)據(jù)庫里插入數(shù)據(jù)的時候,網(wǎng)絡(luò)抽風(fēng)了,導(dǎo)致你這一次插入訂單數(shù)據(jù)失敗了,直接 exception 異常了,你蒙圈了。
如下圖 3 所示:
那這個時候你覺得可能會產(chǎn)生什么樣的問題呢,其實很簡單,這個時候你這個訂單要購買的商品庫存已經(jīng)被鎖定了,你為了下這個訂單用的優(yōu)惠券,也已經(jīng)被鎖定了。
結(jié)果呢,你的訂單自己本身的數(shù)據(jù)并沒進(jìn)入數(shù)據(jù)庫,然后還返回一個了異常信息給用戶說,本次下單失敗。
但是你說下單失敗就失敗吧,結(jié)果呢,運(yùn)營看庫存數(shù)據(jù)的時候可能會一臉蒙圈,為啥有一些商品庫存被鎖定了,結(jié)果沒有對應(yīng)的跟訂單,而且一直沒人付款來購買呢??
然后用戶自己也有點發(fā)蒙,因為一查自己的優(yōu)惠券,好不容易攢了幾張券來買東西,結(jié)果現(xiàn)在訂單沒下成,優(yōu)惠券狀態(tài)都搞成已使用了,自己還沒法用這些優(yōu)惠券了。
如下圖 4 所示:
其實這就是一個非常經(jīng)典的分布式事務(wù)的問題了,你一個創(chuàng)建訂單的請求,橫跨了訂單、庫存、營銷三個系統(tǒng),分別涉及三個數(shù)據(jù)庫。
所有很可能會發(fā)現(xiàn),你的庫存和營銷的數(shù)據(jù)操作都成功了,而且?guī)齑婧蜖I銷數(shù)據(jù)庫里的本地事務(wù)都提交了,結(jié)果訂單插入數(shù)據(jù)庫失敗了,訂單數(shù)據(jù)庫里的本地事務(wù)回滾了,但是庫存和營銷數(shù)據(jù)庫里的本地事務(wù)已經(jīng)提交了,他們是不會回滾的。
如下圖 5 所示:
什么叫做逆向補(bǔ)償
那既然問題已經(jīng)找到了,我們希望的應(yīng)該是什么效果呢?
我們其實希望的效果是,如果訂單要是插入數(shù)據(jù)庫失敗了,訂單數(shù)據(jù)庫本地事務(wù)回滾了,我們應(yīng)該想辦法去通知一下庫存系統(tǒng)和營銷系統(tǒng),把之前在庫存數(shù)據(jù)庫和營銷數(shù)據(jù)庫里已經(jīng)提交的數(shù)據(jù)修改做一個逆向補(bǔ)償,進(jìn)行恢復(fù)。
什么叫做逆向補(bǔ)償呢?意思就是說,之前庫存系統(tǒng)如果在數(shù)據(jù)庫里執(zhí)行的是 insert,那么此時就應(yīng)該執(zhí)行 delete,把之前插入的數(shù)據(jù)刪除了。
如果之前執(zhí)行的 delete,現(xiàn)在就應(yīng)該執(zhí)行 insert,把刪除的額數(shù)據(jù)重新插入回去,如果之前執(zhí)行的是 udpate 語句,現(xiàn)在就應(yīng)該再次執(zhí)行一個 update 語句,把數(shù)據(jù)恢復(fù)到更新之前的狀態(tài)。
如下圖 6 所示:
互聯(lián)網(wǎng)最流行的分布式事務(wù)組件 seata
那既然我們想要實現(xiàn)這個效果,這個時候問題就來了,單單依賴我們自己那肯定搞不定這個問題了,這個時候就必須引入 Spring Cloud Alibaba 里的大佬組件,seata。
seata 就是專門幫助我們解決這個問題的,如果我們要是在系統(tǒng)里引入 seata 框架之后,其實每個系統(tǒng)里都會嵌入 seata,同時我們還需要去部署一個 seata server。
如下圖 7 所示:
這個時候,我們的系統(tǒng)運(yùn)行原理會變成這樣:訂單系統(tǒng)中的 seata 會發(fā)送請求給 seata server 去開啟一個全局事務(wù),然后庫存系統(tǒng)先運(yùn)行,他在進(jìn)行數(shù)據(jù)庫 crud 的時候,這些操作都會被 seata 框架進(jìn)行攔截。
然后 seata 框架會在一個本地事務(wù)里,把你的 sql 語句和逆向補(bǔ)償日志,一起插入到你的庫存數(shù)據(jù)庫里去,在庫存數(shù)據(jù)庫里必須有一個 undo_log 表,存儲 seata 的逆向補(bǔ)償日志。
那這個逆向補(bǔ)償日志是什么呢?簡單,如果你的 sql 是 insert,那逆向補(bǔ)償日志可以幫助你后續(xù)構(gòu)建 delete 語句來刪除,如果你的 sql 是 update,那逆向補(bǔ)償日志可以記錄你更新之前的舊數(shù)據(jù),他可以幫助你后續(xù)把數(shù)據(jù) update 到老版本的狀態(tài)。
如下圖 8 所示:
你庫存系統(tǒng)的 sql 語句和他們的補(bǔ)償日志,是在一個本地事務(wù)里一起提交的,一起成功或者一起失敗,所以但凡你的庫存系統(tǒng)更新成功了,就一定會有對應(yīng)的補(bǔ)償日志也會在庫存 數(shù)據(jù)庫里的,以備不時之需,營銷系統(tǒng)其實也是相同的運(yùn)行原理。
那么假設(shè)說庫存系統(tǒng)和營銷系統(tǒng),按照這個思路都執(zhí)行完畢了,到訂單系統(tǒng)了,他結(jié)果撂挑子了,插入訂單數(shù)據(jù)庫失敗。
當(dāng)然,在插入的時候其實也會有對應(yīng)的補(bǔ)償日志會一起提交,但是因為這個時候網(wǎng)絡(luò)問題,導(dǎo)致插入訂單和插入補(bǔ)償日志一起失敗了。
所以此時訂單系統(tǒng)的 seata 就會上報 seata server 說,大哥,我這兒完犢子了,您要不通知庫存和營銷兩個兄弟,逆向補(bǔ)償一下吧。
如下圖 9 所示:
接著 seata server 發(fā)現(xiàn)說,這分布式事務(wù)都失敗了,那趕緊的,他會通知庫存系統(tǒng)和營銷系統(tǒng)里的 seata 框架小兄弟說,兄弟們,趕緊的,把之前插入你們數(shù)據(jù)庫里的 undo_log 表里的補(bǔ)償日志拿出來,構(gòu)建一下逆向補(bǔ)償 sql。
之前是 insert 你就給我弄個 delete,之前是 delete 你就給我弄個 insert,之前是 update 你還是 update,逆向補(bǔ)償 sql 趕緊跑一把,把數(shù)據(jù)給我恢復(fù)了,前隊改后隊,跑步前進(jìn),hurry up 起來。
如下圖 10 所示:
總結(jié)
太棒了,到這個時候為止,我們就發(fā)現(xiàn) seata 老大的作用了,你訂單、庫存、營銷三個系統(tǒng)隨便跑,有誰失敗了,seata server 收到你的失敗通知,就會告訴別的系統(tǒng)用 undo log 日志構(gòu)建補(bǔ)償 sql,把數(shù)據(jù)都給回滾了,完美。