新聞中心
引言

B/S構(gòu)架的應(yīng)用越來(lái)越普及,但由于它有別于C/S構(gòu)架的特殊性,并發(fā)控制始終沒(méi)能得到很好的解決,如售票系統(tǒng)經(jīng)常會(huì)出現(xiàn)同一張火車(chē)票出售多次的現(xiàn)象。典型的案例如下:
例如若有兩個(gè)客戶(hù)端,A客戶(hù)先讀取了賬戶(hù)余額2000元,之后B客戶(hù)也讀取了賬戶(hù)余額2000元的數(shù)據(jù),A客戶(hù)提取了500元,對(duì)數(shù)據(jù)庫(kù)作了變更,此時(shí)數(shù)據(jù)庫(kù)中的余額為1500元,B客戶(hù)也要提取1300元,根據(jù)其所取得的資料,2000-1300將為700余額,若此時(shí)再對(duì)數(shù)據(jù)庫(kù)進(jìn)行變更,最后的余額700元就會(huì)不正確,應(yīng)當(dāng)是200元,問(wèn)題的出現(xiàn)是由于兩個(gè)客戶(hù)對(duì)同一條數(shù)據(jù)進(jìn)行并發(fā)訪(fǎng)問(wèn)造成的。
Web應(yīng)用中并發(fā)控制的特殊性
上述問(wèn)題在C/S構(gòu)架中可以通過(guò)長(zhǎng)事務(wù)來(lái)實(shí)現(xiàn),但Web應(yīng)用是基于Internet網(wǎng)絡(luò)環(huán)境的,其中的并發(fā)控制有其內(nèi)在的特殊性:
1. Web所基于的網(wǎng)絡(luò)協(xié)議HTTP(Hyper Text Transfer Protocol)是一種無(wú)連接的協(xié)議,數(shù)據(jù)庫(kù)服務(wù)器無(wú)法保存事務(wù)的狀態(tài)信息;
2. 用戶(hù)可以隨時(shí)中止或啟動(dòng)瀏覽器中當(dāng)前主頁(yè)上的事務(wù)。
由于上述特殊性,Web應(yīng)用中并發(fā)控制不能采用嚴(yán)格的長(zhǎng)事務(wù)來(lái)實(shí)現(xiàn),但可以長(zhǎng)事務(wù)的思路來(lái)實(shí)現(xiàn),在數(shù)據(jù)讀取的時(shí)候把相應(yīng)的數(shù)據(jù)鎖定,在更新階段把鎖放開(kāi),然后更新數(shù)據(jù)。
Web應(yīng)用中并發(fā)控制的實(shí)現(xiàn)
業(yè)務(wù)邏輯的實(shí)現(xiàn)過(guò)程中,往往需要保證數(shù)據(jù)訪(fǎng)問(wèn)的排他性。如在 金融 系統(tǒng)的日終結(jié)算處理中,我們希望針對(duì)某個(gè)cut-off時(shí)間點(diǎn)的數(shù)據(jù)進(jìn)行處理,而不希望在結(jié)算進(jìn)行過(guò)程中(可能是幾秒種,也可能是幾個(gè)小時(shí)),數(shù)據(jù)再發(fā)生變化。此時(shí),我們就需要通過(guò)一些機(jī)制來(lái)保證這些數(shù)據(jù)在某個(gè)操作過(guò)程中不會(huì)被外界修改,這樣的機(jī)制,就是所謂的“鎖”,即給選定的目標(biāo)數(shù)據(jù)上鎖,使其無(wú)法被其他程序修改。有兩種鎖機(jī)制:即通常所說(shuō)的“樂(lè)觀鎖(Optimistic Locking)” 和“悲觀鎖(Pessimistic Locking)”。
1.樂(lè)觀鎖(Optimistic Locking)
樂(lè)觀鎖(optimistic locking)則樂(lè)觀的認(rèn)為資料的存取很少發(fā)生同時(shí)存取的問(wèn)題,因而不作數(shù)據(jù)庫(kù)層次上的鎖定,為了維護(hù)正確的數(shù)據(jù),樂(lè)觀鎖定使用應(yīng)用程序上的邏輯實(shí)現(xiàn)版本控制來(lái)解決。
并發(fā)控制時(shí),數(shù)據(jù)不一致的情況一旦發(fā)生,有幾個(gè)解決的 方法 ,一種是先更新為主,一種是后更新的為主,比較復(fù)雜的就是檢查發(fā)生變動(dòng)的數(shù)據(jù)來(lái)實(shí)現(xiàn),或是檢查所有屬性來(lái)實(shí)現(xiàn)樂(lè)觀鎖定。
Hibernate通過(guò)版本號(hào)檢查來(lái)實(shí)現(xiàn)后更新為主,這也是Hibernate所推薦的方式,在數(shù)據(jù)庫(kù)中加入一個(gè)VERSON欄記錄,在讀取數(shù)據(jù)時(shí)連同版本號(hào)一同讀取,并在更新數(shù)據(jù)時(shí)遞增版本號(hào),然后比對(duì)版本號(hào)與數(shù)據(jù)庫(kù)中的版本號(hào),如果大于數(shù)據(jù)庫(kù)中的版本號(hào)則予以更新,否則就回報(bào)錯(cuò)誤。
以Hibernate實(shí)現(xiàn)版本號(hào)控制鎖定的話(huà),我們的對(duì)象中增加一個(gè)version屬性,例如:
public class MyAccount {
private int version;
....
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
....
}
|
而在映像文件中,我們使用optimistic-lock屬性設(shè)定version控制,屬性欄之后增加一個(gè)標(biāo)簽,例如:
optimistic-lock="version" |
設(shè)定好版本控制之后,在上例中如果B客戶(hù)試圖更新數(shù)據(jù),將會(huì)引發(fā)StableObjectStateException例外,我們可以捕捉這個(gè)例外,在處理中重新讀取數(shù)據(jù)庫(kù)中的數(shù)據(jù),同時(shí)將B客戶(hù)目前的數(shù)據(jù)與數(shù)據(jù)庫(kù)中的數(shù)據(jù)讀出來(lái),讓B客戶(hù)有機(jī)會(huì)比對(duì)不一致的數(shù)據(jù),以決定要變更的部份,或者您可以設(shè)計(jì)程式自動(dòng)讀取新的資料,并重復(fù)扣款業(yè)務(wù)流程,直到數(shù)據(jù)可以更新為止,這一切可以在后臺(tái)執(zhí)行,而不用讓您的客戶(hù)知道。在其它架構(gòu)中也可通過(guò)這種思路來(lái)實(shí)現(xiàn)樂(lè)觀鎖,但版本控制和沖突的檢測(cè)要在自己程序的程序中實(shí)現(xiàn)和維護(hù)。
#p#
2.悲觀鎖(Pessimistic Locking)
雖然樂(lè)觀鎖能夠提高系統(tǒng)的性能,但它是對(duì)發(fā)生沖突的訪(fǎng)問(wèn)進(jìn)行事后的補(bǔ)救,應(yīng)用在用戶(hù)輸入數(shù)據(jù)量很少的場(chǎng)合比較適合,但如果在 企業(yè) ERP,用戶(hù)與系統(tǒng)交互涉及大量數(shù)據(jù)在頁(yè)面表單上錄入,如果事后提交失敗后才提示用戶(hù)要重新錄入是很不現(xiàn)實(shí)的,所以有必要進(jìn)行事前控制,這就要采用悲觀鎖。
在多個(gè)客戶(hù)端可能讀取同一筆數(shù)據(jù)或同時(shí)更新一筆數(shù)據(jù)的情況下,防止同一個(gè)數(shù)據(jù)被修改而造成混亂,最簡(jiǎn)單的手段就是在讀取時(shí)對(duì)數(shù)據(jù)進(jìn)行鎖定,其它客戶(hù)端不能對(duì)同一筆數(shù)據(jù)進(jìn)行更新的讀取動(dòng)作。
悲觀鎖定(Pessimistic Locking)一如其名稱(chēng)所示,悲觀的認(rèn)定每次資料存取時(shí),其它的客戶(hù)端也會(huì)存取同一筆數(shù)據(jù),因此對(duì)該筆數(shù)據(jù)進(jìn)行事先鎖定,直到自己操作完成后解除鎖定。
悲觀鎖定通常透過(guò)系統(tǒng)或數(shù)據(jù)庫(kù)本身的功能來(lái)實(shí)現(xiàn),依賴(lài)系統(tǒng)或數(shù)據(jù)庫(kù)本身提供的鎖定機(jī)制,Hibernate即是如此,我們可以利用Query或Criteria的setLockMode()方法來(lái)設(shè)定要鎖定的表或列(row)及其鎖定模式,鎖定模式有以下的幾個(gè):
LockMode.WRITE:在insert或update時(shí)進(jìn)行鎖定,Hibernate會(huì)在save()方法時(shí)自動(dòng)獲得鎖定。
LockMode.UPGRADE:利用SELECT … FOR UPDATE進(jìn)行鎖定。
LockMode.UPGRADE_NOWAIT:利用SELECT … FOR UPDATE NOWAIT進(jìn)行鎖定,在Oracle環(huán)境下使用。
LockMode.READ:在讀取記錄時(shí)Hibernate會(huì)自動(dòng)獲得鎖定。
LockMode.NONE:沒(méi)有鎖定。
也可以在使用Session的load()或是lock()時(shí)指定鎖定模式以進(jìn)行鎖定。
如果數(shù)據(jù)庫(kù)不支持所指定的鎖定模式,Hibernate會(huì)選擇一個(gè)合適的鎖定替換,而不是丟出一個(gè)例外。
3.其它構(gòu)架中悲觀鎖的實(shí)現(xiàn)
Hibernate的悲觀鎖,也是基于數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn)。下面的代碼實(shí)現(xiàn)了對(duì)“用戶(hù)”查詢(xún)記錄的加鎖:
String sqlStr = "from userInfo as user where user.userId=’admin’"; |
query.setLockMode對(duì)查詢(xún)語(yǔ)句中,特定別名所對(duì)應(yīng)的記錄進(jìn)行加鎖(我們?yōu)閡serInfo類(lèi)指定了一個(gè)別名“user”),這里也就是對(duì)返回的所有user記錄進(jìn)行加鎖:
select tuser0_.id as id, tuser0_.userId as userId, tuser0_.group_id as group_id, |
通過(guò)上述轉(zhuǎn)換后的sql語(yǔ)句可知,Hibernate的加鎖其實(shí)是利用了數(shù)據(jù)庫(kù)的for update語(yǔ)句,在讀取階段對(duì)某條記錄的鎖定,而在更新階段提交,釋放鎖。
其實(shí)其它架構(gòu)也可以采取該思路,不過(guò),數(shù)據(jù)庫(kù)的for update語(yǔ)句的鎖定和釋放一定要在數(shù)據(jù)的同一個(gè)連接中,如果讀取階段和更新階段不是統(tǒng)一連接,即讀取之后斷開(kāi)了與數(shù)據(jù)庫(kù)的連接,則for update語(yǔ)句的鎖定立即失效,為此,如果其它架構(gòu)中要采取這種方式則要做相應(yīng)的調(diào)整。
首先,由于Web應(yīng)用是無(wú)狀態(tài)的,也就是說(shuō)數(shù)據(jù)庫(kù)的for update語(yǔ)句的鎖定和釋放不一定是數(shù)據(jù)的同一個(gè)連接,為此,采用痕跡跟蹤法,在讀取數(shù)據(jù)時(shí)生成唯一的序列號(hào)(serialId),建立與數(shù)據(jù)連接的映射,并放置一個(gè)map數(shù)據(jù)結(jié)構(gòu)中;在更新時(shí),通過(guò)該serialId在連接池中重新獲取該連接,用該連接去更新數(shù)據(jù)。
如果系統(tǒng)是采用dao讀取數(shù)據(jù),實(shí)體bean去更新數(shù)據(jù),則只要在更新數(shù)據(jù)之前斷開(kāi)讀取數(shù)據(jù)時(shí)的連接,則可以通過(guò)其它途徑更新數(shù)據(jù),如下代碼所示:
public void update (AbstractEntityData data, String[] selTeamName ,String serialId) |
其中,dao.closeConnect(serialId)是斷開(kāi)數(shù)據(jù)連接,bo.update(data)是通過(guò)EJB更新數(shù)據(jù)庫(kù)
4.序列號(hào)(serialId)的創(chuàng)建和維護(hù)
由于不同用戶(hù)可能同時(shí)建立連接或同一用戶(hù)先后建立連接,故創(chuàng)建序列號(hào)可以在讀取數(shù)據(jù)時(shí)通過(guò)sessionId和時(shí)間戳組合而成。而在操作的過(guò)程中,為了保持序列號(hào)不會(huì)丟失和唯一性,它不能放在session或application中,而是放在頁(yè)面的request對(duì)象里,通過(guò)它向其它頁(yè)面?zhèn)鬟f。
5.關(guān)聯(lián)表的鎖定
其實(shí),Hibernate的悲觀鎖方式只能對(duì)單個(gè)表的記錄進(jìn)行鎖定,但現(xiàn)實(shí)中,存在關(guān)聯(lián)更新的情況,即在更新主表的時(shí)候有可能會(huì)更新到與之相關(guān)的子表,與此同時(shí),其它用戶(hù)也可能通過(guò)其它主表更新相應(yīng)的子表同一條記錄。
有兩種方式處理,一是在讀取數(shù)據(jù)通過(guò)sql語(yǔ)句關(guān)聯(lián)子表相應(yīng)記錄,因?yàn)閒or update對(duì)所有關(guān)聯(lián)表中符合條件的記錄都會(huì)加鎖;二是為子表找一個(gè)入口表,在更新子表的同時(shí),必須更新子表的入口表。
6.例外操作的處理
采用這種方式,有一些例外情況必須小心處理,一是頁(yè)面的關(guān)閉,如果調(diào)用相應(yīng)的方法,如onbeforeunload()等,釋放對(duì)應(yīng)的數(shù)據(jù)庫(kù)連接;二是用戶(hù)非正常關(guān)機(jī)退出系統(tǒng),必須有數(shù)據(jù)庫(kù)周期清除無(wú)用的連接,如間隔二十分鐘等,來(lái)釋放讀取時(shí)對(duì)數(shù)據(jù)的鎖定,否則,該數(shù)據(jù)會(huì)長(zhǎng)時(shí)間被鎖定,直至應(yīng)用服務(wù)器重啟。
結(jié)論
軟件系統(tǒng)的并發(fā)控制一般是通過(guò)加鎖來(lái)實(shí)現(xiàn),同樣,Web應(yīng)用也是采用樂(lè)觀鎖和悲觀鎖來(lái)實(shí)現(xiàn),樂(lè)觀鎖是一種事后補(bǔ)救措施,是通過(guò)程序的邏輯控制版本來(lái)實(shí)現(xiàn)的,而悲觀鎖是事前的一種預(yù)防措施,它利用數(shù)據(jù)庫(kù)的鎖機(jī)制來(lái)實(shí)現(xiàn),Hibernate對(duì)它做了一層封裝,使應(yīng)用更加方便,為了讓其它架構(gòu)都能適用,本文還原了Hibernate的實(shí)現(xiàn)原理,提出一般的實(shí)現(xiàn)思路和注意實(shí)現(xiàn)。
本文標(biāo)題:Web應(yīng)用中并發(fā)控制的實(shí)現(xiàn)
標(biāo)題來(lái)源:http://www.5511xx.com/article/djjgedo.html


咨詢(xún)
建站咨詢(xún)
