天天看點

軟體事務記憶體導論(七)阻塞事務

我們經常會遇到這樣一種情況,即某事務t能否成功完成依賴于某個變量是否發生了變化,并且由于這種原因所引起的事務運作失敗也可能隻是暫時性的。作

為對這種暫時性失敗的響應,我們可能會傳回一個錯誤碼并告訴事務t等待一段時間之後再重試。然而在事務t等待期間,即使其他任務已經更改了事務t所依賴的

資料,事務t也沒法立即感覺到并重試了。為了解決這一問題,akka為我們提供了一個簡單的工具——retry(),該函數可以先将事務進行復原,并将事

務置為阻塞狀态直到該事物所依賴的引用對象發生變化或事務阻塞的時間超過了之前配置的阻塞逾時為止。我本人更願意将這一過程稱為“有意識地等待”,因為這

種說法聽起來比“阻塞”更合适一些。下面讓我們将阻塞(或有意識地等待)用于下面的兩個例子當中。

<b>在java中阻塞事務</b>

程式員一般都會對咖啡因上瘾,是以加班的時候任何主動要去拿些咖啡回來喝的人都知道不能空手而歸。但是這個拿咖啡的人很聰明,他沒有忙等(busy

wait)至咖啡罐被重新填滿,而是在akka的幫助下給自己設定了一個消息提醒,一旦咖啡罐有變化他就能收到這個通知。下面讓我們用retry()來實

現這個可以有意識等待的fillcup()函數。

在fillcup()函數中,我們将事務配置成blockingallowed,并将事務完成的逾時時間設為6秒。當發現目前沒有足夠數量的咖啡

時,fillcups()函數沒有簡單地傳回一個錯誤碼,而是調用了stmutil的retry()函數進行有意識地等待。這将使得目前事務進入阻塞狀

态,直到與之相關的cups引用發生變化為止。一旦有任何相關的引用發生改變,系統将啟動一個新事務将之前包含retry的原子性代碼進行重做。

下面讓我們通過調用fillcup()函數來觀察retry()的實際效果:

在main()函數中,我們啟動了一個每隔大約5秒就往咖啡壺重新裝填咖啡的定時任務。随後,第一個跑去拿咖啡的同僚a立即取走了20杯咖啡。緊接

着,當我們這邊自告奮勇去取咖啡的同僚b想再取走10杯咖啡時,他的動作将被阻塞直至重新裝填任務完成為止,而這種等待要比不斷重試的方案高效得多。重新

裝填的事務完成之後,同僚b的請求将被自動重試,而這一次他的請求成功完成了。如果重新裝填任務沒有在我們設定的逾時時間内發生,則請求咖啡的事務将會失

敗,在上例的try代碼塊中的那個請求就屬于這種情況。我們可以通過輸出日志來觀察到這一行為,同時也可以更深入地體會到retry()為我們帶來的便

利:

從上述輸出結果中我們可以看到,第一個倒20杯咖啡的請求是在程式開始運作之後0.4秒左右完成的。而第一個請求完成之後,咖啡壺裡就僅剩餘4杯咖

啡了,是以第二個倒10杯咖啡的請求就隻能被阻塞,直到程式運作至5秒左右時重新裝填的任務完成為止。在重新裝填的任務完成之後,倒10杯咖啡的事務被重

新啟動,并在程式運作到5秒多一點的時候成功完成。最後一個倒22杯咖啡的任務則由于在規定的逾時時間内沒有再次發生重新裝填而以失敗告終。

其實我們在日常工作中并不會經常用到retry(),隻有當程式邏輯需要執行某些操作、而這些操作又依賴于某些相關資料發生變化的情況下,我們才能受益于這個監控資料變化的特性。

<b>在scala中阻塞事務</b>

在上面java版的示例中,我們使用了一個提供了很多stm相關的便利接口的stmutils對象。而在scala中,我們可以直接使用

stmutils裡提供的各種特性(trait)。此外,我們在scala中同樣可以用工廠方法來建立transactionfactory。

在建立transactionfactory對象時,我們并沒有直接使用durationint來配置事務的逾時時間,而是用

inttodurationint()函數來完成從int到durationint的隐式轉換。通過scala隐式轉換所帶來的文法上的便利,我們在初始

化transactionfactory對象時隻需簡單調用6

seconds即可。示例代碼中餘下的部分就隻是從java到scala的一個簡單的翻譯而已,其最終的結果輸出如下所示:

filled up....20

........ at 0.325964

retry........ at 0.327425

retry........ at 0.329587

refilling.... at 5.105191

filled up....10

........ at 5.106074

retry........ at 5.106296

retry........ at 5.106466

failed: transaction defaulttransaction has timed with a

total timeout of 6000000000 ns