2009-08-06 62 views
1

我編寫了一個簡單的WCF服務,用於存儲用戶發送的消息,並在詢問時將這些消息發送給目標用戶。基於XML的存儲的WCF服務。併發性問題?

<messages recipient="username"> 
    <message sender="otheruser"> 
    ... 
    </message 
</messages> 

有可能不止一個用戶在同一時間將消息發送到同一收件人,可能導致:就目前而言,持續性是通過創建具有以下結構用戶名 .xml文件執行該xml文件將被同時更新。 WCF服務目前使用basicHttp綁定實現,沒有任何併發​​訪問規定。

什麼併發的風險有哪些?我應該如何處理它們?對正在訪問的xml文件執行ReadWrite鎖定? 目前該服務最多可以運行5個用戶,可能會增長到50個,但不會超過。

編輯: 由於客戶如上所述將實例化一個新的服務類每叫它做。 (InstanceContext是PerCall,ConcurrencyMode不相關)這是在服務上使用basicHttpBinding和默認設置所固有的。

下面的代碼:

public class SomeWCFService:ISomeServiceContract 
     { 
      ClassThatTriesToHoldSomeInfo useless; 

      public SomeWCFService() 
      { 
      useless=new ClassThatTriesToHoldSomeInfo(); 
      } 

      #region Implementation of ISomeServiceContract 
      public void IncrementUseless() 
      { 
      useless.Counter++; 
      } 
      #endregion 
     } 

的行爲是,如果它被寫:

public class SomeWCFService:ISomeServiceContract 
     { 
      ClassThatTriesToHoldSomeInfo useless; 

      public SomeWCFService() 
      {} 

      #region Implementation of ISomeServiceContract 
      public void IncrementUseless() 
      { 
      useless=new ClassThatTriesToHoldSomeInfo(); 
      useless.Counter++; 
      } 
      #endregion 
     } 

所以併發從來都不是問題,直到您嘗試訪問某些外部存儲的數據在數據庫或一份文件。 不足之處在於,除非將其存儲在外部,否則無法在該服務的方法調用之間存儲任何數據。

回答

2

如果您的WCF服務是一個單獨的服務,並保證是這樣,那麼你不需要做任何事情。由於WCF一次只允許處理一個請求,因此對用戶名文件的併發訪問不是問題,除非爲請求提供服務的操作會生成多個訪問同一文件的線程。然而,正如你可以想象的,單身服務並不是非常具有伸縮性,並且不是你想要的。

如果您的WCF服務是不是單身,同一個用戶文件,然後併發訪問是一個非常現實的場景,你必須明確地解決這個問題。您的服務的多個實例可能會同時嘗試訪問同一個文件以更新它,並且您將收到'無法訪問文件,因爲它正在被另一個進程'異常或類似情況使用。所以這意味着你需要同步對用戶文件的訪問。您可以使用監視器(鎖定),ReaderWriterLockSlim等。但是,您希望此鎖定以每個文件爲基礎進行操作。當不同文件上的更新正在進行時,您不想鎖定其他文件上的更新。因此,您需要爲每個文件保留一個鎖對象並鎖定該對象,例如

//when a new userfile is added, create a new sync object 
fileLockDictionary.Add("user1file.xml",new object()); 

//when updating a file 
lock(fileLockDictionary["user1file.xml"]) 
{ 
    //update file. 
} 

請注意,該字典也是一個共享資源,需要同步訪問。

現在,併發處理,並確保在適當的粒度對共享資源的同步訪問,不僅想出合適的解決方案方面,而且在調試和維護解決方案方面是非常困難的。調試多線程應用程序並不好玩,也很難重現問題。有時你沒有選擇,但有時你會做。那麼,爲什麼你沒有使用或考慮基於數據庫的解決方案有什麼特別的原因嗎?數據庫將爲您處理併發。你不需要做任何事情。如果你擔心購買數據庫的成本,那裏有非常好的開源數據庫,比如MySQL和PostgreSQL,這些數據庫不會讓你付出任何代價。

與基於XML文件的方法的另一個問題是其更新將是昂貴的。您將從內存中的用戶文件加載xml,創建一個消息元素並將其保存迴文件。隨着XML的增長,這個過程需要更長的時間,需要更多的內存等。它也會傷害你的可信度,因爲更新過程會持續更長的鎖。另外,I/O是昂貴的。也有配備了基於數據庫的解決方案的好處:交易,備份,能夠方便地查詢你的數據,複製,鏡像等

我不知道你的要求和限制,但我確實認爲文件 - 基於網絡的解決方案將會出現問題。

+0

爲什麼,感謝廣泛的答覆!爲了滿足客戶需求,我的程序設計儘可能多,所以我只想探索幾種解決方案來生成工作代碼。有一個CRUD操作數據庫在我的列表中(我正在考慮SQLight),但首先我想構建一個強大的基於fileIO的系統,只是爲了瞭解它是如何工作的以及出現什麼問題。 – Dabblernl 2009-08-07 07:44:37

+0

我不知道單身WCF服務:該服務在II6託管在遠程計算機上,客戶端通過basicHttpBinding的訪問。每個客戶都會產生自己的「IServiceContract」實施實例嗎?還是單身人士的默認? – Dabblernl 2009-08-07 07:45:35

+0

客戶端不知道或需要了解有關如何實例化服務的任何信息:單個,每個調用或每個會話。客戶端只需通知客戶端代理並開始調用它的方法。如何創建服務實例是服務方關心的問題。默認的InstanceContextMode是每個會話。基本上,每個客戶端連接都會創建一個新的服務實例,並且其生命週期必須與該連接的持續時間綁定。 – 2009-08-11 11:19:10

2

你需要增加它,並寫入磁盤之前讀取文件,所以你必須嘗試兩個重疊操作的(相當小)的危險 - 第二操作從磁盤中讀取第一個操作之前已寫入磁盤,當提交第二條消息時,第一條消息將被覆蓋。

一個簡單的答案可能是你排隊的消息,以確保它們被串行處理。當服務收到消息時,只需將內容轉儲到MSMQ隊列中即可。讓另一個單線程進程從隊列中讀取並將相應的更改寫入xml文件。這樣,您可以確保一次只寫入一個文件並解決任何併發問題。

+0

1爲MSMQ代替膠帶RPC。 – 2009-08-06 12:12:29

+0

還沒有MSMQ的經驗。將看看它。謝謝 – Dabblernl 2009-08-06 12:34:02

+0

我喜歡這個答案,但它很沉重,並沒有那麼快實施。他不使用數據庫,認爲他會想使用MSMQ? :) – 2009-08-12 02:37:17

0

那麼,它只是恰巧,我已經做了一些幾乎完全一樣,但它實際上並沒有消息......

以下是我會處理它。

您服務本身會談到中央對象(或對象),可調度基於發送消息的請求。

與每個發送者的對象,同時更新任何維護內部鎖。當它獲得修改的新請求時,它可以從磁盤讀取(如有必要),更新數據並寫入磁盤(如有必要)。

因爲不同的更新將在不同的線程發生,內部的鎖將被序列化。如果您調用任何「外部」對象以避免死鎖情況,請確保釋放鎖定。

如果I/O成爲瓶頸,你可以看看涉及將消息在一個文件中,單獨的文件,而不是立即將它們寫入磁盤,等等。事實上,我想存儲每個消息不同的策略用戶在一個單獨的文件夾中的確切原因。

最大的一點是,每個服務實例的行爲,本質上,一個適配器的核心類,那只有一個實例一個類將永遠是負責讀取/寫入的消息對於一個給定的收件人。其他類可以請求一個讀/寫,但他們並沒有真正執行它(甚至不知道它是如何執行的)。這也意味着,他們的代碼將看起來像「方法addMessage(消息)」,而不是「SaveMessages(GetMessages.Add(消息))」。

這就是說,使用數據庫是一個非常好的建議,並可能爲您節省很多頭痛。

1

的基本問題是,當你訪問一個全球性的資源(如一個靜態變量,或者文件系統中的文件),你需要確保你鎖定該資源或以某種方式序列化到它訪問。

這裏我的建議(如果你想只是把它做快速不使用數據庫或任何東西,這將是更好)將是您的服務代碼插入你的郵件進入隊列結構在內存中。

public MyService : IMyService 
{ 
    public static Queue queue = new Queue(); 
    public void SendMessage(string from, string to, string message) 
    { 
      Queue syncQueue = Queue.Synchronized(queue); 
      syncQueue.Enqueue(new Message(from, to, message)); 
    } 
} 

然後在應用程序的其他地方,您可以創建一個後臺線程,從該隊列中讀取並一次向文件系統寫入一個更新。

void Main() 
{ 


    Timer timer = new Timer(); 
    timer.Tick += (o, e) 
     { 
      Queue syncQueue = Queue.Synchronized(MyService.queue); 
      while(syncQueue.Count > 0) 
      { 
       Message message = syncQueue.Dequeue() as Message; 
       WriteMessageToXMLFile(message); 
      } 
      timer.Start(); 
     }; 
    timer.Start(); 

    //Or whatever you do here 
    StartupService(); 
} 

這不是漂亮(我不是100%肯定它編譯),但它應該工作。它有點像是「用我擁有的工具來完成它,而不是我想要的工具」,這種方法我認爲你正在尋找。

的客戶也斷了線儘快,而不是等待文件斷開連接之前被寫入到文件系統。這也可能很糟糕......如果您的應用在斷開連接並且後臺線程尚未寫入消息後斷開連接,則客戶可能不知道他們的消息沒有傳遞。

在這裏的其他方法也一樣有效......我想後的序列化方法,而不是鎖定的做法他人建議。

HTH, 安德森