581
技術社區[雲棲]
軟件事務內存導論(十)處理寫偏斜異常
處理寫偏斜異常
在6.6節中,我們曾經簡單討論了寫偏斜(write skew)以及Clojure
STM是如何解決這個問題的。Akka同樣提供了處理寫偏斜問題的支持,但是需要我們配置一下才能生效。OK,一聽到配置這個詞可能讓你覺得有些提心吊
膽,但實際操作起來其實起來還是蠻簡單的。下麵就讓我們首先了解一下Akka在不進行任何配置情況下的默認行為。
讓我們回顧一下之前曾經見到過的那個多個賬戶共享同一個聯合餘額最低限製例子。首先我們創建了一個名為Portfolio的類來保存支票賬戶餘額和
儲蓄賬戶餘額。根據銀行規定,這兩個賬戶的總餘額不得低於$1000。在Portfolio類的代碼中我們用Java重新實現了withdraw()函
數。在該函數中,我們先讀取兩個賬戶的餘額,將二者相加得到總餘額,並在等待一個故意插進去的延時(引入這個延時的目的是為了人為製造事務衝突的環境)之
後,從其中一個賬戶餘額中減掉給定數量的金額(當然,在操作之前需要判斷減掉這個數量後總餘額不少於$1000)。最後需要注意的
是,withdraw()函數是在一個使用了默認設置的事務中完成上述操作的。
01 |
public class Portfolio {
|
02 |
final private Ref<Integer> checkingBalance = new Ref<Integer>( 500 );
|
03 |
final private Ref<Integer> savingsBalance = new Ref<Integer>( 600 );
|
04 |
public int getCheckingBalance() { return checkingBalance.get(); }
|
05 |
public int getSavingsBalance() { return savingsBalance.get(); }
|
07 |
public void withdraw( final boolean fromChecking, final int amount) {
|
08 |
new Atomic<Object>() {
|
09 |
public Object atomically() {
|
10 |
final int totalBalance =
|
11 |
checkingBalance.get() + savingsBalance.get();
|
12 |
try { Thread.sleep( 1000 ); } catch (InterruptedException ex) {}
|
13 |
if (totalBalance - amount >= 1000 ) {
|
15 |
checkingBalance.swap(checkingBalance.get() - amount);
|
17 |
savingsBalance.swap(savingsBalance.get() - amount);
|
21 |
"Sorry, can't withdraw due to constraint violation" );
|
下麵讓我們創建兩個事務來並發地更改賬戶內的餘額:
01 |
public class UsePortfolio {
|
02 |
public static void main( final String[] args) throws InterruptedException {
|
03 |
final Portfolio portfolio = new Portfolio();
|
04 |
int checkingBalance = portfolio.getCheckingBalance();
|
05 |
int savingBalance = portfolio.getSavingsBalance();
|
06 |
System.out.println( "Checking balance is " + checkingBalance);
|
07 |
System.out.println( "Savings balance is " + savingBalance);
|
08 |
System.out.println( "Total balance is " +
|
09 |
(checkingBalance + savingBalance));
|
10 |
final ExecutorService service = Executors.newFixedThreadPool( 10 );
|
11 |
service.execute( new Runnable() {
|
12 |
public void run() { portfolio.withdraw( true , 100 ); }
|
14 |
service.execute( new Runnable() {
|
15 |
public void run() { portfolio.withdraw( false , 100 ); }
|
19 |
checkingBalance = portfolio.getCheckingBalance();
|
20 |
savingBalance = portfolio.getSavingsBalance();
|
21 |
System.out.println( "Checking balance is " + checkingBalance);
|
22 |
System.out.println( "Savings balance is " + savingBalance);
|
23 |
System.out.println( "Total balance is " +
|
24 |
(checkingBalance + savingBalance));
|
25 |
if (checkingBalance + savingBalance < 1000 )
|
26 |
System.out.println( "Oops, broke the constraint!" );
|
正如我們在輸出結果中所看到的那樣,在默認情況下,Akka沒能避免寫偏斜問題,兩個事務違反了銀行的規定,即都從賬戶裏取出了錢。
Checking balance is 500
Savings balance is 600
Total balance is 1100
Checking balance is 400
Savings balance is 500
Total balance is 900
Oops, broke the constraint!
現在到了該徹底解決這個問題的時候了。讓我們祭出TransactionFactory這個能幫助我們在程序裏對事物進行配置的法寶,在Portfolio類的第9行插入下麵這段創建工廠實例的代碼:
1 |
akka.stm.TransactionFactory factory = |
2 |
new akka.stm.TransactionFactoryBuilder()
|
在插進來的這幾行代碼中,我們創建了一個TransactionFactoryBuilder,並將writeSkew和trackReads屬性
分別設置為false和true。與Clojure
STM對於ensure的處理類似,這兩個設置項的目的是告訴事務要在其運行過程中對讀操作進行追蹤,同時也會使事務在讀數據的過程中對賬戶餘額變量加讀
鎖直至提交開始為止。
除了上麵提到的幾處更改之外,Portfolio和UsePortfolio的其他代碼都保持不變。而在對事務進行了上述設置之後,其輸出結果如下所示:
Checking balance is 500
Savings balance is 600
Total balance is 1100
Sorry, can't withdraw due to constraint violation
Checking balance is 400
Savings balance is 600
Total balance is 1000
由於並發執行的不可預測性,我們不能確定兩個事務到底哪個會勝出。但是我們可以從輸出結果中看到,在所有操作結束後兩個賬戶的餘額是不同的,而在6.6節的Clojure示例中,最終兩個賬戶餘額是相同的。我們可以通過多次運行這兩個實例來觀察二者之間的差異。
在本節我們是用Java完成整個示例的。如果換成Scala,則我們可以使用在6.10節中學習的語法來配置事務的writeSkew和trackReads屬性。
文章轉自 並發編程網-ifeve.com
最後更新:2017-05-22 16:01:55