Ist asynchroner HttpClient aus .Net 4.5 eine schlechte Wahl für Anwendungen mit intensiver Last?


130

Ich habe kürzlich eine einfache Anwendung zum Testen des HTTP-Anrufdurchsatzes erstellt, die asynchron im Vergleich zu einem klassischen Multithread-Ansatz generiert werden kann.

Die Anwendung kann eine vordefinierte Anzahl von HTTP-Aufrufen ausführen und zeigt am Ende die Gesamtzeit an, die für deren Ausführung erforderlich ist. Während meiner Tests wurden alle HTTP-Aufrufe an meinen lokalen IIS-Server gesendet und eine kleine Textdatei (12 Byte groß) abgerufen.

Der wichtigste Teil des Codes für die asynchrone Implementierung ist unten aufgeführt:

public async void TestAsync()
{
    this.TestInit();
    HttpClient httpClient = new HttpClient();

    for (int i = 0; i < NUMBER_OF_REQUESTS; i++)
    {
        ProcessUrlAsync(httpClient);
    }
}

private async void ProcessUrlAsync(HttpClient httpClient)
{
    HttpResponseMessage httpResponse = null;

    try
    {
        Task<HttpResponseMessage> getTask = httpClient.GetAsync(URL);
        httpResponse = await getTask;

        Interlocked.Increment(ref _successfulCalls);
    }
    catch (Exception ex)
    {
        Interlocked.Increment(ref _failedCalls);
    }
    finally
    { 
        if(httpResponse != null) httpResponse.Dispose();
    }

    lock (_syncLock)
    {
        _itemsLeft--;
        if (_itemsLeft == 0)
        {
            _utcEndTime = DateTime.UtcNow;
            this.DisplayTestResults();
        }
    }
}

Der wichtigste Teil der Multithreading-Implementierung ist unten aufgeführt:

public void TestParallel2()
{
    this.TestInit();
    ServicePointManager.DefaultConnectionLimit = 100;

    for (int i = 0; i < NUMBER_OF_REQUESTS; i++)
    {
        Task.Run(() =>
        {
            try
            {
                this.PerformWebRequestGet();
                Interlocked.Increment(ref _successfulCalls);
            }
            catch (Exception ex)
            {
                Interlocked.Increment(ref _failedCalls);
            }

            lock (_syncLock)
            {
                _itemsLeft--;
                if (_itemsLeft == 0)
                {
                    _utcEndTime = DateTime.UtcNow;
                    this.DisplayTestResults();
                }
            }
        });
    }
}

private void PerformWebRequestGet()
{ 
    HttpWebRequest request = null;
    HttpWebResponse response = null;

    try
    {
        request = (HttpWebRequest)WebRequest.Create(URL);
        request.Method = "GET";
        request.KeepAlive = true;
        response = (HttpWebResponse)request.GetResponse();
    }
    finally
    {
        if (response != null) response.Close();
    }
}

Das Ausführen der Tests ergab, dass die Multithread-Version schneller war. Es dauerte ungefähr 0,6 Sekunden, um für 10.000 Anfragen fertig zu sein, während die asynchrone Anfrage ungefähr 2 Sekunden dauerte, um für die gleiche Menge an Last fertig zu sein. Dies war eine kleine Überraschung, da ich erwartet hatte, dass die asynchrone schneller sein würde. Vielleicht lag es daran, dass meine HTTP-Anrufe sehr schnell waren. In einem realen Szenario, in dem der Server eine aussagekräftigere Operation ausführen sollte und in dem auch eine gewisse Netzwerklatenz auftreten sollte, können die Ergebnisse umgekehrt werden.

Was mich jedoch wirklich beschäftigt, ist das Verhalten von HttpClient, wenn die Last erhöht wird. Da die Zustellung von 10.000 Nachrichten ungefähr 2 Sekunden dauert, dachte ich, dass die Zustellung der 10-fachen Anzahl von Nachrichten ungefähr 20 Sekunden dauern würde, aber das Ausführen des Tests ergab, dass die Zustellung der 100.000 Nachrichten ungefähr 50 Sekunden dauert. Darüber hinaus dauert die Zustellung von 200.000 Nachrichten in der Regel länger als 2 Minuten, und häufig schlagen einige Tausend Nachrichten (3-4.000) mit der folgenden Ausnahme fehl:

Eine Operation an einem Socket konnte nicht ausgeführt werden, weil dem System nicht genügend Pufferplatz zur Verfügung stand oder weil eine Warteschlange voll war.

Ich habe die fehlgeschlagenen IIS-Protokolle und -Vorgänge überprüft und bin nie auf den Server gelangt. Sie sind innerhalb des Clients fehlgeschlagen. Ich habe die Tests auf einem Windows 7-Computer mit dem Standardbereich für kurzlebige Ports von 49152 bis 65535 ausgeführt. Das Ausführen von netstat hat gezeigt, dass während der Tests etwa 5-6.000 Ports verwendet wurden, sodass theoretisch viel mehr verfügbar sein sollten. Wenn das Fehlen von Ports tatsächlich die Ursache für die Ausnahmen war, bedeutet dies, dass entweder netstat die Situation nicht ordnungsgemäß gemeldet hat oder HttClient nur eine maximale Anzahl von Ports verwendet, nach denen Ausnahmen ausgelöst werden.

Im Gegensatz dazu verhielt sich der Multithread-Ansatz zum Generieren von HTTP-Aufrufen sehr vorhersehbar. Ich brauchte ungefähr 0,6 Sekunden für 10.000 Nachrichten, ungefähr 5,5 Sekunden für 100.000 Nachrichten und wie erwartet ungefähr 55 Sekunden für 1 Million Nachrichten. Keine der Nachrichten ist fehlgeschlagen. Während der Ausführung wurden nie mehr als 55 MB RAM verwendet (laut Windows Task-Manager). Der Speicher, der beim asynchronen Senden von Nachrichten verwendet wurde, wuchs proportional zur Last. Bei den 200.000 Nachrichtentests wurden rund 500 MB RAM verwendet.

Ich denke, es gibt zwei Hauptgründe für die obigen Ergebnisse. Der erste ist, dass HttpClient sehr gierig zu sein scheint, wenn es darum geht, neue Verbindungen mit dem Server herzustellen. Die hohe Anzahl der von netstat gemeldeten verwendeten Ports bedeutet, dass HTTP Keep-Alive wahrscheinlich nicht viel davon profitiert.

Das zweite ist, dass HttpClient keinen Drosselungsmechanismus zu haben scheint. Tatsächlich scheint dies ein allgemeines Problem im Zusammenhang mit asynchronen Operationen zu sein. Wenn Sie eine sehr große Anzahl von Vorgängen ausführen müssen, werden alle gleichzeitig gestartet und ihre Fortsetzungen werden ausgeführt, sobald sie verfügbar sind. Theoretisch sollte dies in Ordnung sein, da bei asynchronen Vorgängen externe Systeme belastet werden. Wie oben gezeigt, ist dies jedoch nicht ganz der Fall. Wenn eine große Anzahl von Anforderungen gleichzeitig gestartet wird, erhöht sich die Speichernutzung und die gesamte Ausführung wird verlangsamt.

Ich habe es geschafft, bessere Ergebnisse in Bezug auf Speicher und Ausführungszeit zu erzielen, indem ich die maximale Anzahl asynchroner Anforderungen mit einem einfachen, aber primitiven Verzögerungsmechanismus begrenzt habe:

public async void TestAsyncWithDelay()
{
    this.TestInit();
    HttpClient httpClient = new HttpClient();

    for (int i = 0; i < NUMBER_OF_REQUESTS; i++)
    {
        if (_activeRequestsCount >= MAX_CONCURENT_REQUESTS)
            await Task.Delay(DELAY_TIME);

        ProcessUrlAsyncWithReqCount(httpClient);
    }
}

Es wäre sehr nützlich, wenn HttpClient einen Mechanismus zum Begrenzen der Anzahl gleichzeitiger Anforderungen enthalten würde. Bei Verwendung der Task-Klasse (die auf dem .NET-Thread-Pool basiert) wird die Drosselung automatisch erreicht, indem die Anzahl der gleichzeitigen Threads begrenzt wird.

Für eine vollständige Übersicht habe ich auch eine Version des asynchronen Tests erstellt, die auf HttpWebRequest anstelle von HttpClient basiert, und es geschafft, viel bessere Ergebnisse zu erzielen. Zunächst können Sie die Anzahl der gleichzeitigen Verbindungen (mit ServicePointManager.DefaultConnectionLimit oder über config) begrenzen. Dies bedeutet, dass die Ports nie ausgehen und bei keiner Anforderung fehlschlagen (HttpClient basiert standardmäßig auf HttpWebRequest) , aber es scheint die Einstellung des Verbindungslimits zu ignorieren).

Der asynchrone HttpWebRequest-Ansatz war immer noch etwa 50 bis 60% langsamer als der Multithreading-Ansatz, aber vorhersehbar und zuverlässig. Der einzige Nachteil war, dass es unter großer Last eine große Menge an Speicher verbrauchte. Zum Beispiel wurden rund 1,6 GB für das Senden von 1 Million Anfragen benötigt. Durch die Begrenzung der Anzahl gleichzeitiger Anforderungen (wie oben für HttpClient) konnte ich den verwendeten Speicher auf nur 20 MB reduzieren und eine Ausführungszeit erzielen, die nur 10% langsamer ist als beim Multithreading-Ansatz.

Nach dieser langen Präsentation lauten meine Fragen: Ist die HttpClient-Klasse aus .Net 4.5 eine schlechte Wahl für Anwendungen mit intensiver Last? Gibt es eine Möglichkeit, es zu drosseln, um die von mir erwähnten Probleme zu beheben? Wie wäre es mit der asynchronen Variante von HttpWebRequest?

Update (danke @Stephen Cleary)

Wie sich herausstellt, kann HttpClient genau wie HttpWebRequest (auf dem es standardmäßig basiert) die Anzahl der gleichzeitigen Verbindungen auf demselben Host mit ServicePointManager.DefaultConnectionLimit begrenzen. Das Seltsame ist, dass laut MSDN der Standardwert für das Verbindungslimit 2 ist. Ich habe dies auch auf meiner Seite mit dem Debugger überprüft, der darauf hinwies, dass tatsächlich 2 der Standardwert ist. Es scheint jedoch, dass der Standardwert ignoriert wird, wenn nicht explizit ein Wert für ServicePointManager.DefaultConnectionLimit festgelegt wird. Da ich bei meinen HttpClient-Tests keinen expliziten Wert dafür festgelegt habe, dachte ich, dass dieser ignoriert wurde.

Nach dem Setzen von ServicePointManager.DefaultConnectionLimit auf 100 wurde HttpClient zuverlässig und vorhersehbar (netstat bestätigt, dass nur 100 Ports verwendet werden). Es ist immer noch langsamer als asynchrones HttpWebRequest (um etwa 40%), aber seltsamerweise benötigt es weniger Speicher. Für den Test mit 1 Million Anforderungen wurden maximal 550 MB verwendet, verglichen mit 1,6 GB in der asynchronen HttpWebRequest.

Obwohl HttpClient in Kombination mit ServicePointManager.DefaultConnectionLimit die Zuverlässigkeit zu gewährleisten scheint (zumindest für das Szenario, in dem alle Anrufe an denselben Host getätigt werden), scheint die Leistung durch das Fehlen eines geeigneten Drosselungsmechanismus negativ beeinflusst zu werden. Etwas, das die gleichzeitige Anzahl von Anforderungen auf einen konfigurierbaren Wert begrenzt und den Rest in eine Warteschlange stellt, würde es für Szenarien mit hoher Skalierbarkeit viel besser geeignet machen.


4
HttpClientsollte respektieren ServicePointManager.DefaultConnectionLimit.
Stephen Cleary

2
Ihre Beobachtungen scheinen es wert zu sein, untersucht zu werden. Eines stört mich jedoch: Ich denke, es ist höchst erfunden, Tausende von asynchronen E / A auf einmal auszugeben. Ich würde das niemals in der Produktion machen. Die Tatsache, dass Sie asynchron sind, bedeutet nicht, dass Sie verrückt werden können, wenn Sie verschiedene Ressourcen verbrauchen. (Microsoft offizielle Proben sind auch in dieser Hinsicht ein wenig irreführend.)
usr

1
Drosseln Sie jedoch nicht mit Zeitverzögerungen. Drosseln Sie auf einer festen Parallelitätsstufe, die Sie empirisch bestimmen. Eine einfache Lösung wäre SemaphoreSlim.WaitAsync, obwohl dies auch nicht für beliebig große Aufgabenmengen geeignet wäre.
usr

1
@FlorinDumitrescu Zum Drosseln können Sie SemaphoreSlim, wie bereits erwähnt, oder ActionBlock<T>aus TPL Dataflow verwenden.
Svick

1
@svick, danke für deine Vorschläge. Ich bin nicht daran interessiert, einen Mechanismus zur Drosselung / Begrenzung der Parallelität manuell zu implementieren. Wie bereits erwähnt, diente die in meiner Frage enthaltene Implementierung nur zum Testen und zur Validierung einer Theorie. Ich versuche nicht, es zu verbessern, da es nicht in Produktion gehen wird. Mich interessiert, ob das .Net-Framework einen integrierten Mechanismus zur Begrenzung der Parallelität von asynchronen E / A-Vorgängen bietet (einschließlich HttpClient).
Florin Dumitrescu

Antworten:


64

Neben den in der Frage erwähnten Tests habe ich kürzlich einige neue Tests erstellt, die viel weniger HTTP-Aufrufe beinhalten (5000 im Vergleich zu 1 Million zuvor), jedoch auf Anforderungen, deren Ausführung viel länger dauerte (500 Millisekunden im Vergleich zu etwa 1 Millisekunde zuvor). Beide Testeranwendungen, die synchron Multithread-Anwendung (basierend auf HttpWebRequest) und die asynchrone E / A-Anwendung (basierend auf dem HTTP-Client), lieferten ähnliche Ergebnisse: ca. 10 Sekunden für die Ausführung mit ca. 3% der CPU und 30 MB Speicher. Der einzige Unterschied zwischen den beiden Testern bestand darin, dass der Multithread-Tester 310 Threads zur Ausführung verwendete, während der asynchrone nur 22 Threads ausführte.

Als Abschluss meiner Tests sind asynchrone HTTP-Aufrufe nicht die beste Option, wenn sehr schnelle Anforderungen bearbeitet werden. Der Grund dafür ist, dass beim Ausführen einer Aufgabe, die einen asynchronen E / A-Aufruf enthält, der Thread, in dem die Aufgabe gestartet wird, beendet wird, sobald der asynchrone Aufruf erfolgt und der Rest der Aufgabe als Rückruf registriert wird. Wenn der E / A-Vorgang abgeschlossen ist, wird der Rückruf zur Ausführung auf dem ersten verfügbaren Thread in die Warteschlange gestellt. All dies erzeugt einen Overhead, der schnelle E / A-Operationen effizienter macht, wenn sie auf dem Thread ausgeführt werden, der sie gestartet hat.

Asynchrone HTTP-Aufrufe sind eine gute Option, wenn lange oder möglicherweise lange E / A-Vorgänge ausgeführt werden, da keine Threads damit beschäftigt sind, auf den Abschluss der E / A-Vorgänge zu warten. Dies verringert die Gesamtzahl der von einer Anwendung verwendeten Threads, sodass mehr CPU-Zeit für CPU-gebundene Vorgänge aufgewendet werden kann. Darüber hinaus verhindert asynchrone E / A bei Anwendungen, die nur eine begrenzte Anzahl von Threads zuweisen (wie dies bei Webanwendungen der Fall ist), die Erschöpfung des Thread-Pool-Threads, die auftreten kann, wenn E / A-Aufrufe synchron ausgeführt werden.

Async HttpClient ist also kein Engpass für Anwendungen mit intensiver Last. Es ist nur so, dass es von Natur aus nicht sehr gut für sehr schnelle HTTP-Anforderungen geeignet ist, sondern ideal für lange oder potenziell lange Anforderungen, insbesondere in Anwendungen, in denen nur eine begrenzte Anzahl von Threads verfügbar ist. Es wird auch empfohlen, die Parallelität über ServicePointManager.DefaultConnectionLimit mit einem Wert zu begrenzen, der hoch genug ist, um ein gutes Maß an Parallelität zu gewährleisten, aber niedrig genug, um eine kurzlebige Portverarmung zu verhindern. Weitere Details zu den Tests und Schlussfolgerungen für diese Frage finden Sie hier .


3
Wie schnell ist "sehr schnell"? 1ms? 100ms? 1.000 ms?
Tim P.

Ich verwende so etwas wie Ihren "asynchronen" Ansatz, um eine Last auf einem unter Windows bereitgestellten WebLogic-Webserver wiederzugeben, aber es tritt ziemlich schnell ein Problem mit der ephemischen Portverarmung auf. Ich habe ServicePointManager.DefaultConnectionLimit nicht berührt und stelle bei jeder Anforderung alles (HttpClient und Antwort) bereit und erstelle es neu. Haben Sie eine Idee, was dazu führen kann, dass die Verbindungen offen bleiben und die Ports leer werden?
Iravanchi

@ TimP. Für meine Tests waren, wie oben erwähnt, "sehr schnell" die Anforderungen, deren Ausführung nur 1 Millisekunde dauerte. In der realen Welt wird dies immer subjektiv sein. Aus meiner Sicht kann etwas, das einer kleinen Abfrage in einer lokalen Netzwerkdatenbank entspricht, als schnell angesehen werden, während etwas, das einem API-Aufruf über das Internet entspricht, als langsam oder möglicherweise langsam angesehen werden kann.
Florin Dumitrescu

1
@Iravanchi Bei "asynchronen" Ansätzen werden das Senden von Anforderungen und die Verarbeitung von Antworten separat ausgeführt. Wenn Sie viele Anrufe haben, werden alle Anfragen sehr schnell gesendet und die Antworten werden bearbeitet, sobald sie eintreffen. Da Sie Verbindungen erst nach Eingang ihrer Antworten entsorgen können, kann sich eine große Anzahl gleichzeitiger Verbindungen ansammeln und Ihre kurzlebigen Ports erschöpfen. Sie sollten die maximale Anzahl gleichzeitiger Verbindungen mit ServicePointManager.DefaultConnectionLimit begrenzen.
Florin Dumitrescu

1
@FlorinDumitrescu, ich möchte auch hinzufügen, dass Netzwerkanrufe von Natur aus unvorhersehbar sind. Dinge, die in 90% der Fälle in 10 ms ausgeführt werden, können Blockierungsprobleme verursachen, wenn diese Netzwerkressource in den anderen 10% der Fälle überlastet oder nicht verfügbar ist.
Tim P.

27

Eine Sache, die sich auf Ihre Ergebnisse auswirken könnte, ist, dass Sie mit HttpWebRequest den ResponseStream nicht erhalten und diesen Stream verbrauchen. Mit HttpClient wird der Netzwerkstrom standardmäßig in einen Speicherstrom kopiert. Um HttpClient auf die gleiche Weise zu verwenden, wie Sie derzeit HttpWebRquest verwenden, müssten Sie dies tun

var requestMessage = new HttpRequestMessage() {RequestUri = URL};
Task<HttpResponseMessage> getTask = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);

Die andere Sache ist, dass ich nicht wirklich sicher bin, was der wirkliche Unterschied aus der Threading-Perspektive ist, den Sie tatsächlich testen. Wenn Sie in die Tiefen von HttpClientHandler eintauchen, wird einfach Task.Factory.StartNew ausgeführt, um eine asynchrone Anforderung auszuführen. Das Threading-Verhalten wird genauso an den Synchronisationskontext delegiert wie Ihr Beispiel mit dem Beispiel HttpWebRequest.

Zweifellos fügt HttpClient etwas Overhead hinzu, da standardmäßig HttpWebRequest als Transportbibliothek verwendet wird. So können Sie mit einem HttpWebRequest direkt mit HttpClientHandler immer eine bessere Leistung erzielen. Die Vorteile von HttpClient liegen in den Standardklassen wie HttpResponseMessage, HttpRequestMessage, HttpContent und allen stark typisierten Headern. An sich ist es keine Perf-Optimierung.


(alte Antwort, aber) HttpClientscheint einfach zu bedienen zu sein und ich dachte, dass asynchron der richtige Weg ist, aber es scheint viele "Aber und Wenn" zu geben. Vielleicht HttpClientsollte das neu geschrieben werden, damit es intuitiver zu bedienen ist? Oder dass die Dokumentation wirklich die wichtigen Dinge betonte, wie man sie am effizientesten nutzt?
Mortb

@mortb, Flurl.Http flurl.io ist ein intuitiver zu verwendender Wrapper von HttpClient
Michael Freidgeim

1
@ MichaelFreidgeim: Danke, obwohl ich inzwischen gelernt habe, mit dem HttpClient zu leben ...
mortb

17

Dies beantwortet zwar nicht direkt den asynchronen Teil der OP-Frage, behebt jedoch einen Fehler in der von ihm verwendeten Implementierung.

Wenn Sie möchten, dass Ihre Anwendung skaliert wird, vermeiden Sie die Verwendung instanzbasierter HttpClients. Der Unterschied ist riesig! Abhängig von der Last sehen Sie sehr unterschiedliche Leistungszahlen. Der HttpClient wurde so konzipiert, dass er für alle Anforderungen wiederverwendet werden kann. Dies wurde von den Leuten im BCL-Team bestätigt, die es geschrieben haben.

Ein aktuelles Projekt war es, einem sehr großen und bekannten Online-Computerhändler dabei zu helfen, für einige neue Systeme den Black Friday / Holiday-Verkehr zu skalieren. Bei der Verwendung von HttpClient sind einige Leistungsprobleme aufgetreten. Da es implementiert wird IDisposable, haben die Entwickler das getan, was Sie normalerweise tun würden, indem sie eine Instanz erstellt und in eine using()Anweisung eingefügt haben . Als wir mit dem Lasttest begannen, brachte die App den Server in die Knie - ja, der Server nicht nur die App. Der Grund ist, dass jede Instanz von HttpClient einen E / A-Abschlussport auf dem Server öffnet. Aufgrund der nicht deterministischen Finalisierung von GC und der Tatsache, dass Sie mit Computerressourcen arbeiten, die sich über mehrere OSI-Schichten erstrecken , kann das Schließen von Netzwerkports eine Weile dauern. In der Tat Windows-Betriebssystem selbstDas Schließen eines Ports kann bis zu 20 Sekunden dauern (laut Microsoft). Wir haben Ports schneller geöffnet, als sie geschlossen werden konnten - Erschöpfung des Server-Ports, die die CPU auf 100% hämmerte. Mein Fix bestand darin, den HttpClient in eine statische Instanz zu ändern, die das Problem löste. Ja, es handelt sich um eine verfügbare Ressource, aber der Overhead wird durch den Leistungsunterschied bei weitem aufgewogen. Ich empfehle Ihnen, einige Lasttests durchzuführen, um zu sehen, wie sich Ihre App verhält.

Auch beantwortet unter Link unten:

Was ist der Aufwand für das Erstellen eines neuen HttpClient pro Aufruf in einem WebAPI-Client?

https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client


Ich habe genau das gleiche Problem festgestellt, das die Erschöpfung des TCP-Ports auf dem Client verursacht. Die Lösung bestand darin, die HttpClient-Instanz für lange Zeiträume zu leasen, in denen iterative Aufrufe getätigt wurden, und nicht für jeden Aufruf zu erstellen und zu entsorgen. Die Schlussfolgerung, zu der ich kam, war "Nur weil es Dispose implementiert, heißt das nicht, dass es billig ist, es zu entsorgen".
PhillipH

Wenn der HttpClient statisch ist und ich bei der nächsten Anforderung einen Header ändern muss, was bedeutet das für die erste Anforderung? Kann das Ändern des HttpClient schaden, da er statisch ist, z. B. das Ausgeben eines HttpClient.DefaultRequestHeaders.Accept.Clear (); ? Wenn ich beispielsweise Benutzer habe, die sich über Token authentifizieren, müssen diese Token als Header für die Anforderung an die API hinzugefügt werden, bei denen es sich um verschiedene Token handelt. Hätte es nicht nachteilige Auswirkungen, wenn der HttpClient nicht statisch wäre und dieser Header dann in HttpClient geändert würde?
Crizzwald

Wenn Sie HttpClient-Instanzmitglieder wie Header / Cookies usw. verwenden müssen, sollten Sie keinen statischen HttpClient verwenden. Andernfalls wären Ihre Instanzdaten (Header, Cookies) für jede Anfrage gleich - sicherlich NICHT das, was Sie wollen.
Dave Black

da dies der Fall ist ... wie würden Sie verhindern, was Sie oben in Ihrem Beitrag beschreiben - gegen Last? Load Balancer und mehr Server darauf werfen?
Crizzwald

@crizzwald - In meinem Beitrag habe ich die verwendete Lösung notiert. Verwenden Sie eine statische Instanz von HttpClient. Wenn Sie Header / Cookies auf einem HttpClient verwenden müssen, würde ich nach einer Alternative suchen.
Dave Black
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.