【发布时间】:2009-09-03 02:54:54
【问题描述】:
从自托管 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 绑定。它具有 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
【问题讨论】: