Der übliche Ansatz ist, wie Ozz bereits erwähnte , eine Nachrichtenwarteschlange . Aus Entwurfssicht ist eine Nachrichtenwarteschlange im Wesentlichen eine FIFO-Warteschlange , bei der es sich um einen ziemlich grundlegenden Datentyp handelt:
Das Besondere an einer Nachrichtenwarteschlange ist, dass Ihre Anwendung zwar für das Einreihen in die Warteschlange verantwortlich ist, ein anderer Prozess jedoch für das Entfernen der Warteschlange verantwortlich ist. In der Warteschlangensprache ist Ihre Anwendung der Absender der Nachricht (en) und der Prozess der Warteschlangenentfernung der Empfänger. Der offensichtliche Vorteil ist, dass der gesamte Prozess asynchron ist und der Empfänger unabhängig vom Absender arbeitet, solange Nachrichten verarbeitet werden müssen. Der offensichtliche Nachteil ist, dass Sie eine zusätzliche Komponente, den Absender, benötigen, damit das Ganze funktioniert.
Da Ihre Architektur jetzt auf zwei Komponenten basiert, die Nachrichten austauschen, können Sie den ausgefallenen Begriff Interprozesskommunikation verwenden .
Wie wirkt sich das Einführen einer Warteschlange auf das Design Ihrer Anwendung aus?
Bestimmte Aktionen in Ihrer Anwendung generieren E-Mails. Das Einführen einer Nachrichtenwarteschlange würde bedeuten, dass diese Aktionen jetzt stattdessen Nachrichten in die Warteschlange verschieben sollten (und nicht mehr). Diese Nachrichten sollten die absolut minimale Menge an Informationen enthalten, die zum Erstellen der E-Mails erforderlich sind, wenn Ihr Empfänger sie verarbeiten kann.
Format und Inhalt der Nachrichten
Das Format und der Inhalt Ihrer Nachrichten liegen ganz bei Ihnen, aber Sie sollten bedenken, je kleiner desto besser. Ihre Warteschlange sollte so schnell wie möglich beschrieben und verarbeitet werden können. Wenn Sie einen Großteil der Daten darauf werfen, entsteht wahrscheinlich ein Engpass.
Darüber hinaus unterliegen mehrere Cloud-basierte Warteschlangendienste Einschränkungen hinsichtlich der Nachrichtengröße und können größere Nachrichten aufteilen. Sie werden es nicht bemerken, die geteilten Nachrichten werden als eine Nachricht zugestellt, wenn Sie danach fragen, aber Ihnen werden mehrere Nachrichten in Rechnung gestellt (vorausgesetzt natürlich, Sie verwenden einen Dienst, für den eine Gebühr erforderlich ist).
Design des Empfängers
Da es sich um eine Webanwendung handelt, ist ein einfacher Ansatz für Ihren Empfänger ein einfaches Cron-Skript. Es würde alle x
Minuten (oder Sekunden) laufen und es würde:
- Pop-
n
Anzahl von Nachrichten aus der Warteschlange,
- Verarbeiten Sie die Nachrichten (dh senden Sie die E-Mails).
Beachten Sie, dass ich Pop anstelle von Get oder Fetch sage. Dies liegt daran, dass Ihr Empfänger die Elemente nicht nur aus der Warteschlange abruft, sondern auch löscht (dh aus der Warteschlange entfernt oder als verarbeitet markiert). Wie genau dies geschehen wird, hängt von Ihrer Implementierung der Nachrichtenwarteschlange und den spezifischen Anforderungen Ihrer Anwendung ab.
Natürlich beschreibe ich im Wesentlichen eine Stapeloperation , die einfachste Art, eine Warteschlange zu verarbeiten. Abhängig von Ihren Anforderungen möchten Sie Nachrichten möglicherweise komplizierter verarbeiten (dies würde auch eine kompliziertere Warteschlange erfordern).
Der Verkehr
Ihr Empfänger kann den Datenverkehr berücksichtigen und die Anzahl der von ihm verarbeiteten Nachrichten basierend auf dem Datenverkehr zum Zeitpunkt der Ausführung anpassen. Ein vereinfachter Ansatz wäre, Ihre hohen Verkehrsstunden basierend auf früheren Verkehrsdaten vorherzusagen und davon auszugehen, dass Sie ein Cron-Skript verwendet haben, das jede x
Minute ausgeführt wird. Sie könnten so etwas tun:
if(
now() > 2pm && now() < 7pm
) {
process(10);
} else {
process(100);
}
function process(count) {
for(i=0; i<=count; i++) {
message = dequeue();
mail(message)
}
}
Ein sehr naiver und schmutziger Ansatz, aber er funktioniert. Wenn dies nicht der Fall ist, besteht der andere Ansatz darin, den aktuellen Datenverkehr Ihres Servers bei jeder Iteration zu ermitteln und die Anzahl der Prozesselemente entsprechend anzupassen. Bitte nicht mikrooptimieren, wenn dies nicht unbedingt erforderlich ist. Sie würden Ihre Zeit verschwenden.
Warteschlangenspeicher
Wenn Ihre Anwendung bereits eine Datenbank verwendet, ist eine einzelne Tabelle die einfachste Lösung:
CREATE TABLE message_queue (
id int(11) NOT NULL AUTO_INCREMENT,
timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed enum('0','1') NOT NULL DEFAULT '0',
message varchar(255) NOT NULL,
PRIMARY KEY (id),
KEY timestamp (timestamp),
KEY processed (processed)
)
Es ist wirklich nicht komplizierter. Sie können es natürlich so kompliziert machen, wie Sie es benötigen. Sie können beispielsweise ein Prioritätsfeld hinzufügen (was bedeuten würde, dass dies keine FIFO-Warteschlange mehr ist, aber wenn Sie es tatsächlich benötigen, wen interessiert das?). Sie können es auch einfacher machen, indem Sie das verarbeitete Feld überspringen (aber dann müssten Sie Zeilen löschen, nachdem Sie sie verarbeitet haben).
Eine Datenbanktabelle wäre ideal für 2000 Nachrichten pro Tag, würde sich jedoch wahrscheinlich nicht gut für Millionen von Nachrichten pro Tag skalieren lassen. Es sind eine Million Faktoren zu berücksichtigen. Alles in Ihrer Infrastruktur spielt eine Rolle für die Gesamtskalierbarkeit Ihrer Anwendung.
Unter der Annahme, dass Sie die datenbankbasierte Warteschlange bereits als Engpass identifiziert haben, besteht der nächste Schritt in jedem Fall darin, einen Cloud-basierten Dienst zu betrachten. Amazon SQS ist der einzige Dienst, den ich verwendet habe und der das getan hat, was er verspricht. Ich bin mir sicher, dass es da draußen einige ähnliche Dienste gibt.
Speicherbasierte Warteschlangen sind ebenfalls zu berücksichtigen, insbesondere bei kurzlebigen Warteschlangen. memcached eignet sich hervorragend als Speicher für Nachrichtenwarteschlangen.
Unabhängig davon, auf welchem Speicher Sie Ihre Warteschlange aufbauen möchten, sollten Sie intelligent und abstrakt sein. Weder Ihr Absender noch Ihr Empfänger sollten an einen bestimmten Speicher gebunden sein, da sonst ein späterer Wechsel zu einem anderen Speicher eine vollständige PITA wäre.
Realer Ansatz
Ich habe eine Nachrichtenwarteschlange für E-Mails erstellt, die Ihrer Arbeit sehr ähnlich ist. Es war ein PHP-Projekt und ich habe es um Zend Queue herum erstellt , eine Komponente des Zend Frameworks, die mehrere Adapter für verschiedene Speicher bietet . Meine Speicher wo:
- PHP-Arrays für Unit-Tests,
- Amazon SQS in Produktion,
- MySQL in der Entwicklungs- und Testumgebung.
Meine Nachrichten waren so einfach wie möglich. Meine Anwendung erstellte kleine Arrays mit den wesentlichen Informationen ( [user_id, reason]
). Der Nachrichtenspeicher war eine serialisierte Version dieses Arrays (zuerst war es das interne Serialisierungsformat von PHP, dann JSON, ich erinnere mich nicht, warum ich gewechselt habe). Das reason
ist eine Konstante und natürlich habe ich irgendwo eine große Tabelle, die reason
ausführlichere Erklärungen enthält (ich habe es geschafft, etwa 500 E-Mails an Kunden mit der kryptischen reason
anstelle der vollständigeren Nachricht einmal zu senden ).
Weiterführende Literatur
Standards:
Werkzeuge:
Interessante liest: