2009-09-03 96 views
4

從自託管WCF服務向多個客戶端發送消息(大約10個左右)時,消息的延遲時間明顯比我預期的要長(幾秒鐘後發送給客戶端)本地網絡)。有沒有人有一個想法,爲什麼這將是如何解決它?發送加載時延遲的WCF消息


一些背景:應用程序是股票行情樣式服務。它接收來自第三方服務器的消息,並將它們重新發布到連接到該服務的客戶端。儘快發佈消息非常重要,在大多數情況下,從收到消息到發佈給所有客戶端之間的時間少於50ms(速度非常快,接近DateTime.Now的解決方案)。

在過去的幾周裏,我們一直在監測一些消息延遲2或3秒的場合。前幾天,我們得到了一個很大的高峯,信息被延遲了40-60秒。據我所知,消息不會被丟棄(除非整個連接被丟棄)。延誤似乎並非特定於任何一個客戶;它會影響所有客戶端(包括本地網絡上的客戶端)。

我通過發送ThreadPool發送消息給客戶端。只要消息到達,我就爲每個客戶端的每條消息調用一次BeginInvoke()。理論上說,如果任何一個客戶端收到一條消息的速度很慢(因爲它是在撥號和下載更新或某事),它不會影響其他客戶端。這不是我正在觀察的;似乎所有的客戶端(包括本地網絡上的客戶端)都受到延遲時間相似的影響。

我正在處理的消息量是100-400每秒。消息包含一個字符串,一個guid,一個日期,根據消息類型,包含10-30個整數。我使用Wireshark觀察它們每個小於1kB。我們同時擁有10-20個客戶端。

WCF服務器託管在Windows 2003 Web版服務器上的Windows服務中。我正在使用啓用了SSL/TLS加密和自定義用戶名/密碼認證的NetTCP綁定。它具有4Mbit的互聯網連接,雙核CPU和1GB RAM,專門用於此應用程序。該服務設置爲ConcurrencyMode.Multiple。即使在高負載的情況下,服務過程很少超過20%的CPU使用率。

到目前爲止,我已經調整了各種WCF配置選項,如:

  • serviceBehaviors/serviceThrottling/maxConcurrentSessions(目前102)
  • serviceBehaviors/serviceThrottling/maxConcurrentCalls(目前64)
  • 綁定/ netTcpBinding/binding/maxConnections(當前爲100)
  • bindings/netTcpBinding/binding/listenBacklog(當前爲100)
  • bindings/netTcpBinding/bin丁/的SendTimeout(目前45秒,雖然我已經試過了高達3分鐘)

像郵件被內部WCF一旦達到某個閾值(所以爲什麼我是越來越排隊在我看來,節流限制)。但是爲了影響所有客戶端,需要用一個或兩個慢速客戶端來最大化所有傳出連接。有誰知道這是否是WCF內部的真實情況?

我還可以通過在傳入消息時將它們發送到客戶端來提高效率。但是,我懷疑有一些潛在的問題,並且從長遠來看,合併不能解決問題。

WCF配置(公司名稱變更):

<system.serviceModel> 

<host> 
<baseAddresses> 
    <add baseAddress="net.tcp://localhost:8100/Publisher"/> 
</baseAddresses> 
</host> 

<endpoint address="ThePublisher" 
           binding="netTcpBinding" 
           bindingConfiguration="Tcp" 
             contract="Company.Product.Server.Publisher.IPublisher" /> 

</behavior> 

代碼用來發送消息:

Private Sub HandleDataBackground(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs) 
      If Me._FeedDataQueue.Count > 0 Then 
       ' Dequeue any items received in last 50ms. 
       While True 
        Dim dataAndReceivedTime As DataWithReceivedTimeArg 
        SyncLock Me._FeedDataQueue 
         If Me._FeedDataQueue.Count = 0 Then Exit While 
         dataAndReceivedTime = Me._FeedDataQueue.Dequeue() 
        End SyncLock 

        ' Publish data to all clients. 
        Me.SendDataToClients(dataAndReceivedTime) 
       End While 
      End If 
    End Sub 

    Private Sub SendDataToClients(ByVal data As DataWithReceivedTimeArg) 
      Dim clientsToReceive As IEnumerable(Of ClientInformation) 
      SyncLock Me._ClientInformation 
       clientsToReceive = Me._ClientInformation.Values.Where(Function(c) Contract.CollectionContains(c.ContractSubscriptions, data.Data.Contract) AndAlso c.IsUsable).ToList() 
      End SyncLock 

      For Each clientInfo In clientsToReceive 
       Dim futureChangeMethod As New InvokeClientCallbackDelegate(Of DataItem)(AddressOf Me.InvokeClientCallback) 
       futureChangeMethod.BeginInvoke(clientInfo, data.Data, AddressOf Me.SendDataToClient) 
      Next 

    End Sub 
    Private Sub SendDataToClient(ByVal callback As IFusionIndicatorClientCallback, ByVal data As DataItem) 
     ' Send 
     callback.ReceiveData(data) 
    End Sub 

    Private Sub InvokeClientCallback(Of DataT)(ByVal client As ClientInformation, ByVal data As DataT, ByVal method As InvokeClientCallbackMethodDelegate(Of DataT)) 
     Try 
      ' Send 
      If client.IsUsable Then 
       method(client.CallbackObject, data) 
       client.LastContact = DateTime.Now 
      Else 
       ' Make sure the callback channel has been removed. 
       SyncLock Me._ClientInformation 
        Me._ClientInformation.Remove(client.SessionId) 
       End SyncLock 
      End If 
     Catch ex As CommunicationException 
      .... 
     Catch ex As ObjectDisposedException 
      .... 
     Catch ex As TimeoutException 
      .... 
     Catch ex As Exception 
      .... 
     End Try 
    End Sub 

消息類型的一個樣本:

<DataContract(), KnownType(GetType(DateTimeOffset)), KnownType(GetType(DataItemDepth)), KnownType(GetType(DataItemDepthDetail)), KnownType(GetType(DataItemHistory))> _ 
Public MustInherit Class DataItem 
    Implements ICloneable 

    Protected _Contract As String 
    Protected _MessageId As Guid 
    Protected _TradeDate As DateTime 

    <DataMember()> _ 
    Public Property Contract() As String 
    ... 
    End Property 

    <DataMember()> _ 
    Public Property MessageId() As Guid 
    ... 
    End Property 

    <DataMember()> _ 
    Public Property TradeDate() As DateTime 
    ... 
    End Property 

    Public MustOverride Function Clone() As Object Implements System.ICloneable.Clone 
End Class 

<DataContract()> _ 
Public Class DataItemDepth 
    Inherits DataItem 

    Protected _VolumnPriceDetail As IList(Of DataItemDepthItem) 

    <DataMember()> _ 
    Public Property VolumnPriceDetail() As IList(Of DataItemDepthItem) 
    ... 
    End Property 

    Public Overrides Function Clone() As Object 
    ... 
    End Function 
End Class 


<DataContract()> _ 
Public Class DataItemDepthItem 
    Protected _Volume As Int32 
    Protected _Price As Int32 
    Protected _BidOrAsk As BidOrAsk ' BidOrAsk is an Int32 enum 
    Protected _Level As Int32 

    <DataMember()> _ 
    Public Property Volume() As Int32 
    ... 
    End Property 

    <DataMember()> _ 
    Public Property Price() As Int32 
    ... 
    End Property 

    <DataMember()> _ 
    Public Property BidOrAsk() As BidOrAsk ' BidOrAsk is an Int32 enum 
    ... 
    End Property 

    <DataMember()> _ 
    Public Property Level() As Int32 
    ... 
    End Property 
End Class 

回答

2

與微軟支持長期支持請求後,我們成功地找出問題。

使用Begin/End調用WCF通道方法調用委託模式實際上變成了同步調用,而不是異步調用。

異步調用WCF方法的正確方法是除了異步委託之外的任何方式,它可能包括線程池,原始線程或WCF異步回調。

最後我用WCF async callbacks(它可以應用到回調接口,雖然我找不到具體的例子)。

以下鏈接更加明確: http://blogs.msdn.com/drnick/archive/2007/06/12/begininvoke-bugs.aspx