Tenemos una clase que transmite mensajes a un servicio sin estado de Service Fabric. Este servicio sin estado tiene una única partición, pero con muchas réplicas. El mensaje debe enviarse a todas las réplicas del sistema. Por lo tanto, consultamos el FabricClient para la partición única y todas las réplicas de esa partición. Utilizamos la comunicación HTTP estándar (el servicio sin estado tiene Communication Listener con un escucha OWIN autohospedado, usando WebListener / HttpSys) con una instancia compartida de HttpClient. Durante una prueba de carga, recibimos muchos errores durante el envío de mensajes. Tenga en cuenta que tenemos otros servicios en la misma aplicación, que también se comunican (WebListener / HttpSys, ServiceProxy y ActorProxy).

El código donde vemos Excepciones es (stacktrace está debajo del ejemplo de código):

private async Task SendMessageToReplicas(string actionName, string message)
{
  var fabricClient = new FabricClient();
  var eventNotificationHandlerServiceUri = new Uri(ServiceFabricSettings.EventNotificationHandlerServiceName);

  var promises = new List<Task>();
  // There is only one partition of this service, but there are many replica's
  Partition partition = (await fabricClient.QueryManager.GetPartitionListAsync(eventNotificationHandlerServiceUri).ConfigureAwait(false)).First();

  string continuationToken = null;
  do
  {
    var replicas = await fabricClient.QueryManager.GetReplicaListAsync(partition.PartitionInformation.Id, continuationToken).ConfigureAwait(false);
    foreach(Replica replica in replicas)
    {
      promises.Add(SendMessageToReplica(replica, actionName, message));
    }

    continuationToken = replicas.ContinuationToken;
  } while(continuationToken != null);

  await Task.WhenAll(promises).ConfigureAwait(false);
}


private async Task SendMessageToReplica(Replica replica, string actionName, string message)
{
  if(replica.TryGetEndpoint(out Uri replicaUrl))
  {
    Uri requestUri = UriUtility.Combine(replicaUrl, actionName);
    using(var response = await _httpClient.PostAsync(requestUri, message == null ? null : new JsonContent(message)).ConfigureAwait(false))
    {
      string responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
      if(!response.IsSuccessStatusCode)
      {
        throw new Exception();
      }
    }
  }
  else
  {
    throw new Exception();
  }
}

Se produce la siguiente excepción:

System.Fabric.FabricTransientException: Could not ping any of the provided Service Fabric gateway endpoints. ---> System.Runtime.InteropServices.COMException: Exception from HRESULT: 0x80071C49
at System.Fabric.Interop.NativeClient.IFabricQueryClient9.EndGetPartitionList2(IFabricAsyncOperationContext context)
at System.Fabric.FabricClient.QueryClient.GetPartitionListAsyncEndWrapper(IFabricAsyncOperationContext context)
at System.Fabric.Interop.AsyncCallOutAdapter2`1.Finish(IFabricAsyncOperationContext context, Boolean expectedCompletedSynchronously)
--- End of inner exception stack trace ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Company.ServiceFabric.ServiceFabricEventNotifier.<SendMessageToReplicas>d__7.MoveNext() in c:\work\ServiceFabricEventNotifier.cs:line 138

Durante el mismo período también vemos que se lanza esta excepción:

System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 0 - An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.) ---> System.ComponentModel.Win32Exception (0x80004005): An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.OpenAsync(CancellationToken cancellationToken)

Los registros de eventos en las máquinas en el clúster muestran estas advertencias:

Event ID: 4231
Source: Tcpip
Level: Warning
A request to allocate an ephemeral port number from the global TCP port space has failed due to all such ports being in use.

Event ID: 4227
Source: Tcpip
Level: Warning
TCP/IP failed to establish an outgoing connection because the selected local endpoint was recently used to connect to the same remote endpoint. This error typically occurs when outgoing connections are opened and closed at a high rate, causing all available local ports to be used and forcing TCP/IP to reuse a local port for an outgoing connection. To minimize the risk of data corruption, the TCP/IP standard requires a minimum time period to elapse between successive connections from a given local endpoint to a given remote endpoint.

Y finalmente, el registro de administración de Microsoft-Service Fabric muestra cientos de advertencias similares a

Event 4121
Source Microsoft-Service-Fabric
Level: Warning
client-02VM4.company.nl:19000/192.168.10.36:19000: error = 2147942452, failureCount=160522. Filter by (type~Transport.St && ~"(?i)02VM4.company.nl:19000") to get listener lifecycle. Connect failure is expected if listener was never started, or listener/its process was stopped before/during connecting.

Event 4097
Source Microsoft-Service-Fabric
Level: Warning
client-02VM4.company.nl:19000 : connect failed, having tried all addresses

Después de un tiempo, las advertencias se convierten en errores:

Event 4096
Source Microsoft-Service-Fabric
Level: Error
client-02VM4.company.nl:19000 failed to bind to local port for connecting: 0x80072747

¿Alguien puede decirnos por qué sucede esto y qué podemos hacer para resolverlo? ¿Estamos haciendo algo mal?

4
Michiel Overeem 18 oct. 2017 a las 15:07

3 respuestas

La mejor respuesta

Nosotros (yo trabajo con el OP) hemos estado probando esto y resultó ser el FabricClient como lo sugirió Esben Bach.

La documentación en el FabricClient también establece:

Se recomienda encarecidamente que comparta FabricClients tanto como sea posible. Esto se debe a que FabricClient tiene múltiples optimizaciones, como el almacenamiento en caché y el procesamiento por lotes, que de otro modo no podría utilizar por completo.

Parece que FabricClient se comporta como la clase HttpClient, donde también debe compartir la instancia y, cuando no lo haga, obtendrá el mismo problema, agotamiento de puertos.

Las excepciones comunes al trabajar con FabricClient documentación sin embargo, también menciona que cuando ocurre una FabricObjectClosedException debe:

Deseche el objeto FabricClient que está utilizando e instancia un nuevo objeto FabricClient.

Compartir el FabricClient soluciona el problema de agotamiento de puertos.

1
FuriousCactus 14 nov. 2017 a las 16:35

¿Cuál es el motivo para llamar a cada instancia de servicio existente?

Normalmente, debe llamar a una sola instancia de servicio proporcionada por el tiempo de ejecución de SF (intentará elegir una del mismo nodo / proceso o de otro nodo si este nodo está demasiado cargado).

Si necesita señalar algún cambio de estado / evento en todas sus instancias de servicio, tal vez esto debería hacerse dentro de la implementación del servicio para que compruebe este cambio de estado (desde un servicio con estado tal vez) o desde una cola de eventos pub-sub tiempo que necesita esta información (consulte, por ejemplo, https://github.com/loekd/ServiceFabric.PubSubActors).

Otra idea es enviar muchos mensajes a una instancia de servicio a la vez en otra acción que admita datos masivos.

Mantener la conexión abierta como en la respuesta anterior es una buena solución si debe enviar mensajes individuales desde una sola fuente con una frecuencia alta.

Además, la persona que llama debe hacer la resistencia de conexión, ver por ejemplo https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication#communicating-with-a-service

1
Robert 19 oct. 2017 a las 08:34

Parece que tiene un problema de agotamiento de puertos. Siempre que ese sea el caso, entonces deberá averiguar cómo reutilizar sus conexiones, o deberá implementar algún tipo de mecanismo de aceleración para no utilizar todos los puertos disponibles.

No estoy seguro de cómo se comporta el cliente Fabric, puede ser que sea responsable del agotamiento, o tal vez sea la parte del Servidor SQL para la que no podemos ver el código (pero desde que lo publicó en un registro, supongo que probablemente no esté relacionado con su ping prueba).

Mirando la fuente de referencias para httpwebresponse (https: / /github.com/Microsoft/referencesource/blob/master/System/net/System/Net/HttpWebResponse.cs) también podría ser que disponer de la respuesta (es decir, su declaración de uso para postasync) está cerrando la conexión de HttpClients . Lo que significa que no está reutilizando la conexión sino abriendo nuevas todo el tiempo.

Supongo que probar una variante que no elimine su httpwebresponse es algo bastante fácil.

1
Esben Bach 19 oct. 2017 a las 05:31