1. 程式人生 > >[轉載]TransactionScope和分散式事務

[轉載]TransactionScope和分散式事務

原文連結:http://www.cnblogs.com/cn_wpf/archive/2007/08/06/844766.html

分散式事務聽起來很不錯,其實不然。它只是儘可能的降低資料不一致的可能性,並不能完全避免。從我的應用中來看,總數約5千萬的操作,錯了十幾個。當然,這個錯誤率完全可以忍受了。不能忍受的是當你的DB在cluster(叢集)當中,msdtc也會被作為一項資源出現,cluster的某些問題會詭異的導致msdtc不可用,問題排查起來是非常鬱悶的。大家都知道,作為大型系統,不太可能不用cluster,所以msdtc的問題會顯得很突出,伊給我的感覺實在是脆弱……

事務這個東東用來保證非常critical的資料的一致性,是否使用事務,要看你的業務需求.但是.NET 2.0在分散式事務支援上面不夠彪悍,這使得某些情況下,你要犧牲一些東西,如:某些可以並行執行的部分,但是不能並行執行。System.Transactions中偶還沒發現支援並行執行事務的Transactioin物件,所以如果想讓程式碼比較“傻瓜”,就不得不序列執行所有的操作步驟,當然無法獲得並行執行可並行步驟的快速響應。當然,系統的吞吐率可能因為序列提高了一點點,看你的需求了。

如果你不得不用分散式事務,那也得琢磨琢磨:

1. 這步操作一定得在事務當中嗎?這步操作如果沒完成或者失敗了,值得回滾整個事務嗎?難道沒有優雅的補償措施或者容錯措施?

2. 分散式事務涉及到的點,必須的這麼多?必須得實時的操作這一大串?不能通過通知類操作去精簡掉某些點?

3. 在發起分散式事務之後,你是不是做了事務無關的操作,儘管這些操作跟事務無關?(如,讀取資料、計算、等使用者返回訊息、等其他模組的呼叫返回等等)要知道事務應該儘快結束。

4. 你沒有把一些讀操作也算在事務裡面了吧?這是很容易犯的錯誤,你在事務中Enlist了一個select 操作。

5. 你的操作,某些步驟可以等全部操作完成之後再執行.這類操作具有明顯的通知類特點。通知類操作是說,我給你一個通知,並且我保證通知到了你;你必須吃下這個通知,並且保證處理成功,但是你不必我一通知你你就處理。這樣的操作很明顯可以用另外一個任務去搞。
and so on….有經驗的安達補充……

好吧,到這裡你不得不使用傳說中的“分散式事務”,那麼,偶建議你首先在應用程式伺服器安裝如下幾個補丁:

1. kb916002.(http://support.microsoft.com/kb/916002/en-us),它解決了“New request is not allowed to start because it should come with valid transaction descriptor.”的bug,不安裝它,你的應用會出現大量這種錯誤。

2. kb929246.(http://support.microsoft.com/kb/929246/en-us),kb網站上說,它解決了“
Consider the following scenario. You connect two Microsoft SQL Server databases in a transaction scope. The transaction scope is defined by using the System.Transactions.TransactionScope

class in the Microsoft .NET Framework 2.0. In this scenario, you intermittently receive the following error message:

System.InvalidOperationException: SqlConnection does not support parallel transactions

This problem only occurs if you enable connection pooling for the connection.

”。實際上你看到更多的是當你的Transaction被Abort時(如超時),SqlConnection並沒有被釋放。直接後果是一段時間之後,你的SqlConnection連線池中沒有可用連線,你無法拿到SqlConnection。當初報給GTech的時候感覺這種bug真是相當的愚蠢。是的,愚蠢!

3. kb936983(http://support.microsoft.com/kb/936983/en-us),kb網站上又說,丫解決了“
In a Microsoft .NET Framework 2.0-based application, you try to use the System.Transactions.CommittableTransaction.Commit method to commit a transaction. However, the call to theSystem.Transactions.CommittableTransaction.Commit method may always be blocked.

”。實際上你看到的情況是,你的程式運行了很長時間,但是你發現效能計數器.Net Provider for SqlServer/NumberOfStasisConnections開始有了示數,並且逐漸增加。於是你查看了程式碼,發現根本就不存在連線洩漏。接著你又開始查資料庫,你發現確實有在前一天就Enlist到分散式事務中的連線在等待命令。然後,計數器示數.Net Provider for SqlServer/NumberOfStasisConnections連續增加,你意識到程序中的殭屍連線越來越多,為了避免連線池被吃光,你不得不偷偷的重啟了服務,你甚至不得不為此寫了一個指令碼來定時重啟你的服務。最後,你開始罵娘。相對2,這個bug稍微nb了一些。

我們有理由懷疑MS並沒有仔細認真的測試System.Transactions和System.Data.SqlClient中的東西。

TransactionScope在文件中宣稱只在“必要”情況下才提升事務級別,但是事實上不是這樣。在TransactionScope內,只要你用不同的SqlConnection物件操作DB一次以上(不管你的目標是不是同一個例項、同一個庫),都會提升事務級別到分散式事務。很猥瑣吧?當然,從SqlClient目前實現上可以理解這個事情:

我們知道,在事務提交或回滾前,事務拿著的連線是不被釋放的,不管你在程式碼中有沒有呼叫Close或Dispose(這裡要稍微提一下Dispose模式。對這兩個方法的呼叫沒有本質上的不同)。所以就算是用相同的連線字串生成的倆SqlConnection,其內部引用的連線池中的internal連線也不是一個,也就是說,從DB的角度看,是兩個不同的Session.不同的Session不能共享一個本地事務(這句話本身可能需要小心求證),只能通過分散式事務管理。

但是微軟不能優化它嗎?能。連線字串完全可以解析成一堆field,就像SqlConnectionStringBuilder那樣,然後通過比較前面一個internal的連線中資訊來判斷是重用前一個連線還是從連線池裡面拿一個新的。當然還有一個坎,有任何一點不同的連線字串都會生成不同的連線池,就算是隻多一個空格也是這樣,連線池可能也得改造改造。

但是微軟沒有做這種改造,那我們就得自己做了——執行相同例項相同庫上操作都在一個SqlConnection上執行好了。

值得一提的是,DbConnection物件有一個EnlistTransaction方法,它給了我們手工分散式事務的機會。現在我們設計東西,大多數情況下是採用從上到下的設計,最後才會關心persistent方式。這個時候DbConnection.EnlistTransaction就顯得特別的有用。相比之下,TransactionScope過於死板。而且,手工控制Enlist給了我們並行執行一堆操作,最後大家一起提交或回滾的可能(只要你的需求能忍受稍高的錯誤率。分散式事務的兩階段提交方式理論上註定這個所謂的“稍高”不會比直接使用分散式事務高多少。)

最後稍微提提MSDTC的配置和DTCPing工具。

MSDTC配置主要是“安全配置”,並且要在應用程式伺服器(應用程式伺服器也是分散式事務的一個節點)、所有相關的DB伺服器上進行配置。怎麼配置google一把,到處都是。

配置完之後,應該把DTCPing拷貝到所有這些伺服器上,並且進行雙向的連通性確認。雙向是說A->B得通,B->A也得通。注意DTCPing的用法,上面寫的明明白白,就不多說了。

如果某臺機器不能解析別的機器名,修改一把hosts檔案就OK了。