networking - 发送 WCF 消息在负载下被延迟

标签 networking performance

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

一些背景:该应用程序是股票行情式服务。它从第 3 方服务器接收消息并将它们重新发布到连接到该服务的客户端。尽可能快地发布消息非常重要,并且在大多数情况下,从接收消息到将其发布到所有客户端之间的时间小于 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 绑定(bind)。它具有 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 委托(delegate)模式调用 WCF channel 方法实际上会变成同步调用,而不是异步调用。
    异步调用 WCF 方法的正确方法是通过异步委托(delegate)以外的任何方式,其中可能包括线程池、原始线程或 WCF 异步回调。
    最后我用了WCF async callbacks (可以应用于回调接口(interface),虽然我找不到具体的例子)。
    以下链接使这一点更加明确:
    https://docs.microsoft.com/en-us/archive/blogs/drnick/begininvoke-bugs

    关于networking - 发送 WCF 消息在负载下被延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1371218/

    相关文章:

    macos - 从另一台机器访问 Mac OS X 上的 Jenkins

    mysql - 如何加速MySQL查询: order by count

    tomcat - Tomcat 和(MOM 或 CORBA)之间的关系是什么

    linux - 如何通过backgm在网络连接之前保持闪屏直到网络正常并进入youtube页面

    c - 接受返回的套接字 ID 0——这是预期的吗?

    java - 为什么我的自下而上合并排序在 Java 中这么慢?

    android - 当适配器注册了观察者时,无法更改此适配器是否具有稳定 ID

    mysql - 需要优化以下mysql查询

    R - 是否有一种矢量化方式/预制函数可以快速生成两个向量之间的唯一集?

    c - C语言套接字编程