A abordagem comum, como Ozz já mencionou , é uma fila de mensagens . De uma perspectiva de design, uma fila de mensagens é essencialmente uma fila FIFO , que é um tipo de dados bastante fundamental:
O que torna uma fila de mensagens especial é que, embora seu aplicativo seja responsável pelo enfileiramento, um processo diferente seria responsável pelo desenfileiramento. No jargão da fila, seu aplicativo é o remetente da (s) mensagem (s) e o processo de remoção da fila é o destinatário. A vantagem óbvia é que todo o processo é assíncrono, o destinatário trabalha independentemente do remetente, desde que haja mensagens a serem processadas. A desvantagem óbvia é que você precisa de um componente extra, o remetente, para que tudo funcione.
Como sua arquitetura agora conta com dois componentes que trocam mensagens, você pode usar o termo sofisticado comunicação entre processos para ela.
Como a introdução de uma fila afeta o design do seu aplicativo?
Certas ações em seu aplicativo geram emails. A introdução de uma fila de mensagens significaria que essas ações agora devem enviar mensagens para a fila (e nada mais). Essas mensagens devem conter a quantidade mínima absoluta de informações necessárias para criar os emails quando o destinatário os processar.
Formato e conteúdo das mensagens
O formato e o conteúdo de suas mensagens dependem totalmente de você, mas lembre-se de que quanto menor, melhor. Sua fila deve ser o mais rápida possível para gravar e processar, lançar uma grande quantidade de dados provavelmente criará um gargalo.
Além disso, vários serviços de enfileiramento na nuvem têm restrições no tamanho das mensagens e podem dividir mensagens maiores. Você não notará que as mensagens divididas serão exibidas quando solicitadas, mas você será cobrado por várias mensagens (supondo que você esteja usando um serviço que exige uma taxa).
Projeto do receptor
Como estamos falando de um aplicativo Web, uma abordagem comum para o seu receptor seria um simples script cron. Seria executado a cada x
minuto (ou segundos) e:
- Pop
n
quantidade de mensagens da fila,
- Processe as mensagens (por exemplo, envie os e-mails).
Observe que estou dizendo pop em vez de obter ou buscar, porque o receptor não está apenas recebendo os itens da fila, mas também os limpando (por exemplo, removendo-os da fila ou marcando-os como processados). Como exatamente isso vai acontecer depende da sua implementação da fila de mensagens e das necessidades específicas do seu aplicativo.
É claro que o que estou descrevendo é essencialmente uma operação em lote , a maneira mais simples de processar uma fila. Dependendo das suas necessidades, convém processar as mensagens de uma maneira mais complicada (isso também exigiria uma fila mais complicada).
Tráfego
Seu receptor pode levar em consideração o tráfego e ajustar o número de mensagens processadas com base no tráfego no momento em que é executado. Uma abordagem simplista seria prever o horário de alto tráfego com base em dados de tráfego anteriores e supondo que você seguisse um script cron que é executado a cada x
minuto; você poderia fazer algo assim:
if(
now() > 2pm && now() < 7pm
) {
process(10);
} else {
process(100);
}
function process(count) {
for(i=0; i<=count; i++) {
message = dequeue();
mail(message)
}
}
Uma abordagem muito ingênua e suja, mas funciona. Se isso não acontecer, bem, a outra abordagem seria descobrir o tráfego atual do seu servidor a cada iteração e ajustar o número de itens do processo de acordo. Por favor, não micro-otimize, se não for absolutamente necessário, você estaria perdendo seu tempo.
Armazenamento na fila
Se seu aplicativo já usa um banco de dados, uma única tabela seria a solução mais simples:
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)
)
Realmente não é mais complicado que isso. É claro que você pode torná-lo o mais complicado possível, por exemplo, adicionar um campo prioritário (o que significa que isso não é mais uma fila FIFO, mas se você realmente precisar, quem se importa?). Você também pode simplificar, ignorando o campo processado (mas precisará excluir as linhas depois de processá-las).
Uma tabela de banco de dados seria ideal para 2000 mensagens por dia, mas provavelmente não seria adequada para milhões de mensagens por dia. Há um milhão de fatores a serem considerados, tudo na sua infraestrutura desempenha um papel na escalabilidade geral do seu aplicativo.
De qualquer forma, supondo que você já tenha identificado a fila baseada em banco de dados como um gargalo, a próxima etapa seria procurar um serviço baseado em nuvem. O Amazon SQS é o único serviço que usei e fiz o que promete. Tenho certeza de que existem alguns serviços semelhantes por aí.
Filas baseadas em memória também são algo a considerar, especialmente para filas de curta duração. O memcached é excelente como armazenamento na fila de mensagens.
Qualquer que seja o armazenamento em que você decida montar sua fila, seja inteligente e abstraia. Nem o remetente nem o destinatário devem estar vinculados a um armazenamento específico; caso contrário, mudar para um armazenamento diferente posteriormente seria uma PITA completa.
Abordagem da vida real
Criei uma fila de mensagens para e-mails muito parecidos com o que você está fazendo. Ele estava em um projeto PHP e eu o construí em torno do Zend Queue , um componente do Zend Framework que oferece vários adaptadores para diferentes armazenamentos. Meus armazéns onde:
- Matrizes PHP para teste de unidade,
- Amazon SQS na produção,
- MySQL nos ambientes de desenvolvimento e teste.
Minhas mensagens eram o mais simples possível, meu aplicativo criou pequenas matrizes com as informações essenciais ( [user_id, reason]
). O armazenamento de mensagens era uma versão serializada dessa matriz (primeiro, era o formato interno de serialização do PHP, depois o JSON, não me lembro por que mudei). A reason
é uma constante e é claro que eu tenho um grande em algum lugar da tabela que mapeia reason
a explicações mais completas (Eu consegui enviar cerca de 500 e-mails para os clientes com a enigmática reason
em vez da mensagem mais completa uma vez).
Leitura adicional
Padrões:
Ferramentas:
Leituras interessantes: