MS SQL Server 邮件队列
使用 SQL 数据库的邮件队列。
在 Microsoft 环境中,有许多方法可以实现队列,从消息队列到文件系统,但我们将讨论使用 MS SQL Server 的选项。 本文同样适用于任何关系数据库和任何平台。
使用数据库的优点
- 事务完整性
- 并发控制
- 故障恢复
- 可扩展性
- 易于编码
- 统计/报告
- 批处理
有很多方法可以解决这个问题,我们鼓励大家提出意见和批评。 为了本文的目的,该解决方案将采取最简单的形式。
一些问题需要在良好、简单、可扩展的设计中解决。 让我们从需要发送电子邮件的代码开始。 除了通常的内容(收件人、发件人、正文)之外,可能还需要优先级、重试和状态。 根据这些要求,我们可以在数据库中设计一个简单的表
CREATE TABLE [dbo].[MailQueue] (
[ID] [int] IDENTITY (10000, 1),
[dtStamp] [datetime],
[DateToProcess] [datetime],
[DateProcessed] [datetime],
[FromName] [varchar] (100),
[FromAddress] [varchar] (400),
[ToAddress] [varchar] (400),
[CC] [varchar] (400),
[varchar] (400),
[Status] [varchar] (800),
[ThreadLock] UNIQUEIDENTIFIER,
[AttemptsRemaining] [int],
[Priority] [int],
[text],
)
这个简单的表模式将提供队列。 代码会将行插入到表中,每封电子邮件一行。 实际工作将在从队列中读取的 Windows 服务中完成。 让我们深入了解哪些列是不明确的。
状态
- 插入该行时,状态将开始为“UnSent”。
- 如果电子邮件已成功发送,则状态将更改为“Sent”。
- 如果邮件未发送,则状态将表示一些错误。
尝试剩余次数
每次电子邮件服务尝试发送电子邮件失败时,此值都会递减。 从队列中提取邮件时,电子邮件服务将仅读取此值大于零的行。
优先级
通过使此数字更大,电子邮件服务将首先使用此数字按其优先级选择电子邮件行。
线程锁定
电子邮件服务尝试发送电子邮件时,将设置此字段。 服务处理完该行后,该字段将设置回 null。
用于获取行的 SQL
BEGIN TRANSACTION
-- retry old mails that failed
UPDATE MailQueue
SET ThreadLock = NULL
WHERE ThreadLock IS NOT NULL
AND DateProcessed < DATEADD( minute, 15, GETDATE() )
AND Status != 'Sent'
-- select mails to send
SELECT TOP 10 *
INTO #tmpRows
FROM MailQueue
WHERE ThreadLock IS NULL
AND DateToProcess > GETDATE()
AND AttemptsRemaining > 0
ORDER BY Priority DESC
-- update to lock them
UPDATE MailQueue
SET ThreadLock = '<MY GUID>',
DateProcessed = GETDATE()
FROM #tmpRows r
WHERE r.ID = MailQueue.ID
AND ThreadLock IS NULL
AND DateToProcess > GETDATE()
AND AttemptsRemaining > 0
COMMIT TRANSACTION
-- select rows
SELECT *
FROM MailQueue
WHERE ThreadLock = '<MY GUID>'
需要传入三个变量; 每批的行数(在本例中为 10),由将处理批次的线程生成的 GUID(在本例中为“<MY GUID>”)以及用于重试电子邮件的线程超时时间(在本例中为 15 分钟)。
电子邮件服务
该服务将运行上述 SQL 并迭代行,发送电子邮件。 该服务可以是多线程的,在这种情况下,每个线程将运行 SQL 并处理其自己的批次。 该服务可以在许多机器上运行 - 仍然不应该有两个线程同时处理同一行。
电子邮件服务的任务是更新数据库。 以下是成功和失败的示例 SQL
-- success
UPDATE MailQueue
SET ThreadLock = NULL,
DateProcessed = GETDATE(),
Status = 'Sent'
WHERE ID = 10001
-- failure
UPDATE MailQueue
SET ThreadLock = NULL,
DateProcessed = GETDATE(),
Status = 'Cannot access CDO.Message object'
AttemptsRemaining = AttemptsRemaining - 1
WHERE ID = 10002
如果电子邮件服务或线程由于某种原因死亡,则该行将在超时时间段后由下一个运行的 select 语句放回队列中。
接下来,一个 C# 示例。