Webアプリケーションから送信された自動メールを管理する方法


11

私はWebアプリケーションを設計していますが、自動化された電子メールの送信を管理するためのアーキテクチャをどのように設計するのでしょうか。

現在、この機能をWebアプリに組み込み、ユーザーの入力/操作(新しいユーザーの作成など)に基づいてメールを送信しています。問題は、メールサーバーに直接接続するのに数秒かかることです。アプリケーションの規模を拡大すると、これは将来的に大きなボトルネックになるでしょう。

システムアーキテクチャ内で大量の自動化された電子メールの送信を管理する最良の方法は何ですか?

大量のメールが送信されることはありません(1日最大2000)。メールをすぐに送信する必要はありません。最大10分の遅延が問題ありません。

更新:メッセージキューが回答として提供されましたが、これはどのように設計されますか?これはアプリで処理され、静かな期間に処理されますか、またはキューを管理するだけの新しい「メールアプリ」またはWebサービスを作成する必要がありますか?


大まかなスケール感を教えてください。何百、何千、何百万ものメール?また、メールをすぐに送信する必要がありますか、それともわずかな遅延は許容できますか?
ヤンニス

電子メールの送信には、受信メールホストへのSMTPメッセージの引き渡しが含まれますが、それはメッセージが実際に配信されたことを意味しません。事実上、すべての電子メール送信は非同期であり、「成功を待つ」ふりをしても意味がありません。
キリアンフォス

1
私は「成功を待っている」わけではありませんが、SMTPサーバーがリクエストを受け入れるまで待つ必要があります。@YannisRizos更新REを参照してくださいあなたのコメント
Gaz_Edge

2000(これは説明されている最大値)のメールの場合、それは機能します。たとえば、10営業時間内に発生すると、1分間に3通のメールが届きます。DNSレコードを適切に設定し、プロバイダーがこれらの金額で送信することを受け入れていることを確認してください。「メールサーバーがダウンしているのは何ですか?」についても考えてください。2000通のメールを送信する負荷は心配することではありません。
リュックフランケン

CRONTABがどこにあるかの答え
Tulainsコルドバ

回答:


15

Ozzがすでに述べたように、一般的なアプローチはメッセージキューです。設計の観点から見ると、メッセージキューは基本的にFIFOキューであり、かなり基本的なデータ型です。

FIFOキュー

メッセージキューを特別なものにしているのは、アプリケーションがエンキューを担当している間、別のプロセスがデキューを担当するということです。キューイングの用語では、アプリケーションはメッセージの送信者であり、デキュープロセスは受信者です。明らかな利点は、処理するメッセージがある限り、プロセス全体が非同期であり、受信側が送信側とは独立して動作することです。明らかな欠点は、すべてが機能するために追加のコンポーネントである送信者が必要なことです。

アーキテクチャはメッセージを交換する2つのコンポーネントに依存するようになったため、プロセス間通信という派手な用語を使用できます。

キューを導入すると、アプリケーションの設計にどのような影響がありますか?

アプリケーションの特定のアクションは、電子メールを生成します。メッセージキューを導入すると、それらのアクションはメッセージを代わりにキューにプッシュする必要があります(それ以上何もしない)。これらのメッセージには、受信者がメールを処理するときにメールを作成するために必要な最小限の情報が含まれている必要があります。

メッセージの形式と内容

メッセージの形式と内容は完全にあなた次第ですが、小さいほど良いことに留意する必要があります。キューの書き込みと処理は可能な限り高速にする必要があります。大量のデータをスローすると、ボトルネックが発生する可能性があります。

さらに、いくつかのクラウドベースのキューサービスはメッセージサイズに制限があり、大きなメッセージを分割する場合があります。分割されたメッセージは、要求すると1つのメッセージとして配信されますが、複数のメッセージに対して課金されます(もちろん、料金が必要なサービスを使用している場合)。

受信機の設計

Webアプリケーションについて説明しているので、レシーバーの一般的なアプローチは単純なcronスクリプトです。それは毎x分(または秒)ごとに実行され、次のようになります。

  • nキューからのメッセージのポップ量、
  • メッセージを処理します(つまり、メールを送信します)。

getまたはfetchの代わりにpopと言っていることに注意してください。これは、レシーバがキューからアイテムを取得するだけでなく、アイテムをクリアする(キューからアイテムを削除する、処理済みとしてマークする)ためです。それがどのように起こるかは、メッセージキューの実装とアプリケーションの特定のニーズによって異なります。

もちろん、私が説明しているのは基本的にバッチ操作であり、キューを処理する最も簡単な方法です。ニーズに応じて、より複雑な方法でメッセージを処理することもできます(より複雑なキューも必要になります)。

トラフィック

受信者はトラフィックを考慮し、実行時のトラフィックに基づいて処理するメッセージの数を調整できます。単純なアプローチは、過去の交通量データに基づいて交通量の多い時間を予測し、1 x分ごとに実行されるcronスクリプトを使用すると仮定すると、次のようになります。

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

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

非常に素朴で汚いアプローチですが、動作します。そうでない場合、他のアプローチは、各反復でサーバーの現在のトラフィックを見つけて、それに応じて処理アイテムの数を調整することです。絶対に必要ではない場合は、最適化を行わないでください。時間を無駄にします。

キューストレージ

アプリケーションがすでにデータベースを使用している場合、その上の単一のテーブルが最も簡単なソリューションになります。

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

それは本当にそれより複雑ではありません。もちろん、必要に応じて複雑にすることもできます。たとえば、優先度フィールドを追加できます(つまり、FIFOキューではなくなりますが、実際に必要な場合は誰が気にしますか?)。処理済みのフィールドをスキップすることで、より簡単にすることもできます(ただし、処理後は行を削除する必要があります)。

データベーステーブルは、1日あたり2000メッセージに理想的ですが、おそらく 1日あたり数百万のメッセージに対して適切にスケーリングされません。考慮すべき要素は100万個あります。インフラストラクチャ内のすべてが、アプリケーションの全体的なスケーラビリティに影響します。

いずれの場合も、データベースベースのキューを既にボトルネックとして特定していると仮定すると、次のステップはクラウドベースのサービスを調べることです。Amazon SQSは私が使用したサービスの1つであり、約束どおりに実行しました。似たようなサービスがたくさんあると確信しています。

メモリベースのキューも、特に短期間のキューの場合に考慮する必要があります。memcachedは、メッセージキューストレージとして優れています。

キューを構築するストレージが何であれ、賢く抽象化します。送信者も受信者も特定のストレージに縛られるべきではありません。さもなければ、後で別のストレージに切り替えることは完全なPITAになります。

現実のアプローチ

私はあなたがやっていることと非常に似ているメールのメッセージキューを構築しました。これはPHPプロジェクト上にあり、異なるストレージ用の複数のアダプターを提供するZend FrameworkのコンポーネントであるZend Queueを中心に構築しました。私のストレージ:

  • 単体テスト用のPHP配列、
  • 本番環境のAmazon SQS、
  • 開発環境およびテスト環境でのMySQL。

私のメッセージはできる限りシンプルで、私のアプリケーションは重要な情報を持つ小さな配列を作成しました([user_id, reason])。メッセージストアは、その配列のシリアル化されたバージョンでした(最初はPHPの内部シリアル化形式、次にJSONでしたが、切り替えた理由を覚えていません)。これreasonは一定であり、もちろんreason、より詳細な説明に対応する大き​​なテーブルがどこかにあります(1 reason回で完全なメッセージの代わりに約500通の電子メールをクライアントに送信しました)。

参考文献

基準:

ツール:

興味深い読み物:


ワオ。私がここで受け取った最高の答えについてです!十分に感謝できません!
Gaz_Edge

私は、そして他の何百万人もがこのFIFOをGmailとGoogle Apps Scriptで使用していると確信しています。Gmailフィルターは基準に基づいてすべての受信メールにラベルを付け、それがすべてキューに入れられます。Google Appsスクリプトは、Xの期間ごとに実行され、最初のy個のメッセージを取得し、送信し、キューから取り出します。すすぎと繰り返し。
ダヴチャナ

6

何らかのキューイングシステムが必要です。

1つの簡単な方法は、データベーステーブルに書き込み、このテーブルの行を別の外部アプリケーションで処理することですが、使用できるキューイングテクノロジーは他にもたくさんあります。

電子メールに重要性を持たせて、特定のメールをほぼ即座に処理し(たとえばパスワードのリセット)、重要性の低いメールをまとめて後で送信できるようにすることができます。


これがどのように機能するかを示すアーキテクチャ図または例はありますか?たとえば、キュ​​ーは別の「アプリ」と呼ばれるメールアプリに置かれますか、または静かな期間中にWebアプリケーション内からプロセスを取得します。または、それらを処理するための一種のWebサービスを作成する必要がありますか?
Gaz_Edge

1
@Gaz_Edgeアプリケーションはアイテムをキューにプッシュします。バックグラウンドプロセス(おそらくcronスクリプト)は、n秒ごとにキューからx個のアイテムをポップし、それらを処理します(この場合、電子メールを送信します)。単一のデータベーステーブルは、少量のアイテムのキューストレージとして正常に機能しますが、一般的に、データベースに対する書き込み操作は高価であり、大量の場合、AmazonのSQSのようなサービスを確認する必要があります。
ヤニス

1
@Gaz_Edge「...データベーステーブルに書き込み、別の外部アプリケーションがこのテーブルの行を処理する...」と書いたものよりも簡単に図を描くことができるかどうかはわかりません。 「どんなテクノロジーであっても。
オズ

1
(続き...)トラフィックを考慮した方法でキューをクリアするバックグラウンドプロセスを構築できます。たとえば、サーバーに負荷がかかっているときに、処理するアイテムを少なくする(またはまったく処理しない)ように指示することができます。過去の交通量データ(見た目よりも簡単ですが、エラーのマージンが大きい)を見て、ストレスの多い時間を予測するか、実行するたびにバックグラウンドプロセスで交通状況をチェックする(より正確ですが、追加のオーバーヘッドはほとんど必要ありません)。
ヤニス

@YannisRizosはあなたのコメントを回答にまとめたいですか?また、アーキテクチャ図と設計が役立ちます(今回はこの質問からそれらを取得することに決めました!
;

2

大量のメールが送信されることはありません(1日最大2000)。

キューに加えて、2番目に考慮する必要があるのは、特殊なサービス、たとえばMailChimpを介してメールを送信することです(私はこのサービスと提携していません)。それ以外の場合、Gmailなどのメールサービスの多くは、すぐに手紙をスパムフォルダに送信します。


2

キューシステムを異なる2つのテーブルでモデル化しました。

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]

これらのテーブル間には1-1の関係があります。

メッセージコンテンツを保存するためのメッセージテーブル。実際のコンテンツ(To、CC、BCC、Subject、Bodyなど)は、XML形式でグラフフィールドにシリアル化されます。その他のFrom、To情報は、グラフをデシリアライズせずに問題を報告するためにのみ使用されます。このテーブルを分離すると、テーブルのコンテンツを別のディスクストレージに分割できます。メッセージを送信する準備ができたら、すべての情報を読む必要があります。そのため、すべてのコンテンツを主キーインデックスを持つ1つの列にシリアル化しても問題はありません。

追加の日付ベースの情報とともにメッセージコンテンツの状態を保存するためのMessageStateテーブル。このテーブルを分離することで、高速IOストレージにインデックスを追加して、高速アクセスメカニズムを実現できます。他の列はすでに自明です。

このテーブルをスキャンする別のスレッドプールを使用できます。アプリケーションとプールが同じマシンに存在する場合、EventWaitHandleクラスを使用して、これらのテーブルに挿入された何かについてアプリケーションからプールに信号を送ることができます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.