淺談,seata在使用feign-url通過域名呼叫時分散式事務不生效的問題及解決
在前幾個月時,我們專案出現了分散式事務的問題,那麼什麼是分散式事務問題呢,簡單的說,我們有倆服務A和B,它們對應的資料來源分別是a_db和b_db,A服務收到請求在執行到某個操作時,需要呼叫B服務,在B服務裡繼續執行,B服務裡面的執行牽扯到了對b_db的增刪改操作,等B服務執行完後,A又繼續執行,結果此刻,A發生異常了,由於B在另一個服務,有自己的資料來源,它和A也不屬於一個事務,導致a_db自己回滾了,b_db卻沒有回滾,這不就出問題了麼,這也就是我們系統分散式事務問題的來源。
後來呢,我們引入了springcloud中解決分散式事務的元件seata,關於分散式事務seata的介紹安裝各個模式我就不詳細的一一囉嗦的解釋了,當然為了方便後面大家對問題發生和解決的更好理解,我還是會說一下這個seata解決分散式事務的流程。
關於seata解決分散式事務流程的介紹
seata分為三部分,TC事務協調者,TM事務管理器,RM資源管理器。其中TC是一個獨立的seata-sever服務,用來協調整個分散式事務的,在git_hub上可以自行下載;TM是全域性事務的發起者,管理整個全域性事務,相當於專案中的呼叫者服務,在我們專案中就相當於那個A;RM資源管理器可以有多個,在專案中屬於被呼叫者,在我們專案中相當於那個B。
它們的執行流程如下:
- 1.TM向TC發起一個請求,說明自己要開啟一個全域性事務。
- 2.TC收到了來自於TM的請求,生成了一個XID作為這個全域性事務的唯一標識,返給了TM。
- 3.TM開始去呼叫其他的RM,並將XID一併的傳給了那些RM。
- 4.RM會接收到XID,知道自己的事務屬於這個全域性事務,它會將自己的本地事務註冊到TC作為這個XID下面的一個分支事務,並把自己的事務執行結果也告訴TC。
- 5.各個微服務執行完之後,TC就知道這個XID下的各個分支事務的執行結果,當然TM也知道了。
- 6.TM發現各個分支事務都成功了,就向TC發起請求進行提交,否則就向TC發起請求進行回滾。
- 7.TC收到請求後,就向XID下的所有的分支事務發起相應的請求。
- 8.各個微服務收到TC的請求後,執行相應的命令,並把執行結果上報給TC。
下面為了幫助大家更好的理解,放了一張圖進行演示:
引入seata後的後遺症
我們專案是個不太標準的微服務,分為服務A和B,但卻沒有註冊中心,使用了springcloud的元件feign進行服務通訊呼叫,但是沒有註冊中心不能通過服務名發現服務,只能使用了feign的url模式進行互通 ,當然這也是為問題的爆發埋下了種子。
seata其實使用的模式有很多,比如AT了,TCC了,XA了,還有Saga了,當然我們選用的是AT模式,這種是無侵入業務式的模式,官方也比較推薦。至於註冊配置方式也有很多,有file 、nacos 、eureka、redis、zk、consul、etcd3、sofa等,最終由於我們專案沒有註冊中心的特殊性,我們就只好選用了file單機的註冊配置方式。
於是經過下載seata-server並啟動,修改file.conf檔案的seata-server地址,並將file.conf和registory.conf配置檔案放入到resources下,修改資料來源為代理資料來源,在a_db和b_db新增undo_log資料庫表,變A呼叫端的@Transactional為@GlobalTransactional,這些操作完成後,一個seata分散式事務處理框架的引入算是正式完成,於是在本地電腦上啟動了A,B專案,進行模擬單元測試,分散式事務問題完美處理,於是程式碼上測試伺服器,過了一段時間上了預生產伺服器。
那時候由於工作事多,只在本地模擬了下,伺服器上也只能測試中遇到了再看,沒再去關心這件事,畢竟本地已經完美契合了,結果一個月後上了預生產環境,某個業務程式碼由於資料原因發生了異常,那個方法還牽扯到了分散式事務問題,在觀察資料庫資料時,離奇的發現,分散式事務沒生效,B服務沒有回滾!!
seata問題的一步步剖析及解決
問題發生了後,抓緊在本地模擬了下,發現在自己電腦上分散式事務仍然是可以生效的,程式碼都一樣也沒改過,為什麼在預生產環境就不行了,真實見了鬼了。
後來就在思考預生產環境和自己在本地上測試有什麼區別麼,本地測試時,兩個服務之間的呼叫feign裡面的url填寫的是ip:port,直接就去訪問了指定的服務,但是預生產環境中使用的域名,也就是說請求會根據域名通過nginx轉發到對應服務,假設第一次請求的B1服務,第二次轉發請求的是B2服務,不在一個服務上。有同事就提出了,會不會就和這個B服務有叢集有關。但是還是感覺那裡不對勁,因為聽說我們的預生產環境B雖然做了叢集,但是這幾個B連線的資料來源依舊是同一個資料來源,不過看起來說的也好像很有道理的樣子。
後來啊,就打算在測試環境的伺服器試一下,因為畢竟測試伺服器沒有什麼叢集,也是一個A和一個B,他們之間的呼叫是通過域名而並非ip:port,本以為會成功的,結果卻很悲催,在B服務沒有做叢集的情況下,分散式事務仍然是不生效的,那看起來好像和叢集不叢集沒啥關係啊,畢竟測試環境的服務就是一對一,每次也只會訪問那一個B,仍然還不生效,到底發生了啥呢。
一時間沒有轍了,只有把那個feign的url統一改成了ip:port方式以解決燃眉之急,但是這樣並不好,畢竟這意味著上了生產環境A就固定的去呼叫其中一個B了,那叢集就沒有任何意義了。
大約過了一段時間,有同事說把nginx的負載均衡策略改成ip_hash方式就沒問題了,但這種方式沒有人去驗證,我也沒這個許可權去改伺服器的nginx.conf去驗證,因為在測試環境1對1的情況下都沒辦法保證讓分散式事務生效,那即使改成ip_hash又能如何呢?
後來大佬推薦我看一篇文章,說問題可能出在nginx上,這是文章的網址:nginx做轉發時,帶'_'的header內容丟失.....大體上將的就是nginx對帶下劃線的頭請求進行限制,會把它過濾到,這也就是造成nginx轉發時帶“_”的請求會丟失的情況的產生。後來感覺發現了新大陸,seata分散式事務的事務id的格式是TX_XID,那麼在TM拿到XID去呼叫RM時,會不會發生XID的丟失,導致RM沒有辦法將自己的分支事務註冊到那個XID所代表的全域性事務的下面,因此也不會被管理呢,自然不生效了。所以只要將nginx的那個對下劃線的請求過濾處理掉就好了。
想到後,激動不已,想在伺服器上模擬問題發生及解決,但是專案的nginx.conf豈容我這種小開發隨意改動!沒事,反正自己暫時無事,於是在自己電腦上寫了幾個服務demo,轉發用的是我自己虛擬機器裡面的nginx,自己也整了個seata-server執行起來,把各項配置都配好,開始模擬,果然,伺服器上的問題被我模擬出來了,接下來就是將nginx裡面的對下劃線的過濾去除,去除方式就是在nginx.conf的http部分新增一行: underscores_in_headers on;
再次測試,成功,url寫成域名也沒有關係了。
以下是我的模擬情況記錄表:
模擬方式 | 分散式事務是否生效 |
---|---|
url通過ip:port,nginx未改動 | 生效 |
url通過填寫域名,nginx未改動 | 不生效 |
url通過填寫域名,nginx僅僅將負載均衡策略改為ip_hash | 不生效 |
url通過填寫域名,nginx僅僅加入underscores_in_headers on | 生效 |