Bir web uygulamasından gönderilen otomatik e-postaları yönetme


12

Bir web uygulaması tasarlıyorum ve otomatik e-posta göndermeyi yönetmek için mimariyi nasıl tasarlayacağımı merak ediyorum.

Şu anda bu özelliği web uygulamamda oluşturuyorum ve e-postalar kullanıcı girişine / etkileşimlerine (yeni bir kullanıcı oluşturmak gibi) göre gönderiliyor. Sorun, bir posta sunucusuna doğrudan bağlanmanın birkaç saniye sürmesidir. Uygulamamı büyütmek, bu gelecekte önemli bir şişe boynu olacak.

Sistem mimarim dahilinde büyük miktarda otomatik e-posta göndermeyi yönetmenin en iyi yolu nedir?

Çok fazla e-posta gönderilmez (günde en fazla 2000). E-postaların hemen gönderilmesine gerek yoktur, 10 dakikaya kadar gecikme iyidir.

Güncelleme: Mesaj kuyruğu bir cevap olarak verilmiştir, ancak bu nasıl tasarlanır? Bu uygulamada ele alınacak ve sessiz bir süre içinde işlenecek mi, yoksa sadece kuyruğu yönetmek için yeni bir 'posta uygulaması' veya web hizmeti mi oluşturmam gerekiyor?


Bize kaba bir ölçek duygusu verebilir misiniz? Yüzlerce, binlerce veya milyonlarca posta? Ayrıca, e-postalar hemen gönderilmeli mi yoksa küçük bir gecikme kabul edilebilir mi?
yannis

E-posta göndermek, bir SMTP mesajını alıcı bir posta ana bilgisayarına teslim etmeyi içerir, ancak bu mesajın gerçekten teslim edildiği anlamına gelmez. Etkili bir şekilde, tüm e-posta gönderme zaman uyumsuzdur ve "başarıyı beklemek" gibi davranmanın bir anlamı yoktur.
Kilian Foth

1
Im "başarı bekliyor" değil, ama smtp sunucusunun isteğimi kabul etmesini beklemek zorundayım. @YannisRizos bkz. Güncelleme RE yorumunuz
Gaz_Edge

2000 (tarif edilen maks. E-postalarınız) için sadece çalışır. 10 iş saatinde olduklarında, dakikada yapılabilecek 3 posta, bu da çok yapılabilir. DNS kaydınızı iyi ayarladığınızdan emin olun ve sağlayıcı bunları bu miktarlarda göndermeyi kabul eder. Ayrıca şunu da düşünün: "posta sunucusu ne durumda?". 2000 posta göndermenin yükü endişelenecek bir şey değil.
Luc Franken

Nereye cevap CRONTAB
Tulains Córdova

Yanıtlar:


15

Ozz'un daha önce de belirttiği gibi ortak yaklaşım bir mesaj kuyruğu . Tasarım açısından bakıldığında, bir mesaj kuyruğu esasen bir FIFO kuyruğudur ve oldukça temel bir veri türüdür:

FIFO sırası

Bir ileti kuyruğunu özel kılan, uygulamanızın kuyruktan sorumlu olmakla birlikte, kuyruktan çıkarmadan farklı bir işlemin sorumlu olacağıdır. Kuyruğa alma dilinde, uygulamanız mesajların göndericisidir ve kuyruktan çıkarma işlemi alıcıdır. Bariz avantaj, tüm sürecin asenkron olması, alıcı, işlenecek mesajlar olduğu sürece gönderenden bağımsız olarak çalışmasıdır. Bariz dezavantaj, her şeyin çalışması için ekstra bir bileşene, gönderene ihtiyacınız olmasıdır.

Mimariniz artık mesaj alışverişi yapan iki bileşene dayandığından, bunun için süslü terim süreçler arası iletişimi kullanabilirsiniz .

Kuyruk eklemek uygulamanızın tasarımını nasıl etkiler?

Uygulamanızdaki belirli işlemler e-posta oluşturur. Bir mesaj kuyruğunun tanıtılması, bu eylemlerin artık mesajları bunun yerine kuyruğa göndermesi gerektiği anlamına gelir (ve daha fazlası değil). Bu mesajlar, alıcınız bunları işlediğinde e-postaları oluşturmak için gerekli olan minimum bilgi miktarını taşımalıdır.

Mesajların biçimi ve içeriği

İletilerinizin biçimi ve içeriği tamamen size bağlıdır, ancak ne kadar küçük olursa o kadar iyi olduğunu unutmayın. Sıranız üzerine yazmak ve işlemek için olabildiğince hızlı olmalı, büyük miktarda veri atmak muhtemelen bir darboğaz yaratacaktır.

Ayrıca, birkaç bulut tabanlı kuyruk hizmetinin ileti boyutları üzerinde kısıtlamaları vardır ve daha büyük iletileri bölebilirler. Fark etmeyeceksiniz, bölünmüş mesajlar sizden istendiğinde bir olarak sunulacak, ancak birden fazla mesaj için ücretlendirileceksiniz (elbette ücret gerektiren bir hizmet kullandığınız varsayılarak).

Alıcının tasarımı

Bir web uygulaması hakkında konuştuğumuzdan, alıcınız için ortak bir yaklaşım basit bir cron betiği olacaktır. Her aday olacağını xdakika (veya saniye) ve it would:

  • nKuyruktaki mesajların pop miktarı,
  • Mesajları işleyin (yani e-postaları gönderin).

Get veya getir yerine pop demeye dikkat ediyorum, bunun nedeni alıcınızın yalnızca kuyruktan öğe almaması değil, aynı zamanda bunları da temizlemesidir (yani onları kuyruktan kaldırmak veya işlenmiş olarak işaretlemek). Bunun tam olarak nasıl olacağı, mesaj kuyruğunuzu uygulamanıza ve uygulamanızın özel ihtiyaçlarına bağlıdır.

Tabii ki tanımladığım şey aslında bir toplu işlem , bir kuyruğu işlemenin en basit yolu. İhtiyaçlarınıza bağlı olarak mesajları daha karmaşık bir şekilde işlemek isteyebilirsiniz (bu da daha karmaşık bir sıra gerektirir).

Trafik

Alıcınız trafiği dikkate alabilir ve işlem yaptığı mesaj sayısını çalıştığı sırada trafiğe göre ayarlayabilir. Basit bir yaklaşım, geçmiş trafik verilerine dayalı olarak yüksek trafik saatlerinizi tahmin etmek ve her xdakika çalışan bir cron komut dosyası ile gittiğinizi varsayarsak, şöyle bir şey yapabilirsiniz:

if( 
    now() > 2pm && now() < 7pm
) {
    process(10);
} else {
    process(100);
}

function process(count) {
    for(i=0; i<=count; i++) {
        message = dequeue();
        mail(message)
    }
}

Çok saf ve kirli bir yaklaşım, ama işe yarıyor. Değilse, diğer yaklaşım, sunucunuzun her bir yinelemedeki mevcut trafiğini bulmak ve işlem öğelerinin sayısını buna göre ayarlamak olacaktır. Yine de kesinlikle gerekli değilse, mikro optimizasyon yapmayın, zamanınızı boşa harcıyorsunuz.

Kuyruk depolama

Uygulamanız zaten bir veritabanı kullanıyorsa, o zaman tek bir tablo en basit çözüm olacaktır:

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)
) 

Gerçekten bundan daha karmaşık değil. Elbette ihtiyacınız olduğu kadar karmaşık hale getirebilirsiniz, örneğin, bir öncelik alanı ekleyebilirsiniz (bu, bunun artık bir FIFO kuyruğu olmadığı anlamına gelir, ancak gerçekten ihtiyacınız varsa, kimin umurunda?). Ayrıca, işlenen alanı atlayarak da daha basit hale getirebilirsiniz (ancak satırları işledikten sonra silmeniz gerekir).

Bir veritabanı tablosu günde 2000 mesaj için ideal olurdu, ancak muhtemelen günde milyonlarca mesaj için iyi ölçeklenmeyecektir. Dikkate alınması gereken bir milyon faktör vardır, altyapınızdaki her şey uygulamanızın genel ölçeklenebilirliğinde rol oynar.

Her durumda, veritabanı tabanlı kuyruğu bir darboğaz olarak tanımladığınızı varsayarsak, bir sonraki adım bulut tabanlı bir hizmete bakmak olacaktır. Amazon SQS kullandığım tek hizmetti ve vaat ettiklerini yaptı. Eminim orada birkaç benzer hizmet var.

Bellek tabanlı kuyruklar da, özellikle kısa ömürlü kuyruklar için dikkate alınması gereken bir şeydir. memcached , mesaj kuyruğu depolaması olarak mükemmeldir.

Sıranızı oluşturmaya karar verdiğiniz depolama alanı ne olursa olsun, akıllı olun ve soyutlayın. Ne gönderen ne de alıcınız belirli bir depolama birimine bağlı olmamalıdır, aksi takdirde daha sonra farklı bir depolama birimine geçmek tam bir PITA olacaktır.

Gerçek hayat yaklaşımı

Yaptığınız şeye çok benzeyen e-postalar için bir mesaj kuyruğu oluşturdum. Bir PHP projesindeydim ve Zend Framework'ün farklı depolar için çeşitli adaptörler sunan bir bileşeni olan Zend Queue etrafında oluşturdum . Depolarım nerede:

  • Birim testi için PHP dizileri,
  • Üretimde Amazon SQS,
  • Geliştirme ve test ortamlarında MySQL.

Mesajlarım olabildiğince basitti, uygulamam gerekli bilgileri içeren küçük diziler oluşturdu ( [user_id, reason]). Mesaj deposu bu dizinin serileştirilmiş bir versiyonuydu (ilk önce PHP'nin dahili serileştirme formatı, sonra JSON, neden geçiş yaptığımı hatırlamıyorum). Bu reasonbir sabit ve tabii ki reasondaha dolgun açıklamalar eşler bir yerde büyük bir tablo var (bir reasonkez daha dolu mesaj yerine şifreli müşterilere yaklaşık 500 e-posta göndermek için başardı ).

daha fazla okuma

Standartlar:

Araçlar:

İlginç okumalar:


Vay. Burada şimdiye kadar aldığım en iyi cevap! Sana yeterince teşekkür edemem!
Gaz_Edge

Ben ve milyonlarca kişinin bu FIFO'yu Gmail ve Google Apps Script ile kullandığından eminim. bir Gmail filtresi gelen postaları bir ölçüt temelinde etiketler ve hepsi sıraya koyar. Google Apps Komut Dosyası her X döneminde çalışır, önce y iletileri alır, gönderir, aldatır. Durulayın ve Tekrarlayın.
DavChana

6

Bir tür kuyruk sistemine ihtiyacınız var.

Bunun basit bir yolu, bir veritabanı tablosuna yazmak ve bu tabloda başka bir dış uygulama işlem satırına sahip olmak olabilir, ancak kullanabileceğiniz birçok kuyruklama teknolojisi vardır.

Belirli e-postaların hemen hemen işleme koyulması (örneğin parola sıfırlama) ve daha az önem taşıyan e-postalar daha sonra gönderilmek üzere toplu hale getirilebilir.


bunun nasıl çalıştığını gösteren bir mimari diyagramınız veya örneğiniz var mı? Örneğin, kuyruk farklı bir 'app' diyor posta uygulamasında mı oturuyor yoksa sessiz bir süre içinde web uygulamasından mı işlem yapıyor? Yoksa bunları işlemek için bir tür web hizmeti oluşturmalı mıyım?
Gaz_Edge

1
@Gaz_Edge Uygulamanız öğeleri sıraya iter. Bir arka plan işlemi (büyük olasılıkla bir cron betiği) x öğeyi her n saniyede bir kuyruktan çıkarır ve işler (sizin durumunuzda, e-postayı gönderir). Tek bir veritabanı tablosu, az miktarda öğe için kuyruk depolama alanı olarak iyi çalışır, ancak genellikle bir veritabanındaki konuşma yazma işlemleri pahalıdır ve daha büyük miktarlar için Amazon'un SQS'si gibi hizmetlere bakmak isteyebilirsiniz .
yannis

1
@Gaz_Edge Yazdığımdan daha basit bir diyagram çizebileceğimden emin değilim "... bir veritabanı tablosuna yaz ve bu tabloda başka bir harici uygulama işlemi satırı var ...." ve tablo için "herhangi bir kuyruğu oku "ne olursa olsun teknoloji.
Mart'ta ozz

1
(devam ...) Sırayı temizleyen arka plan işlemini trafiğinizi dikkate alacak şekilde oluşturabilirsiniz, örneğin sunucunuz stres altındayken daha az öğe (veya hiç yok) işlemesi talimatını verebilirsiniz . Geçmiş trafik verilerinize bakarak (göründüğünden daha kolay, ancak büyük bir hata payı ile) veya arka plan işleminizin her çalıştığında trafik durumunu kontrol etmesini (daha doğru, ancak ek yükü nadiren gereklidir).
yannis

@YannisRizos yorumlarınızı bir cevapta birleştirmek mi istiyor? Ayrıca, mimari şemaları ve tasarımları yardımcı olacaktır (Bu sefer onları bu sorudan almaya kararlıyım! ;-))
Gaz_Edge

2

Çok fazla e-posta gönderilmez (günde en fazla 2000).

Sıraya ek olarak, göz önünde bulundurmanız gereken ikinci şey, özel hizmetler yoluyla e-posta göndermektir: Örneğin, MailChimp, (Bu hizmete bağlı değilim). Aksi takdirde, gmail gibi birçok posta hizmeti yakında mektuplarınızı bir spam klasörüne gönderir.


2

Benim kuyruk sistemi farklı 2 tabloda modellenmiştir;

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]

Bu tablolar arasında 1-1 ilişki vardır.

Mesaj içeriğini depolamak için Mesajlar tablosu. Gerçek içerik (To, CC, BCC, Subject, Body vb.) XML alanına Grafik alanına serileştirilir. Diğer Kimden, Kime bilgileri yalnızca grafiğin serileştirmesini kaldırmadan sorunları bildirmek için kullanılır. Bu tablonun ayrılması, tablo içeriğinin farklı bir disk deposuna bölünmesine izin verir. Mesaj göndermeye hazır olduğunuzda, tüm bilgileri birincil anahtar dizini ile bir sütuna serileştirmekle ilgili yanlış bir şey bilmemeniz gerekir.

Ek tarih tabanlı bilgiler içeren ileti içeriğinin durumunu depolamak için MessageState tablosu. Bu tabloyu ayırmak, hızlı G / Ç depolamasında ek dizinlerle hızlı erişim mekanizmasına izin verir. Diğer sütunlar zaten açıklayıcıdır.

Bu tabloları tarayan ayrı bir iş parçacığı havuzu kullanabilirsiniz. Uygulama ve havuz aynı makinede yaşıyorsa , havuzu bu tablolara eklenen bir şey hakkında uygulamadan bildirmek için bir EventWaitHandle sınıfı kullanabilirsiniz , aksi takdirde periyodik olarak bir zaman aşımı ile tarama en iyisidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.