Qmail邮件队列工作原理

12/3/2006来源:Qmail人气:5548

1.概述
以下是qmail的数据流简图

qmail-smtpd --- >>qmail-queue -> qmail-send <<--- qmail-rspawn <<--- qmail-remote
/ |
qmail-inject _/ qmail-clean \_ qmail-lspawn <<--- qmail-local

qmail中,每一条消息都发送到中央队列等待发送,由qmail-queue进程控制。它在以下情况被调用:
1、当产生本地消息时,qmail-inject进程调用qmail-queue。
2、qmail-smtpd准备SMTP协议下的投递邮件任务时调用它。
3、向前(forwarded)发送邮件时,qmail-local调用它。
4、退回邮件时,qmail-send调用它。

每封邮件接着由qmail-lspawn 和qmail-rspawn协助qmail-sned进程完成投递,最后由qmail-clean清除邮件队列。这四个进程是系统由始至终都在运行的,十分重要。

qmail的队列被设计成很强的鲁棒性,并假定基础的文件系统也是强健的。所有的cleanups清除队列操作都由qmail-send独立控制,无须人为干预。详细请看第六部分。

2. 队列结构

队列里的每条消息都由唯一的号码标识,假定某条消息是的标识码是457。队列被组织成几个目录,每个目录都可能包含和这条消息相关的文件:

mess/457: 消息正文
todo/457: 信封: 消息的来源地址和目的地址。实际是指向intd/457的链接。
intd/457: 信封,由qmail-queue生成。
info/457: 信封上的发送者地址,预处理后生成。
local/457: 本地接受地址,预处理后生成。
remote/457: 远程接受地址,预处理后生成。
bounce/457: 传输错误信息。

以下是一条消息所有可能的状态。“+”号表示该目录下文件存在,“-”表示该目录下文件不存在,“?”表示未知。
S1. -mess -intd -todo -info -local -remote -bounce
S2. +mess -intd -todo -info -local -remote -bounce
S3. +mess +intd -todo -info -local -remote -bounce
S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued,表示已经进入队列)
S5. +mess -intd -todo +info ?local ?remote ?bounce (PReprocessed,表示已通过预处理)

重点:如果消息457存在,那么它也对应一个inode结点号457。


3. 消息如何进入队列?

添加一条消息到队列里,qmail-queue首先创建一个单独的目录,命名为 pid/。然后在这个目录下为这条消息创建一个独立文件。文件系统为这个文件分配唯一的标识,暂时记做457。qmail-queue监视这个文件标识,并保证这条消息进入状态S1。
qmail-queue接着把pid/ 更名为mess/457,进入状态S2。然后写一些相关的信息到mess/457目录下,创建目录intd/457,进入状态S3,并在intd/457下写入相关的信封信息。
最后,qmail-queue创建一个指向 intd/457 的链接 :todo/457 ,进入状态S4。到此为止,这条消息已经被处理到队列中,等待qmail-send的处理。
qmail-queue在处理任何文件之前,会启动一个24小时的超时记时器。一旦等待处理超过24小时,qmail-queue将中止当前进程。

4. 队列中的消息如何预处理

一旦消息进入等待队列,qmail-send必须判断哪些是要投递给本地接收者,哪些是要投递给远程接收者。它也有可能重写某些接受者地址。
当qmail-send检测到 todo/457 目录,得知消息457处于状态S4。于是,它先删除已经存在的(假如)info/457, local/457 和 remote/457目录。然后读取整个todo/457目录,生成 info/457 、 local/457 或者 remote/457 目录。完成这些操作后,删除 intd/457目录。这时,该条消息仍然处于S4状态。最后,qmail-send删除 todo/457 目录,进入S5状态。到此为止,这条消息已成功的完成预处理。

5. 预处理后,消息如何被投递。

处在S5状态的消息将被做如下处理。每个 local/457 和 remote/457 目录下的邮件地址将被标记NOT DONE 或 DONE:

DONE: 表示该消息已经被成功投递,或者是投递时遇到了永久性错误。这时,qmail-send将会停止对该消息所示的地址继续进行投递任务。
NOT DONE:如果已经有投递任务在尝试,该标记表示投递人物遇到暂时性错误。这种情况下,qmail-send会随后继续尝试投递。

qmail-send 会在空闲的时间尝试给标记了NOT DONE 的地址发送邮件。如果邮件被成功处理,qmail-send将在该地址标记DONE。如果遇到永久性错误,它首先发送一条错误通知到bounce/457目录(如果该目录不存在,就自动创建),然后将邮件地址标记为DONE。

注意:bounce/457被设计成不强健的。

qmail-send会在任意的时刻处理 bounce/457目录 ,可能如下:
1)从 bounce/457 和 mess/457目录创建一条新的bounce消息
2)删除bounce/457目录。

当local/457中所有的邮件地址都被标记DONE后,qmail-send将删除local/457。同样的,remote/457也做此处理。随后,qmail-send删除队列里的邮件:
第一步、如果 bounce/457存在,qmail-send按照上述的方法处理。
第二步、如果 bounce/457已删除,qmail-send删除 info/457目录,进入状态S2;然后删除 mess/457 ,进入状态S1。

6. Cleanups清除操作。

如果在qmail-queue处理邮件队列时,或者qmail-send正删除邮件时,系统因故障崩溃,当时处理的邮件就会处在S2或者S3状态。
如果qmail-send检查到有邮件处在S2或者S3状态(不是正在删除的邮件),而mess/457的创建时间已经过了36小时,它就相续删除 intd/457 (如果存在)和 mess/457。此时,正在处理该邮件的qmail-queue将被挂起。
同样的,如果qmail-send发现 pid/ 目录下的文件创建时间超过36小时,也将删除该目录。
如果在qmail-send投递邮件时发生系统崩溃故障,Cleanups则无须运行。最遭情况下,邮件可能会被投递两次。其实,在分布式邮件系统上,目前并没有一种有效的方法来降低邮件重复投递的可能。不妨试想一下,如果在接受方的邮件服务器响应之前,投递邮件的SMTP链接发生断路错误,该怎么办?显然,用户必须考虑到这种最坏情况,并再次发送邮件。同样的,如果在qmail-send进程为邮件做DONE标记之前,计算机崩溃了。那么,重启后的新进程必须再次发送该邮件。通常的方法是,利用日志文件。反正,删除重复邮件是接受方的事情,无须顾虑。

7. 更多

目前,info/457的存在有两种作用:
1、它记录了邮件信封中,发送人的信息。
2、守护进程通过它的修改时间来判断该邮件在队列中是否超时。
将来,随着系统改进, info/457 可能会存储更多的信息。任何改变都是向后兼容的,并将由版本号标识。
当qiaml-queue成功的将一条邮件加入到队列中时,它将启动一个由qmail-send设定的触发器。该触发器的工作机制是:命名管道锁触发-- lock/trigger 。在扫描 todo/ 之前,qmail-send 打开 lock/trigger下的管道O_NDELAY 设定可读。qiaml-queue通过向O_NDELAY写一个字节来触发qmail-send。这使得 locks/trigger 可读并唤醒qmail-send进程。在再次扫描 todo/ 之前,qmail-send 先关闭lock/trigger再打开它。

,