你好
我想知道在3層應用程序中實現併發控制的最佳方法嗎? 可能首先想到的是:併發控制
- 客戶端想要編輯數據集中的記錄。
- 向服務器發送一個請求,請鎖定這個記錄
- 服務器接受/拒絕基於鎖表的編輯請求
基於這種方案的鎖應該有兩個記錄的引用鎖定和客戶端使用該記錄。
客戶端必須定期向服務器發送保持活動消息。保持活動狀態用於釋放鎖定的記錄,以防我們在編輯操作中丟失客戶端。
我將使用Delphi與datasnap。也許這是一個新手問題,但我必須問!
你好
我想知道在3層應用程序中實現併發控制的最佳方法嗎? 可能首先想到的是:併發控制
基於這種方案的鎖應該有兩個記錄的引用鎖定和客戶端使用該記錄。
客戶端必須定期向服務器發送保持活動消息。保持活動狀態用於釋放鎖定的記錄,以防我們在編輯操作中丟失客戶端。
我將使用Delphi與datasnap。也許這是一個新手問題,但我必須問!
我建立在jachguate的Optimistic Concurrency Control答案回答評論中提出的問題。
我更喜歡在任何地方使用OCC,因爲實現更容易。我要講一個使用object persistence framework的三層應用程序。我的推薦方案有三個級別:
行或對象級別控制,其中每個對象上存儲唯一的版本ID。如果您嘗試更新對象,則版本ID會自動更改。如果您的版本ID與已有的版本不匹配,則更新將失敗。
字段或列級鎖定。您發送原始對象的完整副本以及更新的副本。在應用新值之前,更新中的每個字段都會比較實際值和舊值。有可能要求用戶解決衝突而不是丟棄它們,但隨着提交中數據量的增加,這會變得混亂。
悲觀鎖定。每個對象都有一個通常爲空的鎖擁有者(該對象未被鎖定)。當你想編輯對象時,你首先要鎖定它。這裏的問題是需要整理鎖,並且圍繞它的業務規則可能很難理解(需要超時)。
這樣做的好處是大多數時候採用低成本的OCC路徑。對於發生很多但是爭用較少的事情,這些好處非常重要。考慮倉庫中的產品跟蹤 - 產品始終移動,但很少同時移動相同的物品,並且在解決問題時很容易(數量剩餘=原始數量少於我的移除和移除)。對於產品重新安置的複雜情況,在產品運輸過程中鎖定產品可能是有意義的(因爲這反映了物理情況)。
當你必須回到鎖定狀態時,能夠通知兩個用戶並建立通信通道通常很有用。至少在可用時通知想要鎖的用戶,最好允許他們發送消息給鎖持器,甚至可以允許他們強制鎖。然後通知鎖失敗者「Jo Smith已經讓你鎖定,你失去了你的改變」。讓辦公室的政治對此進行排序:)
我通常通過用戶投訴而不是錯誤報告驅動回退過程。如果用戶抱怨他們在特定過程中經常失去編輯,請更改它。如果用戶抱怨記錄過於頻繁鎖定,則必須重構對象映射以增加鎖定粒度或改變業務流程。
我設計我的應用程序時考慮到了Optimistic concurrency control,當用戶想要編輯它並且不試圖控制併發時,不鎖定任何記錄。
在處理客戶端應用的更新時設置了適當的內置數據庫鎖定功能後,服務器端(應用程序或數據庫)完成重要計算和更新。 DataSnap自動事務回滾可防止這些鎖在發生故障時阻止其他併發用戶。
使用DataSnap,您可以完全控制兩個用戶通過使用適用於您的字段的ProviderFlags進行編輯衝突時防止數據丟失。設置您想要檢查的任何字段的pfInWhere自動在編輯/刪除時具有與讀取記錄時相同的值。當發生衝突時,您可以在應用程序服務器(提供程序OnUpdateError事件),客戶端(TClientDataSet OnReconcileError事件)上以編程方式作出反應,或者甚至要求用戶解決適當的衝突問題(請參閱「ReconcileErrorDialog」中的ReconcileErrorDialog新項目存儲庫)。與此同時,恕我直言,避免維護鎖列表,客戶端列表,鎖客戶端列表,保持活動的消息,健壯的應用程序服務器故障恢復和所有可能的故障,您將以清潔程序結束所需的複雜性和更好的解決方案
就是我剛纔所說的。請注意,這在低爭用情況下效果更好,但我發現商業應用中經常出現這種情況。如果可以的話,只有在實際使用證明需要時才使用OCC並退回。你可能會發現你永遠不會退後,所以不要寫鎖定代碼,直到你必須。我使用的回退模式是行級OCC,然後是字段級OCC,然後是顯式鎖定。 – 2011-03-10 23:30:11
我在想我的情況是悲觀鎖,因爲編輯一條記錄可能需要一段時間並改變很多值。結束時會出現錯誤,比如抱歉,我們不能保存剛剛完成的更改。 – Najem 2011-03-10 23:31:19
@Najem:重新閱讀我的答案,你不必以這種錯誤結束,你只需要想另一種方式:發生碰撞時作出反應。如果可以通過編程方式確定要執行的操作,則可以對用戶透明地執行操作,如果不是,則可以詢問用戶如何執行操作而不會丟失編輯內容。恕我直言,它在很多方面都更好,例如,因爲編寫和維護代碼的複雜性比編寫適當的鎖定機制要複雜得多,只需提一個。你會得到更少的最終用戶抱怨,因爲有人在編輯高需求記錄時去吃午餐。 – jachguate 2011-03-10 23:43:52
jachgate給出的方法非常好,而且可能更好,但如果您確實想要實現這一點,則需要在服務啓動時創建的服務器上有一個TThreadList
。使用TThreadList
是因爲它是線程安全的。您可以在每個表上使用TThreadList
,這樣您可以最大限度地降低瀏覽列表的性能。 要控制什麼是鎖着的,你需要一個創建並傳遞到列表
TLockedItem = class(TObject)
public
iPK: Integer;
iClientID: Integer;
end;
要進行實際的鎖定對象,你需要這樣的事:
function LockItem(pPK, pClientID: Integer): Boolean;
var
oLockedItem: TLockedItem;
oInternalList: TList;
iCont: Integer;
bExists: Boolean;
begin
bExists := False;
if (Assigned(oLockedList)) then
begin
oInternalList := oLockedList.LockList;
try
if (oInternalList.Count > 0) then
begin
iCont := 0;
while ((not bExists) and (iCont < oInternalList.Count)) do
begin
oLockedItem := TLockedItem(oInternalList[iCont]);
if (oLockedItem.iPK = pPk) then
bExists := True
else
Inc(iCont);
end;
end;
finally
oLockedList.UnlockList;
end;
if (not bExists) then
begin
oLockedItem := TLockedItem.Create;
oLockedItem.iPK := pPK;
oLockedItem.iClientID := pClientID;
oInternalList := oLockedList.LockList;
try
oInternalList.Add(oLockedItem);
finally
oLockedList.UnlockList;
end;
end;
end;
Result := bExists;
end;
這只是一個意識到你需要什麼。你將不得不用類似的邏輯做一個解鎖方法。你可能需要一個客戶列表,這將保持每個客戶端持有的每個TLockItem的一個點,以防連接丟失。這不是一個明確的答案,只是推動方向,如果你想實施這種方法。
祝你好運
很好的答案,但它似乎與DataSnap沒有關係,這是在問題中提到的。 DataSnap是(或可以)是一個無狀態的應用程序服務器,沒有內置的對你在這裏提到的鎖定模式的支持。 – jachguate 2011-03-11 01:02:31
這是因爲我沒有使用過DataSnap,但我認爲應用了設計級別的細節。如果需要,可以將無狀態服務器設爲隱式狀態,但需要更多工作。 – 2011-03-11 02:25:10