So verwalten Sie automatisierte E-Mails, die von einer Webanwendung gesendet werden

11

Ich entwerfe eine Webanwendung und frage mich, wie ich die Architektur für das Versenden automatisierter E-Mails entwerfen soll.

Derzeit ist diese Funktion in meine Web-App integriert, und die E-Mails werden basierend auf Benutzereingaben / Interaktionen gesendet (z. B. beim Erstellen eines neuen Benutzers). Das Problem ist, dass die direkte Verbindung zu einem Mailserver einige Sekunden dauert. Wenn ich meine Bewerbung vergrößere, wird dies in Zukunft ein bedeutender Flaschenhals sein.

Was ist der beste Weg, um das Versenden einer großen Anzahl automatisierter E-Mails innerhalb meiner Systemarchitektur zu verwalten?

Es werden nicht viele E-Mails gesendet (maximal 2000 pro Tag). E-Mails müssen nicht sofort gesendet werden, eine Verzögerung von bis zu 10 Minuten ist in Ordnung.

Update: Die Nachrichtenwarteschlange wurde als Antwort angegeben, aber wie würde dies gestaltet sein? Würde dies in der App erledigt und in einer ruhigen Zeit verarbeitet, oder muss ich eine neue "Mail-App" oder einen neuen Webdienst erstellen, um nur die Warteschlange zu verwalten?

Gaz_Edge
quelle
Können Sie uns einen groben Eindruck von Skalierbarkeit vermitteln? Hunderte, Tausende oder Millionen von Mails? Sollten die E-Mails auch sofort gesendet werden oder ist eine kleine Verzögerung akzeptabel?
Yannis
Beim Senden von E-Mails wird eine SMTP-Nachricht an einen empfangenden E-Mail-Host übergeben. Dies bedeutet jedoch nicht, dass die Nachricht tatsächlich zugestellt wurde. Das Senden von E-Mails ist also effektiv asynchron, und es macht keinen Sinn, so zu tun, als würde man auf den Erfolg warten.
Kilian Foth
1
Ich warte nicht auf Erfolg, aber ich muss warten, bis der SMTP-Server meine Anfrage akzeptiert. @YannisRizos siehe Update RE Ihren Kommentar
Gaz_Edge
Für 2000 (das ist Ihr beschriebenes Maximum) Mails funktioniert es einfach. Wenn sie in etwa 10 Geschäftsstunden stattfinden, sind es 3 Mails pro Minute, was sehr machbar ist. Stellen Sie einfach sicher, dass Sie Ihren DNS-Eintrag gut eingerichtet haben und der Anbieter akzeptiert, dass Sie ihn in diesen Beträgen senden. Denken Sie auch an: "Was ist der Mailserver ausgefallen?". Das Laden von 2000 Mails ist kein Grund zur Sorge.
Luc Franken
Die Antwort auf wo ist CRONTAB
Tulains Córdova

Antworten:

15

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:

FIFO-Warteschlange

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 xMinuten (oder Sekunden) laufen und es würde:

  • Pop- nAnzahl 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 xMinute 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 reasonist eine Konstante und natürlich habe ich irgendwo eine große Tabelle, die reasonausführlichere Erklärungen enthält (ich habe es geschafft, etwa 500 E-Mails an Kunden mit der kryptischen reasonanstelle der vollständigeren Nachricht einmal zu senden ).

Weiterführende Literatur

Standards:

Werkzeuge:

Interessante liest:

Yannis
quelle
Beeindruckend. Fast die beste Antwort, die ich je hier erhalten habe! Ich kann dir nicht genug danken!
Gaz_Edge
Ich und ich bin sicher, dass Millionen andere dieses FIFO mit Google Mail und Google Apps Script verwenden. Ein Google Mail-Filter kennzeichnet eingehende E-Mails anhand von Kriterien und stellt sie in die Warteschlange. Ein Google Apps-Skript wird jede X-Dauer ausgeführt, erhält die ersten y Nachrichten, sendet sie und stellt sie in die Warteschlange. Spülen & Wiederholen.
DavChana
6

Sie benötigen eine Art Warteschlangensystem.

Eine einfache Möglichkeit könnte darin bestehen, in eine Datenbanktabelle zu schreiben und andere externe Anwendungsprozesszeilen in dieser Tabelle zu haben. Sie können jedoch auch viele andere Warteschlangentechnologien verwenden.

E-Mails können wichtig sein, sodass bestimmte E-Mails fast sofort ausgeführt werden (z. B. Zurücksetzen des Kennworts), und E-Mails mit geringerer Bedeutung können für einen späteren Versand zusammengefasst werden.

ozz
quelle
Haben Sie ein Architekturdiagramm oder ein Beispiel, das zeigt, wie dies funktioniert? Befindet sich die Warteschlange beispielsweise in einer anderen "App", beispielsweise einer E-Mail-App, oder wird sie in einer ruhigen Zeit aus der Webanwendung heraus verarbeitet. Oder sollte ich eine Art Webdienst erstellen, um sie zu verarbeiten?
Gaz_Edge
1
@Gaz_Edge Ihre Anwendung verschiebt Elemente in die Warteschlange. Bei einem Hintergrundprozess (höchstwahrscheinlich einem Cron-Skript) werden alle n Sekunden x Elemente aus der Warteschlange entfernt und verarbeitet (in Ihrem Fall wird die E-Mail gesendet). Eine einzelne Datenbanktabelle eignet sich gut als Warteschlangenspeicher für kleine Mengen von Elementen. Im Allgemeinen sind Schreibvorgänge in einer Datenbank jedoch teuer, und für größere Mengen sollten Sie sich Dienste wie Amazon SQS ansehen .
Yannis
1
@Gaz_Edge Ich bin mir nicht sicher, ob ich es einfacher darstellen kann als das, was ich geschrieben habe: "... in eine Datenbanktabelle schreiben und andere externe Anwendungsprozesszeilen in dieser Tabelle haben ..." und für Tabelle "jede Warteschlange lesen" "Welche Technologie auch immer das sein mag.
Ozz
1
(Forts. ...) Sie können den Hintergrundprozess erstellen, der die Warteschlange so löscht, dass Ihr Datenverkehr berücksichtigt wird. Sie können ihn beispielsweise anweisen, in Zeiten, in denen Ihr Server unter Stress steht, weniger (oder gar keine) Elemente zu verarbeiten . Sie müssen diese stressigen Zeiten entweder vorhersagen, indem Sie sich Ihre vergangenen Verkehrsdaten ansehen (einfacher als es sich anhört, aber mit einer großen Fehlerquote) oder indem Sie Ihren Hintergrundprozess bei jeder Ausführung den Verkehrsstatus überprüfen lassen (genauer, aber der zusätzliche Aufwand ist selten notwendig).
Yannis
@YannisRizos möchten Ihre Kommentare zu einer Antwort kombinieren? Auch Architekturdiagramme und -entwürfe wären hilfreich (ich bin entschlossen, sie diesmal aus dieser Frage
herauszuholen
2

Es werden nicht viele E-Mails gesendet (maximal 2000 pro Tag).

Neben der Warteschlange sollten Sie auch E-Mails über spezialisierte Dienste senden: MailChimp zum Beispiel (ich bin nicht mit diesem Dienst verbunden). Andernfalls senden viele Mail-Dienste wie Google Mail Ihre Briefe bald in einen Spam-Ordner.

OZ_
quelle
2

Ich habe mein Warteschlangensystem in verschiedenen 2 Tabellen wie folgt modelliert:

CREATE TABLE [dbo].[wMessages](
  [Id] [uniqueidentifier]  NOT NULL,
  [FromAddress] [nvarchar](255) NOT NULL,
  [FromDisplayName] [nvarchar](255) NULL,
  [ToAddress] [nvarchar](255) NOT NULL,
  [ToDisplayName] [nvarchar](255) NULL,
  [Graph] [xml] NOT NULL,
  [Priority] [int] NOT NULL,
  PRIMARY KEY CLUSTERED ( [Id] ASC ))

CREATE TABLE [dbo].[wMessageStates](
  [MessageId] [uniqueidentifier] NOT NULL,
  [Status] [int] NOT NULL,
  [LastChange] [datetimeoffset](7) NOT NULL,
  [SendAfter] [datetimeoffset](7) NULL,
  [SendBefore] [datetimeoffset](7) NULL,
  [DeleteAfter] [datetimeoffset](7) NULL,
  [SendDate] [datetimeoffset](7) NULL,
  PRIMARY KEY CLUSTERED ( [MessageId] ASC )) ON [PRIMARY]
) ON [PRIMARY]

Es gibt 1-1 Beziehung zwischen diesen Tabellen.

Nachrichtentabelle zum Speichern des Nachrichteninhalts. Der tatsächliche Inhalt (An, CC, BCC, Betreff, Text usw.) wird im XML-Format in das Diagrammfeld serialisiert. Andere Von, Bis-Informationen werden nur zum Melden von Problemen verwendet, ohne das Diagramm zu deserialisieren. Durch das Trennen dieser Tabelle können Tabelleninhalte auf einen anderen Festplattenspeicher partitioniert werden. Sobald Sie bereit sind, eine Nachricht zu senden, müssen Sie alle Informationen lesen. Daher ist es nichts Falsches, den gesamten Inhalt in eine Spalte mit Primärschlüsselindex zu serialisieren.

MessageState- Tabelle zum Speichern des Status des Nachrichteninhalts mit zusätzlichen datumsbasierten Informationen. Das Trennen dieser Tabelle ermöglicht einen schnellen Zugriff auf den Mechanismus mit zusätzlichen Indizes für den schnellen E / A-Speicher. Andere Spalten sind bereits selbsterklärend.

Sie können einen separaten Thread-Pool verwenden, der diese Tabellen durchsucht. Wenn sich Anwendung und Pool auf demselben Computer befinden, können Sie eine EventWaitHandle- Klasse verwenden, um dem Pool von der Anwendung zu signalisieren, dass etwas in diese Tabellen eingefügt wurde. Andernfalls ist ein periodischer Scan mit einer Zeitüberschreitung am besten.

ertan
quelle