网络 – 发送WCF消息在负载下延迟

当从自托管WCF服务向许多客户端(大约10个左右)发送消息时,有时消息的延迟时间比我预期的要长(几秒钟发送到本地网络上的客户端).有谁知道为什么会这样,以及如何解决它?

一些背景:该应用程序是股票行情风格的服务.它从第三方服务器接收消息,并将它们重新发布到连接到该服务的客户端.尽可能快地发布消息非常重要,并且在大多数情况下,接收消息并将其发布到所有客户端之间的时间小于50毫秒(它快速接近DateTime.Now的分辨率).

在过去的几周里,我们一直在监控消息延迟2或3秒的某些情况.几天前,我们得到了一个大的飙升,消息被推迟了40-60秒.据我所知,消息不会被删除(除非删除整个连接).延迟似乎并非特定于任何一个客户;它会影响所有客户端(包括本地网络上的客户端).

我通过垃圾邮件发送ThreadPool向客户端发送消息.消息一到,我就每个客户端的每条消息调用一次BeginInvoke().理论上说,如果任何一个客户端接收消息的速度很慢(因为它是在拨号和下载更新或其他东西上),它将不会影响其他客户端.这不是我所观察到的;似乎所有客户端(包括本地网络上的客户端)都受到类似持续时间的延迟的影响.

我正在处理的消息量是每秒100-400.消息包含一个字符串,一个guid,一个日期,并根据消息类型,包含10-30个整数.我观察过他们使用Wireshark每个不到1kB.我们一次有10-20个客户连接.

WCF服务器托管在Windows 2003 Web Edition Server上的Windows服务中.我正在使用启用了SSL / TLS加密的NetTCP绑定和自定义用户名/密码身份验证.它具有4Mbit互联网连接,双核CPU和1GB内存,专用于此应用.该服务设置为ConcurrencyMode.Multiple.即使在高负载下,服务过程也很少超过20%的CPU使用率.

到目前为止,我已经调整了各种WCF配置选项,例如:

> serviceBehaviors / serviceThrottling / maxConcurrentSessions(目前为102)
> serviceBehaviors / serviceThrottling / maxConcurrentCalls(目前为64)
> bindings / netTcpBinding / binding / maxConnections(目前为100)
> bindings / netTcpBinding / binding / listenBacklog(目前为100)
> bindings / netTcpBinding / binding / sendTimeout(目前45s,虽然我已经尝试了高达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

最佳答案 在Microsoft支持的长期支持请求之后,我们设法确定了问题.

使用Begin / End Invoke委托模式调用WCF通道方法实际上变成了同步调用,而不是异步调用.

除异步委托之外,异步调用WCF方法的正确方法是任何方式,其中可能包括线程池,原始线程或WCF异步回调.

最后我使用了WCF async callbacks(可以应用于回调接口,虽然我找不到具体的例子).

以下链接使其更加明确:
http://blogs.msdn.com/drnick/archive/2007/06/12/begininvoke-bugs.aspx

点赞