JS implementiert Anforderungsdispatcher

JS implementiert Anforderungsdispatcher

Vorwort: JS unterstützt natürlich parallele Anforderungen, bringt aber gleichzeitig einige Probleme mit sich, z. B. eine übermäßige Belastung des Zielservers. Daher wird in diesem Artikel ein „Anforderungsplaner“ zur Steuerung der Parallelität vorgestellt.

TLDR; Springen Sie direkt zum Abschnitt „Abstraktion und Wiederverwendung“.

Um einen Stapel unabhängiger Ressourcen zu erhalten, kann Promise.all(arrayOfPromises) aus Leistungsgründen normalerweise zur gleichzeitigen Ausführung verwendet werden. Wenn wir beispielsweise bereits 100 Anwendungs-IDs haben und die PVs aller Anwendungen aggregieren müssen, schreiben wir normalerweise:

const ids = [1001, 1002, 1003, 1004, 1005];
const urlPrefix = "http://opensearch.example.com/api/apps";

// Die Fetch-Funktion sendet eine HTTP-Anfrage und gibt ein Promise zurück
const appPromises = ids.map(id => `${urlPrefix}/${id}`).map(fetch);

Versprechen.alle(App-Versprechen)
 // Akkumulieren durch reduce.then(apps => apps.reduce((initial, current) => initial + current.pv, 0))
 .catch((Fehler) => console.log(Fehler));

Der obige Code kann normal ausgeführt werden, wenn nicht viele Anwendungen vorhanden sind. Wenn die Anzahl der Anwendungen Zehntausende erreicht, führt Ihr „Stresstest“ bei Systemen, die gleichzeitige Anforderungen nicht sehr gut unterstützen, dazu, dass der Server des Drittanbieters abstürzt und vorübergehend nicht in der Lage ist, auf Anforderungen zu reagieren:

<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="weiß">
<center><h1>502 Ungültiges Gateway</h1></center>
<hr><center>nginx/1.10.1</center>
</body>
</html>

Wie kann man das Problem lösen?

Eine naheliegende Idee ist, dass, da so viele gleichzeitige Anfragen nicht unterstützt werden, es in mehrere große Blöcke aufgeteilt werden kann, von denen jeder ein chunk ist. Anfragen innerhalb chunk sind immer noch gleichzeitig, aber die Chunkgröße ( chunkSize ) ist auf die maximale Anzahl gleichzeitiger Anfragen begrenzt, die vom System unterstützt werden. Die Ausführung des nächsten chunk kann erst fortgesetzt werden, wenn der vorherige chunk fertig ist. Dies bedeutet, dass die Anforderungen innerhalb chunk gleichzeitig erfolgen, die Anforderungen zwischen chunk jedoch seriell. Die Idee ist eigentlich ganz einfach, aber schwer aufzuschreiben. Zusammenfassend gibt es drei Operationen: Block, Seriell und Aggregation

Die Schwierigkeit liegt in der seriellen Ausführung von Promise. Promise bietet nur parallele ( Promise.all ) Funktionen und keine seriellen Funktionen. Wir beginnen mit drei einfachen Anfragen und sehen, wie diese implementiert und das Problem heuristisch gelöst werden kann.

// task1, task2, task3 sind drei Factory-Funktionen, die Promise zurückgeben und unsere asynchrone Anfrage simulieren const task1 = () => new Promise((resolve) => {
 setzeTimeout(() => {
 Entschlossenheit (1);
 console.log('task1 ausgeführt');
 }, 1000);
});

const task2 = () => neues Versprechen((auflösen) => {
 setzeTimeout(() => {
 Entschlossenheit (2);
 console.log('Task2 ausgeführt');
 }, 1000);
});

const task3 = () => neues Versprechen((auflösen) => {
 setzeTimeout(() => {
 Entschlossenheit (3);
 console.log('Task3 ausgeführt');
 }, 1000);
});

// Aggregationsergebnisse let result = 0;

const resultPromise = [Aufgabe1, Aufgabe2, Aufgabe3].reduce((aktuell, nächste) => 	 
 aktuell.dann((Zahl) => {
 console.log('aufgelöst mit Nummer', Nummer); // Das Versprechen von Task2, Task3 wird hier aufgelöst
 Ergebnis += Zahl;

 nächstes zurückgeben();
 }),
 
 Promise.resolve(0)) // Anfangswert aggregieren.then(function(last) {
 console.log('Das letzte mit Nummer eingelöste Versprechen', last); // Das Versprechen von task3 wird hier eingelöst

 Ergebnis += letztes;

 console.log('alle mit Ergebnis ausgeführt', Ergebnis);

 returniere Promise.resolve(Ergebnis);
 });

Das laufende Ergebnis ist in Abbildung 1 dargestellt:

Codeanalyse: Der gewünschte Effekt ist eigentlich fn1().then(() => fn2()).then(() => fn3()) . Der Schlüssel zum obigen Code, der die sequenzielle Ausführung einer Gruppe von Promise ermöglicht, besteht darin, dass die reduce „Engine“ die Ausführung Promise -Factory-Funktion Schritt für Schritt steuert.

Das Problem wurde gelöst. Schauen wir uns den endgültigen Code an:

/**
 * HTTP-Anfrage simulieren * @param {String} url 
 * @return {Versprechen}
 */
Funktion fetch(url) {
 console.log(`${url} abrufen`);
 returniere neues Promise((auflösen) => {
 setTimeout(() => auflösen({ pv: Nummer(url.match(/\d+$/)) }), 2000);
 });
}

const urlPrefix = "http://opensearch.example.com/api/apps";

konstanter Aggregator = {
 /**
 * Eingabemethode, starten Sie die geplante Aufgabe * 
 * @return {Versprechen}
 */
 Start() {
 gib this.fetchAppIds() zurück
 .then(ids => diese.fetchAppsSerially(ids, 2))
 .then(apps => diese.sumPv(apps))
 .catch(Fehler => Konsole.Fehler(Fehler));
 },
 
 /**
 * Alle Anwendungs-IDs abrufen
 *
 * @Privat
 * 
 * @return {Versprechen}
 */
 fetchAppIds() {
 gibt Promise.resolve([1001, 1002, 1003, 1004, 1005]) zurück;
 },

 VersprechenFactory(IDs) {
 return () => Promise.all(ids.map(id => `${urlPrefix}/${id}`).map(fetch));
 },
 
 /**
 * Details zu allen Apps abrufen * 
 * Eine gleichzeitige Anforderung von `gleichzeitigen` Anwendungen wird als Chunk bezeichnet.
 * Der nächste Block wird fortgesetzt, nachdem der vorherige Block gleichzeitig abgeschlossen wurde, bis alle Anwendungen ihn erhalten haben*
 * @Privat
 *
 * @param {[Anzahl]} IDs
 * @param {Number} concurrency Die Anzahl gleichzeitiger Anfragen zu einem Zeitpunkt* @return {[Object]} Informationen zu allen Anwendungen*/
 fetchAppsSerially(ids, Parallelität = 100) {
 // Chunking let chunkOfIds = ids.splice(0, Parallelität);
 const Aufgaben = [];
 
 während (chunkOfIds.length !== 0) {
 Aufgaben.push(diese.promiseFactory(chunkOfIds));
 chunkOfIds = ids.splice(0, Parallelität);
 }
 
 // In Blockreihenfolge ausführen const result = [];
 returniere Aufgaben.Reduce((aktuell, weiter) => aktuell.dann((chunkOfApps) => {
 console.info('Block von', chunkOfApps.length, 'Parallelitätsanforderungen wurden mit folgendem Ergebnis abgeschlossen:', chunkOfApps, '\n\n');
 result.push(...chunkOfApps); // Array reduzieren return next();
 }), Versprechen.lösen([]))
 .then((lastchunkOfApps) => {
 console.info('Chunk of', lastchunkOfApps.length, 'Parallelitätsanforderungen wurden mit folgendem Ergebnis abgeschlossen:', lastchunkOfApps, '\n\n');

 result.push(...lastchunkOfApps); // Nochmals abflachen console.info('Alle Chunks wurden mit Ergebnis ausgeführt', result);
 Ergebnis zurückgeben;
 });
 },
 
 /**
 * Gesamt-PV aller Anwendungen
 * 
 * @Privat
 * 
 * @param {[]} Apps 
 * @return {[Typ]} [Beschreibung]
 */
 SummePv(Apps) {
 const initial = { pv: 0 };

 returniere apps.reduce((Akkumulator, App) => ({ pv: Akkumulator.pv + App.pv }), initial);
 }
};

// Starten Sie die Ausführung von aggregator.start().then(console.log);

Das laufende Ergebnis ist in Abbildung 2 dargestellt:

Abstraktion und Wiederverwendung

Das Ziel wurde erreicht. Da es universell ist, werden wir beginnen, es in ein Muster zur Wiederverwendung zu abstrahieren.

Seriell

Simulieren Sie zuerst eine HTTP-Get-Anfrage.

/**
 * verspottetes http get.
 * @param {string} URL
 * @returns {{ url: Zeichenfolge; Verzögerung: Zahl; }}
 */
Funktion httpGet(URL) {
 const Verzögerung = Math.random() * 1000;

 Konsole.info('GET', URL);

 returniere neues Promise((auflösen) => {
 setzeTimeout(() => {
 lösen({
 URL (URL = URL = URL),
 Verzögerung,
 um: Date.now()
 })
 }, Verzögerung);
 })
}

Führen Sie einen Batch von Anfragen seriell aus.

const ids = [1, 2, 3, 4, 5, 6, 7];

// Batch-Anforderungsfunktion. Beachten Sie, dass die durch Verzögerung ausgeführte „Funktion“ korrekt ist. Andernfalls wird die Anforderung sofort gesendet und der serielle Zweck wird nicht erreicht. const httpGetters = ids.map (id => 
 () => httpGet(`https://jsonplaceholder.typicode.com/posts/${id}`)
);

// Serielle Ausführung const tasks = await httpGetters.reduce((acc, cur) => {
 gibt acc.then(cur) zurück;
 
 // Kurzform, entspricht // return acc.then(() => cur());
}, Versprechen.auflösen());

Aufgaben.dann(() => {
 console.log('fertig');
});

Achten Sie auf die Konsolenausgabe, folgendes sollte seriell ausgegeben werden:

Holen Sie sich https://jsonplaceholder.typicode.com/posts/1
Holen Sie sich https://jsonplaceholder.typicode.com/posts/2
Holen Sie sich https://jsonplaceholder.typicode.com/posts/3
Holen Sie sich https://jsonplaceholder.typicode.com/posts/4
Holen Sie sich https://jsonplaceholder.typicode.com/posts/5
Holen Sie sich https://jsonplaceholder.typicode.com/posts/6
Holen Sie sich https://jsonplaceholder.typicode.com/posts/7

Segment seriell, Segment parallel

Hier kommt der Punkt. Die Implementierung des Anforderungsplaners in diesem Artikel

/**
 * Planen Sie Versprechen.
 * @param {Array<(...arg: any[]) => Promise<any>>} Fabriken 
 * @param {number} Parallelität 
 */
Funktion schedulePromises(Fabriken, Parallelität) {
 /**
 * Stück
 * @param {any[]} arr 
 * @param {number} Größe 
 * @returns {Array<any[]>}
 */
 const chunk = (arr, Größe = 1) => {
 returniere arr.reduce((acc, cur, idx) => {
 const modulo = idx % Größe;

 wenn (modulo === 0) {
 acc[acc.Länge] = [aktuell];
 } anders {
 acc[acc.Länge - 1].push(aktuell);
 }

 Rückgabe gem.
 }, [])
 };

 const chunks = chunk(Fabriken, Parallelität);

 lass resps = [];

 Rückgabewerte für chunks.reduce(
 (account, aktuell) => {
 Rückgabe-Ac
 .then(() => {
  konsole.log('---');
  Fügt Promise.all zurück (aktuell.map(f => f()));
 })
 .then((Zwischenantworten) => {
  resps.push(...Zwischenantworten);

  Rückgabeantworten;
 })
 },

 Versprechen.lösen()
 );
}

Führen Sie zum Testen den Scheduler aus:

// Segmentiert seriell, segmentiert parallel schedulePromises(httpGetters, 3).then((resps) => {
 Konsole.log('resps:', resps);
});

Konsolenausgabe:

---
Holen Sie sich https://jsonplaceholder.typicode.com/posts/1
Holen Sie sich https://jsonplaceholder.typicode.com/posts/2
Holen Sie sich https://jsonplaceholder.typicode.com/posts/3
---
Holen Sie sich https://jsonplaceholder.typicode.com/posts/4
Holen Sie sich https://jsonplaceholder.typicode.com/posts/5
Holen Sie sich https://jsonplaceholder.typicode.com/posts/6
---
Holen Sie sich https://jsonplaceholder.typicode.com/posts/7

resps: [
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/1",
 "Verzögerung": 733.010980640727,
 "um": 1615131322163
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/2",
 "Verzögerung": 594.5056229848931,
 "um": 1615131322024
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/3",
 "Verzögerung": 738.8230109146299,
 "um": 1615131322168
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/4",
 "Verzögerung": 525.4604386109747,
 "um": 1615131322698
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/5",
 "Verzögerung": 29.086379722201183,
 "um": 1615131322201
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/6",
 "Verzögerung": 592.2345027398272,
 "um": 1615131322765
 },
 {
 "URL": "https://jsonplaceholder.typicode.com/posts/7",
 "Verzögerung": 513.0684467560949,
 "um": 1615131323284
 }
]

Zusammenfassen

  1. Wenn die Anzahl gleichzeitiger Anfragen zu groß ist, können Sie sie in Blöcke aufteilen und seriell verarbeiten und die Anfragen dann innerhalb der Blöcke gleichzeitig verarbeiten.
  2. Das Problem mag kompliziert erscheinen, aber wir können es zunächst vereinfachen, dann die wesentlichen Punkte Schritt für Schritt ableiten und es schließlich abstrahieren, um eine Lösung zu finden.
  3. Der Kernpunkt dieses Artikels besteht darin, reduce als seriellen Antriebsmotor zu verwenden, sodass die Beherrschung dieser Methode neue Ideen zur Lösung der Rätsel liefern kann, denen wir bei der täglichen Entwicklung begegnen. Informationen zur Beherrschung reduce finden Sie im vorherigen Artikel „So verwenden Sie Reduce endlich 🎉“.

Oben sind die Details der JS-Implementierung des Anforderungsplaners aufgeführt. Weitere Informationen zum JS-Anforderungsplaner finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM!

Das könnte Sie auch interessieren:
  • js implementiert Axios Limit-Anforderungswarteschlange
  • So verwenden Sie Promise in JavaScript, um die Anzahl gleichzeitiger Anfragen zu steuern
  • Beispielcode für die Verwendung von js zur Implementierung gleichzeitiger Ajax-Anfragen, um die Anzahl der Anfragen zu begrenzen
  • gin JSON-Text der Post-Anforderung abrufen
  • PHP implementiert die Konvertierung von Chrome-Formularanforderungsdaten in JSON-Daten, die von der Schnittstelle verwendet werden
  • Detaillierte Erläuterung mehrerer Lösungen für JavaScript-Unterbrechungsanforderungen

<<:  Installations- und Bereitstellungstutorial der neuesten MySQL-Version 5.7.17 (64-Bit-ZIP-Green-Version) unter Win 8 oder höher

>>:  So aktivieren oder deaktivieren Sie Linux-Dienste mit den Befehlen chkconfig und systemctl

Artikel empfehlen

Der Unterschied zwischen ENTRYPOINT und CMD in Dockerfile

Im Lernprogramm zum Docker-System haben wir geler...

Wir zeigen Ihnen einen Trick, um einen Textvergleich unter Linux durchzuführen

Vorwort Während des Schreibens des Codes werden w...

Detaillierte Erklärung der HTML-Formularelemente (Teil 1)

HTML-Formulare werden verwendet, um verschiedene ...

SQL-Implementierung LeetCode (176. Zweithöchstes Gehalt)

[LeetCode] 176. Zweithöchstes Gehalt Schreiben Si...

Centos erstellt ein Prozessdiagramm für den Chrony-Zeitsynchronisationsserver

Meine Umgebung: 3 centos7.5 1804 Meister 192.168....

Ein paar Dinge, die Sie über responsives Layout wissen müssen

1. Einleitung Responsive Webdesign ermöglicht die...

Django-Online-Bereitstellungsmethode von Apache

Umfeld: 1. Windows Server 2016 Datacenter 64-Bit ...