Lors de l'envoi de messages d'un service WCF auto-hébergé à de nombreux clients (environ 10), les messages sont parfois retardés significativement plus longtemps que prévu (plusieurs secondes à envoyer à un client réseau local). Est-ce que quelqu'un a une idée de pourquoi cela serait et comment le réparer?Envoi de messages WCF en attente de chargement
En arrière-plan: l'application est un service de type ticker. Il reçoit les messages d'un serveur tiers et les re-publie aux clients qui se connectent au service. Il est très important que les messages soient publiés aussi rapidement que possible, et dans la plupart des cas, le délai entre la réception d'un message et sa publication à tous les clients est inférieur à 50ms (il est si rapide qu'il approche la résolution de DateTime.Now).
Au cours des dernières semaines, nous avons surveillé certaines occasions où les messages sont retardés de 2 ou 3 secondes. Il y a quelques jours, nous avons eu un gros pic et les messages ont été retardés de 40 à 60 secondes. Les messages ne sont pas abandonnés autant que je sache (sauf si la connexion entière est abandonnée). Les retards ne semblent pas être spécifiques à un client particulier; il affecte tous les clients (y compris ceux sur le réseau local). J'envoie des messages aux clients en spammant le ThreadPool. Dès que les messages arrivent, j'appelle BeginInvoke() une fois par message par client. La théorie est que si un client est lent à recevoir un message (parce que c'est sur la numérotation et le téléchargement des mises à jour ou quelque chose) que cela n'aura pas d'impact sur les autres clients. Ce n'est pas ce que j'observe cependant; il semble que tous les clients (y compris ceux sur le réseau local) sont impactés par le retard d'une durée similaire.
Le volume de messages que j'ai affaire est 100-400 par seconde. Les messages contiennent une chaîne, un guid, une date et, selon le type de message, 10-30 entiers. Je les ai observés en utilisant Wireshark comme étant moins de 1kB chacun. Nous avons 10-20 clients connectés à la fois.
Le serveur WCF est hébergé dans un service Windows sur un serveur Windows 2003 Web Edition. J'utilise la liaison NetTCP avec le cryptage SSL/TLS activé et une authentification par nom d'utilisateur/mot de passe personnalisé. Il dispose d'une connexion Internet 4Mbit, d'un processeur dual core et de 1GB ram et est dédié à cette application. Le service est défini sur ConcurrencyMode.Multiple. Le processus de service, même en cas de charge élevée, dépasse rarement 20% de l'utilisation du processeur.
Jusqu'à présent, je l'ai peaufiné différentes options de configuration de WCF tels que:
- serviceBehaviors/serviceThrottling/MaxConcurrentSessions (actuellement 102)
- serviceBehaviors/serviceThrottling/maxConcurrentCalls (actuellement 64)
- fixations/netTcpBinding/binding/maxConnections (actuellement 100)
- bindings/netTcpBinding/binding/listenBacklog (actuellement 100)
- bindings/netTcpBinding/bin ding/sendTimeout (actuellement 45s, bien que je l'ai essayé aussi haut que 3 minutes)
Il me semble que les messages sont mis en attente à l'intérieur WCF une fois un certain seuil est atteint (d'où la raison pour laquelle je suis de plus en plus être les limites d'étranglement). Mais pour affecter tous les clients, il faudrait maximiser toutes les connexions sortantes avec un ou deux clients lents. Est-ce que quelqu'un sait si cela est vrai des internes de la WCF?
Je peux également améliorer l'efficacité en fusionnant les messages entrants lorsque je les envoie au client. Cependant, je soupçonne qu'il se passe quelque chose sous-jacent et que la coalescence ne réglera pas le problème à long terme.
WCF Config (avec les noms de société a changé):
<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>
code utilisé pour envoyer des messages:
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
Un échantillon de l'un des types de messages:
<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