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