L'approche commune, comme Ozz l'a déjà mentionné , est une file d'attente de messages . Du point de vue de la conception, une file d'attente de messages est essentiellement une file d'attente FIFO , qui est un type de données plutôt fondamental:
Ce qui rend une file d'attente de messages spéciale, c'est que même si votre application est responsable de la mise en file d'attente, un processus différent serait responsable de la mise en file d'attente. Dans le jargon de mise en file d'attente, votre application est l'expéditeur du ou des messages et le processus de retrait de la file d'attente est le destinataire. L'avantage évident est que l'ensemble du processus est asynchrone, le récepteur fonctionne indépendamment de l'expéditeur, tant qu'il y a des messages à traiter. L'inconvénient évident est que vous avez besoin d'un composant supplémentaire, l'expéditeur, pour que le tout fonctionne.
Étant donné que votre architecture repose désormais sur deux composants échangeant des messages, vous pouvez utiliser le terme de communication inter-processus de fantaisie pour cela.
Comment l'introduction d'une file d'attente affecte-t-elle la conception de votre application?
Certaines actions de votre application génèrent des e-mails. L'introduction d'une file d'attente de messages signifierait que ces actions devraient désormais pousser les messages vers la file d'attente (et rien de plus). Ces messages doivent contenir le minimum absolu d'informations nécessaires à la construction des e-mails lorsque votre destinataire les traite.
Format et contenu des messages
Le format et le contenu de vos messages dépendent entièrement de vous, mais vous devez garder à l'esprit le plus petit sera le mieux. Votre file d'attente doit être aussi rapide à écrire et à traiter que possible, y jeter une masse de données créera probablement un goulot d'étranglement.
En outre, plusieurs services de mise en file d'attente basés sur le cloud ont des restrictions sur la taille des messages et peuvent diviser des messages plus volumineux. Vous ne le remarquerez pas, les messages fractionnés seront servis comme un seul lorsque vous les demanderez, mais vous serez facturé pour plusieurs messages (en supposant bien sûr que vous utilisez un service payant).
Conception du récepteur
Comme nous parlons d'une application Web, une approche courante pour votre récepteur serait un simple script cron. Il fonctionnerait toutes les x
minutes (ou secondes) et il:
- Pop
n
quantité de messages de la file d'attente,
- Traitez les messages (c'est-à-dire envoyez les e-mails).
Notez que je dis pop au lieu d'obtenir ou de récupérer, c'est parce que votre récepteur ne récupère pas seulement les éléments de la file d'attente, il les efface également (c'est-à-dire les supprimer de la file d'attente ou les marquer comme traités). La manière exacte dont cela se produira dépend de votre implémentation de la file d'attente de messages et des besoins spécifiques de votre application.
Bien sûr, ce que je décris est essentiellement une opération par lots , le moyen le plus simple de traiter une file d'attente. Selon vos besoins, vous souhaiterez peut-être traiter les messages de manière plus compliquée (cela nécessiterait également une file d'attente plus compliquée).
Circulation
Votre récepteur peut prendre en compte le trafic et ajuster le nombre de messages qu'il traite en fonction du trafic au moment de son exécution. Une approche simpliste serait de prédire vos heures de trafic élevé en fonction des données de trafic passées et en supposant que vous êtes allé avec un script cron qui s'exécute toutes les x
minutes, vous pouvez faire quelque chose comme ceci:
if(
now() > 2pm && now() < 7pm
) {
process(10);
} else {
process(100);
}
function process(count) {
for(i=0; i<=count; i++) {
message = dequeue();
mail(message)
}
}
Une approche très naïve et sale, mais ça marche. Si ce n'est pas le cas, l'autre approche serait de découvrir le trafic actuel de votre serveur à chaque itération et d'ajuster le nombre d'éléments de processus en conséquence. Veuillez ne pas micro-optimiser si ce n'est pas absolument nécessaire, vous perdriez votre temps.
Stockage de file d'attente
Si votre application utilise déjà une base de données, alors une seule table dessus serait la solution la plus simple:
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)
)
Ce n'est vraiment pas plus compliqué que ça. Vous pouvez bien sûr le rendre aussi compliqué que vous le souhaitez, vous pouvez, par exemple, ajouter un champ prioritaire (ce qui signifierait qu'il ne s'agit plus d'une file d'attente FIFO, mais si vous en avez réellement besoin, peu importe?). Vous pouvez également le rendre plus simple, en sautant le champ traité (mais vous devrez alors supprimer les lignes après les avoir traitées).
Une table de base de données serait idéale pour 2000 messages par jour, mais elle ne serait probablement pas adaptée à des millions de messages par jour. Il y a un million de facteurs à considérer, tout dans votre infrastructure joue un rôle dans l'évolutivité globale de votre application.
Dans tous les cas, en supposant que vous avez déjà identifié la file d'attente basée sur la base de données comme un goulot d'étranglement, l'étape suivante serait d'examiner un service basé sur le cloud. Amazon SQS est le seul service que j'ai utilisé et a fait ce qu'il promet. Je suis sûr qu'il existe de nombreux services similaires.
Les files d'attente basées sur la mémoire sont également quelque chose à considérer, en particulier pour les files d'attente de courte durée. memcached est excellent comme stockage de file d'attente de messages.
Quel que soit le stockage sur lequel vous décidez de construire votre file d'attente, soyez intelligent et abstrait. Ni votre expéditeur ni votre récepteur ne doivent être liés à un stockage spécifique, sinon le basculement vers un autre stockage à une date ultérieure serait un PITA complet.
Approche de la vie réelle
J'ai créé une file d'attente de messages pour les e-mails très similaire à ce que vous faites. C'était sur un projet PHP et je l'ai construit autour de Zend Queue , un composant du Framework Zend qui propose plusieurs adaptateurs pour différents stockages. Mes stockages où:
- Tableaux PHP pour les tests unitaires,
- Amazon SQS en production,
- MySQL sur les environnements de développement et de test.
Mes messages étaient aussi simples que possible, mon application a créé de petits tableaux avec les informations essentielles ( [user_id, reason]
). Le magasin de messages était une version sérialisée de ce tableau (d'abord c'était le format de sérialisation interne de PHP, puis JSON, je ne me souviens pas pourquoi j'ai changé). C'est reason
une constante et bien sûr, j'ai un grand tableau quelque part qui correspond reason
à des explications plus complètes (j'ai réussi à envoyer environ 500 e-mails aux clients avec le cryptique reason
au lieu du message plus complet une fois).
Lectures complémentaires
Normes:
Outils:
Lectures intéressantes: