Estou projetando um aplicativo Web e estou pensando em como projetar a arquitetura para gerenciar o envio de emails automatizados.
Atualmente, tenho esse recurso integrado ao meu aplicativo Web e os emails são enviados com base nas interações / entradas do usuário (como a criação de um novo usuário). O problema é que a conexão direta a um servidor de email leva alguns segundos. Ampliando minha aplicação, este será um gargalo significativo no futuro.
Qual é a melhor maneira de gerenciar o envio de uma grande quantidade de emails automatizados na arquitetura do meu sistema?
Não haverá uma quantidade enorme de emails enviados (no máximo 2000 por dia). Os e-mails não precisam ser enviados imediatamente, até 10 minutos de atraso é bom.
Atualização: o serviço de enfileiramento de mensagens foi fornecido como resposta, mas como isso seria projetado? Isso seria tratado no aplicativo e processado durante um período silencioso ou preciso criar um novo 'aplicativo de email' ou serviço da web para gerenciar apenas a fila?
Respostas:
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:n
quantidade de mensagens da fila,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: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:
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:
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). Areason
é uma constante e é claro que eu tenho um grande em algum lugar da tabela que mapeiareason
a explicações mais completas (Eu consegui enviar cerca de 500 e-mails para os clientes com a enigmáticareason
em vez da mensagem mais completa uma vez).Leitura adicional
Padrões:
Ferramentas:
Leituras interessantes:
fonte
Você precisa de algum tipo de sistema de filas.
Uma maneira simples poderia ser gravar em uma tabela de banco de dados e ter outras linhas de processos de aplicativos externos nessa tabela, mas existem muitas outras tecnologias de enfileiramento que você poderia usar.
Você pode ter uma importância nos emails, para que alguns sejam acionados quase imediatamente (redefinição de senha, por exemplo), e os de menor importância podem ser agrupados em lotes para serem enviados posteriormente.
fonte
Além da fila, a segunda coisa que você deve considerar é enviar e-mails através de serviços especializados: MailChimp, por exemplo (eu não sou afiliado a este serviço). Caso contrário, muitos serviços de email, como o Gmail, em breve enviarão suas cartas para uma pasta de spam.
fonte
Eu modelei o sistema da minha fila em 2 tabelas diferentes como;
Há 1-1 relação entre essas tabelas.
Tabela de mensagens para armazenar o conteúdo da mensagem. O conteúdo real (Para, CC, BCC, Assunto, Corpo etc.) é serializado no campo Gráfico no formato XML. As informações Outras de, Até são usadas apenas para relatar problemas sem desserializar o gráfico. Separar esta tabela permite particionar o conteúdo da tabela em um armazenamento em disco diferente. Quando você estiver pronto para enviar uma mensagem, precisará ler todas as informações; portanto, não há nada errado em serializar todo o conteúdo em uma coluna com índice de chave primária.
Tabela MessageState para armazenar o estado do conteúdo da mensagem com informações adicionais baseadas em data. A separação dessa tabela permite um mecanismo de acesso rápido com índices adicionais no armazenamento rápido de E / S. Outras colunas já são auto-explicativas.
Você pode usar um conjunto de encadeamentos separado que varre essas tabelas. Se o aplicativo e o pool residirem na mesma máquina, você poderá usar uma classe EventWaitHandle para sinalizar o pool do aplicativo sobre algo inserido nessas tabelas; caso contrário, a varredura periódica com um tempo limite é a melhor.
fonte