2009-09-03 6 views
4

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 

Répondre

2

Après une demande de soutien à long avec le soutien de Microsoft, nous avons réussi à identifier le problème.

L'appel de méthodes de canal WCF à l'aide d'un modèle de délégué Begin/End Invoke se transforme en appels synchrones et non asynchrones. La méthode correcte pour appeler de façon asynchrone les méthodes WCF est par n'importe quel moyen sauf les délégués asynchrones, qui peuvent inclure le pool de threads, les threads bruts ou les rappels asynchrones WCF. En fin de compte j'ai utilisé WCF async callbacks (qui peut être appliqué à une interface de rappel, bien que je n'ai pas pu trouver d'exemples spécifiques de cela).

Le lien suivant rend cela plus explicite: http://blogs.msdn.com/drnick/archive/2007/06/12/begininvoke-bugs.aspx

Questions connexes