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再打開它。