4
votes

Asp.net Core HttpClient a de nombreuses connexions TIME_WAIT ou CLOSE_WAIT

J'utilise AddHttpClient () injection de dépendances pour ajouter un client nommé à un service transitoire. Parfois, lorsque j'exécute netstat -a sur le serveur, je vois de nombreuses connexions ouvertes avec le statut TIME_WAIT ou CLOSE_WAIT . Je pense que ces connexions consomment tellement de ressources que d’autres connexions TCP ne peuvent pas fonctionner. Est-ce possible? Existe-t-il un moyen de les arrêter et est-ce sûr?

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        ServicePointManager.DefaultConnectionLimit = 200;

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        services.AddHttpClient(FirebaseService.FirebaseServiceClient, ConfigureFirebaseClient);

        services.AddTransient<FirebaseService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();
    }

    void ConfigureFirebaseClient(HttpClient client)
    {
        var scopes = new string[] { "https://www.googleapis.com/auth/firebase.messaging" };

        Stream certificateStream = File.OpenRead("firebase-adminsdk.json");

        var serviceCredentials = GoogleCredential.FromStream(certificateStream);
        certificateStream.Close();

        var scopedCredentials = serviceCredentials.CreateScoped(scopes);
        var token = scopedCredentials.UnderlyingCredential.GetAccessTokenForRequestAsync().GetAwaiter().GetResult();
        client.SetBearerToken(token);
    }
}

public class FirebaseService
{
    public static string FirebaseServiceClient = "FirebaseServiceClient";

    private HttpClient _client;

    private readonly ILogger<FirebaseService> _logger;
    private readonly string _messagingUrl; 

    public FirebaseService(
        ILogger<FirebaseService> logger,
        IHttpClientFactory clientFactory)
    {
        _logger = logger;
        _messagingUrl = "https://fcm.googleapis.com/v1/projects/test2/messages:send";
        _client = clientFactory.CreateClient(FirebaseServiceClient);
    }

    public async Task<string> PostToFirebase(Dictionary<string, string> payload)
    {
        HttpResponseMessage result = null;
        string cont = null;
        try
        {
            var content = JsonConvert.SerializeObject(payload, Formatting.None);
            var stringContent = new StringContent(content, Encoding.UTF8, "application/json");

            result = await _client.PostAsync(_messagingUrl, stringContent);
            cont = await result.Content.ReadAsStringAsync();
            return cont;
        }
        finally
        {
            result?.Dispose();
        }
    }

}

public class ValuesController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    public ValuesController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var payload = new Dictionary<string, string>();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 100; i++)
        {
            FirebaseService firebaseService = (FirebaseService)_serviceProvider.GetService(typeof(FirebaseService));
            var task = firebaseService.PostToFirebase(payload);
            tasks.Add(task);
            Console.WriteLine(i);
        }

        await Task.WhenAll(tasks.ToArray());

        //Console.WriteLine(result);

        return Ok();
    }

}


9 commentaires

aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong et vous devriez envisager d'utiliser HttpClientFactory puisque vous êtes dans le Core land: docs.microsoft.com/en-us/dotnet/standard/...


Bonjour, c'est ce que je fais en fait. J'utilise des clients nommés comme dans docs.microsoft.com/en-us/aspnet/core/fundamentals/... @IanKemp


Pour plus d'informations sur ce problème, consultez github.com/dotnet/corefx/issues/35698 # issuecomment-468880589


@Ahmet le problème que vous avez lié indique des problèmes avec votre code , en oubliant de disposer les réponses. Cela n'a rien à voir avec HttpClient. Une réponse contient un flux réseau à partir duquel votre application lira le contenu. Le message ne peut pas savoir que vous en avez terminé si vous n'appelez pas dispose . La connexion réseau ne sera pas fermée tant que le message de réponse ne sera pas récupéré


@Ahmet veuillez poster votre code actuel afin que cette question puisse obtenir une réponse correcte. Celui qui est accepté est tout simplement faux


D'accord. J'ai ajouté au message. Mais comme vous pouvez le lire sur github.com/dotnet/corefx/issues/35698# issueecomment-468880589 Il existe une confusion entre les développeurs principaux et les rédacteurs de documents, consommateurs (moi) du composant @PanagiotisKanavos


@Ahmet publie votre code , pas un lien vers un projet. Le code du numéro fait moins de 10 lignes. Il n'y a pas non plus de confusion - les réponses n'étaient pas disposées. Le même problème se produit avec HttpWebRequest si vous oubliez de supprimer la réponse. En fait, je parie que vous avez désactivé la limite de connexion au domaine, sinon vous constateriez que vous ne pouvez établir que deux connexions à la fois. Ce serait parce que chaque réponse non distribuée garderait la connexion ouverte jusqu'à ce qu'elle soit GCd


Peut-être que nous parlons de deux choses différentes mais vous me dites que j'ai oublié de faire quelque chose qui n'est pas documenté dans docs.microsoft.com/en-us/aspnet/core/fundamentals/...


Je n'ai pas changé la limite de domaine en illimité. Je l'ai changé à 100 car j'ai besoin de plus de 2 demandes simultanées par domaine. Comme je l'ai dit, même lorsque j'ai ajouté la disposition, cela n'a pas résolu le problème. J'ai ajouté le code à la question 👍


3 Réponses :


-2
votes

Eh bien, c'est parce que vous l'utilisez peut-être avec la mauvaise méthodologie de gestion de la durée de vie. HttpClient a un problème d'épuisement de socket, il doit donc être utilisé comme un singleton si possible.

Cet article répondra à votre question. Lisez également entrez la description du lien ici à propos d'une solution de contournement pour les changements DNS.


9 commentaires

J'ai lu cet article. En raison du problème d'épuisement des sockets, IHttpClientFactory est introduit. Et j'utilise ça. Il est censé réutiliser les mêmes sources, mais ce n'est pas le cas.


Ensuite, vous feriez mieux d'implémenter le vôtre, il est assez facile d'en enregistrer un en tant que singleton si vous utilisez un conteneur IoC.


Je peux toujours y parvenir, mais quel est l'intérêt d'utiliser un client nommé de IHttpClientFactory alors?


@Ahmet cette réponse est tout simplement fausse. HttpClientFactory met en cache et réutilise les instances HttpClient Handler donc il n'est pas nécessaire d'utiliser un singleton. Un singleton ne pourra pas détecter les modifications DNS. C'est pourquoi HttpClientFactory recycle ces gestionnaires après un certain temps, généralement 2 minutes


@ KristófTóth oui, c'est pourquoi HttpClientFactory a été créé, pour mettre en cache et recycler les instances. Le problème auquel vous avez lié indique qu'un singleton ne doit pas être utilisé car il ne respecte pas les changements DNS


@PanagiotisKanavos peut-être que j'ai compris quelque chose de mal, mais d'après ce que je comprends, l'usine a des problèmes similaires selon les fils publiés ici. C'est pourquoi j'ai suggéré d'utiliser un singleton en conjonction avec ConnectionLeaseTimeout | PooledConnectionLifetime. Mais je me trompe peut-être.


Non, ce n'est pas le cas. Le problème que l'OP a ouvert dans Github montre que le code de l'OP n'a pas disposé correctement les réponses , ce qui signifie que les connexions de socket derrière chaque réponse sont restées ouvertes jusqu'à ce que les réponses soient GC.


J'ai choisi cela comme réponse car la mise en œuvre de la mienne était la seule solution qui fonctionnait. Je me fiche des changements DNS pour le moment. Mon seul problème était que les connexions n'étaient pas réutilisées et que de nouvelles prises étaient ouvertes et maintenues ouvertes pendant des minutes, ce qui a conduit à l'épuisement des prises. @ KristófTóth l'a bien dit. HttpClientFactory a également des problèmes similaires.


Comme Microsoft l'a confirmé, HttpClientFactory est censé réutiliser les mêmes connexions, github.com/dotnet / corefx / issues / 35698 # issuecomment-469068526 mais ce n'est pas le cas.



1
votes

CLOSE_WAIT - l'autre côté a fermé la connexion.

TIME_WAIT - le point de terminaison local (votre application) a fermé la connexion.

Les deux connexions sont conservées quelques minutes de plus juste au cas où il y aurait des paquets retardés de l'autre côté.

"Je crois que ces connexions prennent tellement de ressources que les autres connexions TCP ne peuvent pas fonctionner. Est-ce possible?" - Je crois que non. Ils ne font que garder un port ouvert. Cela dépend du nombre. Si vous en avez quelques centaines, tout ira bien.

"Y a-t-il un moyen de les arrêter, et est-ce sûr?" - Je ne pense pas. Ils ont tous le même PID, donc si vous essayez d'en tuer un, toutes vos applications s'arrêteront.

Dans l'attente de meilleures réponses.


5 commentaires

Je fais des milliers de demandes de publication sur la même URL en peu de temps. Ainsi, lorsque ces connexions restent ouvertes pendant quelques minutes, elles s'accumulent à plus de 9k connexions ouvertes Après avoir passé 9k, je commence à recevoir des exceptions de socket.


Pouvez-vous s'il vous plaît ajouter du code et nous dire quelles exceptions obtenez-vous?


J'ai créé un dépôt de test pour cela. github.com/ahmettahasakar/HttpClientTest J'ai fait en sorte qu'il y ait 100 publications simultanées sur Firebase Cloud Messaging. Même si je poste des charges utiles vierges, les connexions semblent toujours rester ouvertes. Pour que le projet fonctionne, vous avez besoin d'un fichier d'authentification Firebase. Vous pouvez en créer un facilement depuis firebase.google.com/docs/admin/setup Là a également reçu une correspondance de Microsoft sur github.com/dotnet/corefx/issues / 35698 # issuecomment-468880589


@Ahmet, vous devriez avoir publié ce code dans la question. Cela montre que vous avez oublié de supprimer les messages de réponse


J'ai suivi la documentation. Il manquait de la documentation. J'avais même demandé aux rédacteurs de la documentation, et ils étaient également confus. De plus, même lorsque j'ai ajouté l'élimination, cela n'a pas résolu le problème.



1
votes

J'ai réalisé que les connexions avec lesquelles j'avais des problèmes ne provenaient pas de client.PostAsync () . En fait, ils provenaient des demandes d'authentification de jeton Firebase dans l'action de configuration du client de IHttpClientFactory. C'est pourquoi lorsque je suis passé à un singleton ou à une propriété statique, CreateClient (clientName) n'a pas été appelé plus d'une fois, et le problème a disparu. Bien que cela ait été clairement écrit dans la documentation, je l'avais manqué. Chaque fois que CreateClient est appelé, une nouvelle instance de HttpClient est créée et l'action de configuration est appelée.


0 commentaires