2011-03-10 66 views
7

你好
我想知道在3層應用程序中實現併發控制的最佳方法嗎? 可能首先想到的是:併發控制

  1. 客戶端想要編輯數據集中的記錄。
  2. 向服務器發送一個請求,請鎖定這個記錄
  3. 服務器接受/拒絕基於鎖表的編輯請求

基於這種方案的鎖應該有兩個記錄的引用鎖定和客戶端使用該記錄。
客戶端必須定期向服務器發送保持活動消息。保持活動狀態用於釋放鎖定的記錄,以防我們在編輯操作中丟失客戶端。

我將使用Delphi與datasnap。也許這是一個新手問題,但我必須問!

回答

3

我建立在jachguate的Optimistic Concurrency Control答案回答評論中提出的問題。

我更喜歡在任何地方使用OCC,因爲實現更容易。我要講一個使用object persistence framework的三層應用程序。我的推薦方案有三個級別:

  1. 行或對象級別控制,其中每個對象上存儲唯一的版本ID。如果您嘗試更新對象,則版本ID會自動更改。如果您的版本ID與已有的版本不匹配,則更新將失敗。

  2. 字段或列級鎖定。您發送原始對象的完整副本以及更新的副本。在應用新值之前,更新中的每個字段都會比較實際值和舊值。有可能要求用戶解決衝突而不是丟棄它們,但隨着提交中數據量的增加,這會變得混亂。

  3. 悲觀鎖定。每個對象都有一個通常爲空的鎖擁有者(該對象未被鎖定)。當你想編輯對象時,你首先要鎖定它。這裏的問題是需要整理鎖,並且圍繞它的業務規則可能很難理解(需要超時)。

這樣做的好處是大多數時候採用低成本的OCC路徑。對於發生很多但是爭用較少的事情,這些好處非常重要。考慮倉庫中的產品跟蹤 - 產品始終移動,但很少同時移動相同的物品,並且在解決問題時很容易(數量剩餘=原始數量少於我的移除和移除)。對於產品重新安置的複雜情況,在產品運輸過程中鎖定產品可能是有意義的(因爲這反映了物理情況)。

當你必須回到鎖定狀態時,能夠通知兩個用戶並建立通信通道通常很有用。至少在可用時通知想要鎖的用戶,最好允許他們發送消息給鎖持器,甚至可以允許他們強制鎖。然後通知鎖失敗者「Jo Smith已經讓你鎖定,你失去了你的改變」。讓辦公室的政治對此進行排序:)

我通常通過用戶投訴而不是錯誤報告驅動回退過程。如果用戶抱怨他們在特定過程中經常失去編輯,請更改它。如果用戶抱怨記錄過於頻繁鎖定,則必須重構對象映射以增加鎖定粒度或改變業務流程。

+0

很好的答案,但它似乎與DataSnap沒有關係,這是在問題中提到的。 DataSnap是(或可以)是一個無狀態的應用程序服務器,沒有內置的對你在這裏提到的鎖定模式的支持。 – jachguate 2011-03-11 01:02:31

+0

這是因爲我沒有使用過DataSnap,但我認爲應用了設計級別的細節。如果需要,可以將無狀態服務器設爲隱式狀態,但需要更多工作。 – 2011-03-11 02:25:10

3

我設計我的應用程序時考慮到了Optimistic concurrency control,當用戶想要編輯它並且不試圖控制併發時,不鎖定任何記錄。

在處理客戶端應用的更新時設置了適當的內置數據庫鎖定功能後,服務器端(應用程序或數據庫)完成重要計算和更新。 DataSnap自動事務回滾可防止這些鎖在發生故障時阻止其他併發用戶。

使用DataSnap,您可以完全控制兩個用戶通過使用適用於您的字段的ProviderFlags進行編輯衝突時防止數據丟失。設置您想要檢查的任何字段的pfInWhere自動在編輯/刪除時具有與讀取記錄時相同的值。當發生衝突時,您可以在應用程序服務器(提供程序OnUpdateError事件),客戶端(TClientDataSet OnReconcileError事件)上以編程方式作出反應,或者甚至要求用戶解決適當的衝突問題(請參閱「ReconcileErrorDialog」中的ReconcileErrorDialog新項目存儲庫)。與此同時,恕我直言,避免維護鎖列表,客戶端列表,鎖客戶端列表,保持活動的消息,健壯的應用程序服務器故障恢復和所有可能的故障,您將以清潔程序結束所需的複雜性和更好的解決方案

+1

就是我剛纔所說的。請注意,這在低爭用情況下效果更好,但我發現商業應用中經常出現這種情況。如果可以的話,只有在實際使用證明需要時才使用OCC並退回。你可能會發現你永遠不會退後,所以不要寫鎖定代碼,直到你必須。我使用的回退模式是行級OCC,然後是字段級OCC,然後是顯式鎖定。 – 2011-03-10 23:30:11

+0

我在想我的情況是悲觀鎖,因爲編輯一條記錄可能需要一段時間並改變很多值。結束時會出現錯誤,比如抱歉,我們不能保存剛剛完成的更改。 – Najem 2011-03-10 23:31:19

+0

@Najem:重新閱讀我的答案,你不必以這種錯誤結束,你只需要想另一種方式:發生碰撞時作出反應。如果可以通過編程方式確定要執行的操作,則可以對用戶透明地執行操作,如果不是,則可以詢問用戶如何執行操作而不會丟失編輯內容。恕我直言,它在很多方面都更好,例如,因爲編寫和維護代碼的複雜性比編寫適當的鎖定機制要複雜得多,只需提一個。你會得到更少的最終用戶抱怨,因爲有人在編輯高需求記錄時去吃午餐。 – jachguate 2011-03-10 23:43:52

-1

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的一個點,以防連接丟失。這不是一個明確的答案,只是推動方向,如果你想實施這種方法。
祝你好運

+1

對不起,你在這裏執行什麼?使用OPF和悲觀鎖定,你可以使用類似的東西來鎖定對象,但是你的回答是代碼太多,在這個階段沒有足夠的解釋。 – 2011-03-10 23:56:44

+0

@Pascal:感謝代碼會有所幫助 – Najem 2011-03-11 00:06:39

+0

@pascal您能解釋一下這種方法在故障轉移/負載平衡方案中的擴展嗎?我認爲,你堅持1個應用程序服務器限制。 – jachguate 2011-03-11 00:17:55