当从自托管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配置选项,例如:
在我看来,一旦达到某个阈值,消息就会在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
答案 0 :(得分:2)
经过Microsoft支持的长期支持请求后,我们设法确定了问题。
使用Begin / End Invoke委托模式调用WCF通道方法实际上变成了同步调用,而不是异步调用。
异步调用WCF方法的正确方法是异步委托,除了异步委托,可能包括线程池,原始线程或WCF异步回调。
最后我使用WCF async callbacks(可以应用于回调接口,虽然我找不到具体的例子)。
以下链接使其更加明确: http://blogs.msdn.com/drnick/archive/2007/06/12/begininvoke-bugs.aspx