웹 애플리케이션에서 전송 된 자동 이메일을 관리하는 방법


12

웹 응용 프로그램을 설계하고 있으며 자동화 된 전자 메일 전송을 관리하기 위해 아키텍처를 설계하는 방법이 궁금합니다.

현재이 기능을 내 웹 응용 프로그램에 빌드했으며 전자 메일은 사용자 입력 / 상호 작용 (예 : 새 사용자 만들기)을 기반으로 전송됩니다. 문제는 메일 서버에 직접 연결하는 데 몇 초가 걸린다는 것입니다. 내 응용 프로그램을 확장하면 앞으로 중요한 병목이 될 것입니다.

시스템 아키텍처 내에서 대량의 자동 이메일 전송을 관리하는 가장 좋은 방법은 무엇입니까?

대량의 이메일이 전송되지 않습니다 (최대 하루 2000). 이메일을 즉시 보낼 필요는 없습니다. 최대 10 분까지 지연됩니다.

업데이트 : 메시지 큐가 답변으로 제공되었지만 어떻게 설계됩니까? 앱에서 처리되고 조용한 시간 동안 처리됩니까, 아니면 대기열을 관리하기 위해 새 '메일 앱'또는 웹 서비스를 만들어야합니까?


우리에게 거친 규모 감을 줄 수 있습니까? 수백, 수천 또는 수백만 개의 메일? 또한 이메일을 즉시 보내야합니까 아니면 약간의 지연이 허용됩니까?
yannis

이메일 전송은 SMTP 메시지를 수신 메일 호스트로 전달하는 것과 관련이 있지만 메시지가 실제로 전달 된 것은 아닙니다. 따라서 모든 이메일 전송은 비동기 적이며 "성공을 기다리는"척은 없습니다.
Kilian Foth

1
"성공을 기다리는 중"은 아니지만 smtp 서버가 내 요청을 수락 할 때까지 기다려야합니다. @YannisRizos 업데이트 RE 의견 참조
Gaz_Edge

2000 (이것은 설명 된 최대) 메일의 경우 작동합니다. 업무 시간이 10 시간이라고하면 분당 3 개의 메일이 발송됩니다. DNS 레코드를 제대로 설정했는지 확인하면 제공자가이 금액으로 전송하도록 허용합니다. "메일 서버가 다운 된 이유는 무엇입니까?" 2000 개의 메일을 보내는 것은 걱정할 일이 아닙니다.
Luc Franken

CRONTAB
Tulains Córdova

답변:


15

Ozz가 이미 언급했듯이 일반적인 접근 방식 은 메시지 대기열 입니다. 디자인 관점에서 메시지 큐는 본질적으로 FIFO 큐 이며 이는 근본적인 데이터 유형입니다.

FIFO 대기열

메시지 대기열을 특별하게 만드는 것은 응용 프로그램이 대기열을 담당하지만 다른 프로세스가 대기열을 담당하지 않는다는 것입니다. 링고 큐잉에서 응용 프로그램은 메시지를 보내는 사람이고 큐에서 제거 프로세스는받는 사람입니다. 명백한 장점은 처리 할 메시지가있는 한 전체 프로세스가 비동기 적이며 수신자가 발신자와 독립적으로 작동한다는 것입니다. 명백한 단점은 모든 것이 작동하기 위해 추가 구성 요소 인 발신자가 필요하다는 것입니다.

아키텍처는 이제 메시지를 교환하는 두 가지 구성 요소에 의존하기 때문에 프로세스 간 통신 이라는 멋진 용어를 사용할 수 있습니다 .

대기열을 도입하면 애플리케이션 디자인에 어떤 영향을 미칩니 까?

응용 프로그램의 특정 작업은 이메일을 생성합니다. 메시지 대기열을 도입하면 해당 작업이 이제 메시지를 대기열로 대신 밀어야한다는 의미입니다. 이러한 메시지는 수신자가 메시지를 처리 ​​할 때 이메일을 구성하는 데 필요한 최소한의 정보를 전달해야합니다.

메시지의 형식과 내용

메시지의 형식과 내용은 전적으로 귀하에게 달려 있지만, 작을수록 좋습니다. 대기열은 가능한 한 빨리 작성하고 처리해야하며 대량의 데이터를 던져 병목 현상을 일으킬 수 있습니다.

또한 여러 클라우드 기반 큐 서비스는 메시지 크기에 제한이 있으며 더 큰 메시지를 분할 할 수 있습니다. 분할 메시지는 요청할 때 하나의 메시지로 표시되지만 여러 메시지에 대해 요금이 부과됩니다 (물론 요금이 필요한 서비스를 사용한다고 가정).

수신기의 설계

우리는 웹 응용 프로그램에 대해 이야기하고 있기 때문에 수신기에 대한 일반적인 접근 방식은 간단한 cron 스크립트입니다. 매분 x(또는 초) 마다 실행되며 다음 과 같습니다.

  • n큐에서 메시지의 양을,
  • 메시지를 처리합니다 (예 : 이메일 보내기).

get 또는 fetch 대신 pop이라고 말하고 있습니다. 수신자가 대기열에서 항목을 가져 오는 것이 아니라 항목을 지우는 것입니다 (즉 대기열에서 항목을 제거 하거나 처리 된 것으로 표시). 메시지 큐의 구현과 응용 프로그램의 특정 요구에 따라 정확히 어떻게 수행 될지가 결정됩니다.

물론 내가 설명하는 것은 본질적 으로 대기열을 처리하는 가장 간단한 방법 인 배치 작업 입니다. 필요에 따라 더 복잡한 방식으로 메시지를 처리 ​​할 수도 있습니다 (더 복잡한 큐를 요구할 수도 있음).

교통

수신자는 트래픽을 고려하고 실행시 트래픽에 따라 처리하는 메시지 수를 조정할 수 있습니다. 간단한 접근 방식은 과거의 트래픽 데이터를 기반으로 트래픽이 많은 시간을 예측하고 매분 실행되는 cron 스크립트를 사용하여 x다음과 같이 할 수 있다고 가정합니다 .

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 대기열이 아니지만 실제로 필요한 경우 누가 관심이 있습니까?). 처리 된 필드를 건너 뛰어 더 간단하게 만들 수도 있습니다 (그러나 처리 한 후에는 행을 삭제해야합니다).

데이터베이스 테이블은 하루에 2,000 메시지에 대한 이상적 일 것이다, 그러나 그것은 것입니다 아마 하루에 수백만 개의 메시지에 대한 확장 성이 좋지. 고려해야 할 백만 가지 요소가 있습니다. 인프라의 모든 것이 애플리케이션의 전반적인 확장성에 중요한 역할을합니다.

어쨌든 데이터베이스 기반 큐를 병목 현상으로 이미 식별했다고 가정하면 다음 단계는 클라우드 기반 서비스를 보는 것입니다. Amazon SQS 는 내가 사용한 하나의 서비스이며 약속 한대로했습니다. 나는 비슷한 서비스가 꽤 있다고 확신합니다.

메모리 기반 대기열은 특히 단기 대기열의 경우 고려해야 할 사항입니다. memcached 는 메시지 큐 스토리지로 탁월합니다.

큐를 구축하기로 결정한 스토리지가 무엇이든 현명하고 추상화하십시오. 발신자와 수신자 모두 특정 스토리지에 묶여서는 안됩니다. 그렇지 않으면 나중에 다른 스토리지로 전환하면 완전한 PITA가됩니다.

실제 접근

나는 당신이하고있는 것과 매우 유사한 전자 메일을위한 메시지 대기열을 만들었습니다. PHP 프로젝트에 있었고 다른 스토리지에 여러 어댑터 를 제공하는 Zend Framework의 구성 요소 인 Zend Queue 주위에 빌드했습니다 . 내 저장소 :

  • 단위 테스트를위한 PHP 배열
  • 프로덕션 환경에서의 Amazon SQS
  • 개발 및 테스트 환경의 MySQL

내 메시지는 가능한 한 간단했고 내 응용 프로그램은 필수 정보 ( [user_id, reason])로 작은 배열을 만들었습니다 . 메시지 저장소는 해당 배열의 직렬화 버전이었습니다 (먼저 PHP의 내부 직렬화 형식, JSON, 전환 이유를 기억하지 못합니다). 이것은 reason일정하며 물론 reason더 자세한 설명으로 매핑되는 큰 테이블이 있습니다 ( reason한 번 전체 메시지 대신 암호로 클라이언트에게 약 500 개의 이메일을 보낼 수 있었습니다).

추가 자료

표준 :

도구 :

재미있는 읽을 거리 :


와. 내가 여기에받은 최고의 답변에 대해! 충분히 감사 할 수 없습니다!
Gaz_Edge

저는 수백만 명의 사람들이 Gmail 및 Google Apps Script와 함께이 FIFO를 사용하고 있다고 확신합니다. Gmail 필터는 기준에 따라 모든 수신 메일에 레이블을 지정합니다. Google Apps 스크립트는 X 기간마다 실행되며 처음으로 y 개의 메시지를 받고, 보내고, 대기열에서 제외시킵니다. 헹구고 반복하십시오.
DavChana

6

일종의 큐잉 시스템이 필요합니다.

한 가지 간단한 방법은 데이터베이스 테이블에 쓰고이 테이블에 다른 외부 응용 프로그램 프로세스 행을 두는 것이지만 사용할 수있는 다른 큐 기술이 많이 있습니다.

전자 메일을 중요하게 생각하여 특정 전자 메일이 거의 즉시 수행 (예 : 암호 재설정)되고 중요도가 낮은 전자 메일을 일괄 처리하여 나중에 보낼 수 있습니다.


어떻게 작동하는지 보여주는 아키텍처 다이어그램이나 예제가 있습니까? 예를 들어, 대기열이 메일 앱과 다른 '앱'에 있거나 조용한 기간 동안 웹 애플리케이션 내에서 프로세스를 가져 옵니까? 아니면 처리 할 웹 서비스를 만들어야합니까?
Gaz_Edge

1
@Gaz_Edge 응용 프로그램이 항목을 대기열로 푸시합니다. 백그라운드 프로세스 (대부분 크론 스크립트)는 n 초마다 큐에서 x 개의 항목을 팝업하여 처리합니다 (귀하의 경우 이메일 전송). 단일 데이터베이스 테이블은 소량의 항목에 대한 대기열 스토리지로 잘 작동하지만 일반적으로 데이터베이스에 대한 쓰기 작업은 비용이 많이 들고 더 많은 양 의 경우 Amazon SQS 와 같은 서비스를보고 싶을 수 있습니다 .
yannis

1
@Gaz_Edge 필자가 "... 데이터베이스 테이블에 쓰고이 테이블에 다른 외부 응용 프로그램 프로세스 행을 가지고있는 것"보다 더 간단하게 다이어그램을 그릴 수 있는지 잘 모르겠다. "어떤 기술이든 상관 없습니다.
ozz

1
(계속 ...) 트래픽을 고려하는 방식으로 대기열을 지우는 백그라운드 프로세스를 구축 할 수 있습니다. 예를 들어 서버가 스트레스를받을 때 적은 수의 항목을 처리하도록 지시하거나 전혀 처리하지 않을 수 있습니다 . 과거의 트래픽 데이터를 보거나 (사운드보다 쉽지만 큰 오차가있는) 트래픽 프로세스가 실행될 때마다 (보다 정확하지만 백그라운드 프로세스에서 트래픽 상태를 확인하도록하여 스트레스가 많은 시간을 예측해야합니다. 추가 된 오버 헤드는 거의 필요하지 않습니다).
yannis

@YannisRizos는 귀하의 의견을 답변에 결합하고 싶습니까? 또한, 아키텍처 다이어그램 및 디자인 (나는이 질문에서이 시간을 얻을 결정 해요 ;-)!) 도움이 될 것입니다
Gaz_Edge

2

대량의 이메일이 전송되지 않습니다 (최대 하루 2000).

대기열 외에도 두 번째로 고려해야 할 사항은 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 형식의 Graph 필드에 직렬화됩니다. Other From, To 정보는 그래프를 직렬화 해제하지 않고 문제를보고하는 데 사용됩니다. 이 테이블을 분리하면 테이블 내용을 다른 디스크 스토리지로 분할 할 수 있습니다. 메시지를 보낼 준비가되면 모든 정보를 읽을 필요가 있으므로 기본 키 인덱스를 사용하여 모든 내용을 하나의 열로 직렬화하는 데 아무런 문제가 없습니다.

추가 날짜 기반 정보와 함께 메시지 컨텐츠의 상태를 저장하기위한 MessageState 테이블. 이 테이블을 분리하면 빠른 IO 스토리지에서 추가 인덱스를 사용하여 메커니즘에 빠르게 액세스 할 수 있습니다. 다른 열은 이미 설명이 필요합니다.

이 테이블을 스캔하는 별도의 스레드 풀을 사용할 수 있습니다. 응용 프로그램과 풀이 동일한 컴퓨터에있는 경우 EventWaitHandle 클래스를 사용하여 이러한 테이블에 삽입 된 항목에 대해 응용 프로그램에서 풀에 신호를 보낼 수 있습니다. 그렇지 않으면 주기적으로 시간 초과로 스캔하는 것이 가장 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.