Gibt es eine bessere Möglichkeit, Anfragen an der "Tür" zu begrenzen?

8

Im Moment teste ich einen extrem einfachen Semaphore in einer meiner Produktionsregionen in AWS. Bei der Bereitstellung sprang die Latenz von 150 ms auf 300 ms. Ich nahm an, Latenz würde auftreten, aber wenn es fallen gelassen werden könnte, wäre das großartig. Das ist ein bisschen neu für mich, also experimentiere ich. Ich habe den Semaphor so eingestellt, dass 10000 Verbindungen möglich sind. Das ist die gleiche Anzahl wie die maximale Anzahl von Verbindungen, auf die Redis eingestellt ist. Ist der Code unten optimal? Wenn nicht kann jemand mir helfen, es zu optimieren, wenn ich etwas falsch mache, etc. Ich möchte dies als ein Stück Middleware behalten, so dass ich es einfach so auf dem Server n.UseHandler(wrappers.DoorMan(wrappers.DefaultHeaders(myRouter), 10000)) nennen kann.

%Vor%     
reticentroot 22.03.2017, 16:40
quelle

2 Antworten

1

Die von Ihnen umrissene Lösung weist einige Probleme auf. Aber machen wir zuerst einen kleinen Schritt zurück; Es gibt zwei Fragen, eine davon impliziert:

  1. Wie bewerten Sie eingehende Verbindungen effizient?
  2. Wie verhindern Sie, dass ein Back-End-Dienst mit ausgehenden Verbindungen überlastet wird?

Es klingt wie du willst, ist eigentlich die zweite, um zu verhindern, dass zu viele Anfragen auf Redis treffen. Ich werde mit der ersten beginnen und dann einige Kommentare zu der zweiten machen.

Ratenbegrenzende eingehende Verbindungen

Wenn Sie eingehende Verbindungen "an der Tür" wirklich bewerten wollen, sollten Sie dies normalerweise nicht tun, indem Sie innerhalb des Handlers warten . Mit Ihrer vorgeschlagenen Lösung akzeptiert der Dienst weiterhin Anfragen, die sich in der sema <- struct{}{} -Anweisung anstellen. Wenn die Last weiterhin besteht, wird der Dienst eventuell beendet, entweder durch das Fehlen von Sockets, Arbeitsspeicher oder einer anderen Ressource. Beachten Sie außerdem, dass bei einer ansteigenden Anfragerate der Sättigung des Semaphors eine Erhöhung der Latenzzeit durch die am Semaphor wartenden Goroutines vor der Bearbeitung der Anforderung auftritt.

Ein besserer Weg ist es, so schnell wie möglich zu antworten (besonders bei starker Belastung). Dies kann geschehen, indem ein 503 Service Unavailable zurück an den Client gesendet wird oder ein intelligenter Load-Balancer, der es zurückweist.

In Ihrem Fall könnte es zum Beispiel wie folgt aussehen:

%Vor%

Rate, die ausgehende Verbindungen zu einem Back-End-Dienst einschränkt

Wenn der Grund für das Ratenlimit darin besteht, die Überlastung eines Back-End-Service zu vermeiden, möchten Sie in der Regel darauf reagieren, dass dieser Service überlastet wird, und über die Anforderungskette Rückstau anwenden / p>

In der Praxis könnte dies etwas so Einfaches bedeuten, wie die gleiche Art von Semaphor-Logik wie oben in einen Wrapper zu schreiben, der alle Aufrufe an das Backend schützt, und einen Fehler über die Aufrufkette einer Anfrage zurückgibt, wenn der Semaphor überläuft / p>

Zusätzlich, wenn das Backend Statuscodes wie 503 (oder Äquivalent) sendet, sollten Sie diese Anzeige typischerweise auf die gleiche Weise nach unten propagieren oder auf ein anderes Fallback-Verhalten zur Verarbeitung der eingehenden Anfrage zurückgreifen.

Sie sollten auch in Betracht ziehen, dies mit einem Leistungsschalter zu kombinieren, um Versuche zu vermeiden, den Back-End-Dienst schnell aufzurufen, wenn es scheint nicht mehr reagiert zu haben oder nicht.

Die Begrenzung der Rate, indem die Anzahl der gleichzeitigen oder eingereihten Verbindungen wie oben beschrieben begrenzt wird, ist normalerweise ein guter Weg, um Überlastungen zu behandeln. Wenn der Back-End-Dienst überlastet ist, dauern die Anfragen in der Regel länger, wodurch die effektive Anzahl der Anfragen pro Sekunde reduziert wird. Wenn Sie jedoch aus bestimmten Gründen ein festes Limit für die Anzahl der Anforderungen pro Sekunde festlegen möchten, können Sie dies mit rate.Limiter anstelle eines Semaphors.

Ein Kommentar zur Leistung

Die Kosten für das Senden und Empfangen trivialer Objekte auf einem Kanal sollten unter einer Mikrosekunde liegen. Sogar auf einem stark überlasteten Kanal würde es keine zusätzliche Latenz von nur 150 ms geben, nur um mit dem Kanal zu synchronisieren. Unter der Annahme, dass die im Handler vorgenommene Arbeit ansonsten dieselbe ist, sollte die Latenzerhöhung mit Sicherheit mit irgendwo wartenden Goroutinen in Verbindung gebracht werden (zB auf I / O oder um Zugriff auf synchronisierte Regionen zu erhalten, die von anderen Goroutinen blockiert werden). .

Wenn Sie eingehende Anforderungen mit einer Rate erhalten, die nahe bei dem Grenzwert liegt, der mit dem festgelegten Grenzwert für Nebenläufigkeit von 10000 gehandhabt werden kann, oder wenn Sie Spitzen von Anfragen erhalten, ist es möglich, dass sich die durchschnittliche Latenz von Gououtines erhöht in der Warteschlange auf dem Kanal.

In jedem Fall sollte dies leicht messbar sein; Sie könnten z. B. Zeitstempel an bestimmten Punkten im Bearbeitungsweg verfolgen. Ich würde dies an einer Stichprobe (z. B. 0,1%) aller Anfragen tun, um zu vermeiden, dass die Protokollausgabe die Leistung beeinflusst.

    
Josef Grahn 08.09.2017, 16:00
quelle
1

Ich würde einen etwas anderen Mechanismus dafür verwenden, wahrscheinlich einen Arbeitspool wie hier beschrieben:

Ссылка

Ich würde sogar sagen, dass 10000 Goroutines laufen bleiben (sie werden schlafen und darauf warten, dass sie auf einem blockierenden Kanal empfangen werden, also ist es keine Ressourcenverschwendung) und senden die Anfrage + Antwort an den Pool, wenn sie reinkommen .

Wenn Sie eine Zeitüberschreitung wünschen, die mit einem Fehler reagiert, wenn der Pool voll ist, können Sie das auch mit einem select -Block implementieren.

    
Sudhir Jonathan 28.08.2017 07:16
quelle

Tags und Links