本文件的目的
本文件是对 Postfix 队列拥塞分析的介绍。它解释了如何使用 qshape(1) 程序来查找队列拥塞的原因。qshape(1) 随 Postfix 2.1 及更高版本的源代码一起打包,位于 "auxiliary" 目录下。本文档描述了随 Postfix 2.4 附带的 qshape(1)。
本文档涵盖以下主题:
介绍 qshape 工具
当邮件传输缓慢或队列意外增大时,以超级用户(root)身份运行 qshape(1) 以帮助定位问题。qshape(1) 程序以表格形式显示 Postfix 队列内容。
- 水平轴显示队列年龄,最近的邮件以细粒度显示,较旧的邮件以几何方式显示较粗粒度。
- 垂直轴显示目的地(或使用"-s"开关时显示发件人)域。消息最多的域首先列出。
例如,在下面的输出中,我们看到捕获的垃圾邮件在"hold"队列中发件人域分布的前 10 行(大部分为伪造):
$ qshape -s hold | head T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 486 0 0 1 0 0 2 4 20 40 419 yahoo.com 14 0 0 1 0 0 0 0 1 0 12 extremepricecuts.net 13 0 0 0 0 0 0 0 2 0 11 ms35.hinet.net 12 0 0 0 0 0 0 0 0 1 11 winnersdaily.net 12 0 0 0 0 0 0 0 2 0 10 hotmail.com 11 0 0 0 0 0 0 0 0 1 10 worldnet.fr 6 0 0 0 0 0 0 0 0 0 6 ms41.hinet.net 6 0 0 0 0 0 0 0 0 0 6 osn.de 5 0 0 0 0 0 1 0 0 0 4
- "T"列显示每个域的总计(在此示例中为发送者)。数字上方列出的数字表示该数字分钟数以内且不早于上一列年龄限制的消息数量。标有"TOTAL"的行显示所有域的总计。
- 在此示例中,有 14 条消息声称来自 yahoo.com,其中 1 条消息年龄在 10 到 20 分钟之间,1 条消息年龄在 320 到 640 分钟之间,12 条消息年龄大于 1280 分钟(1440 分钟,即一天)。
当输出为终端时,每显示1000条消息(-N选项)后会显示前20个域名的结果(-n选项),最终输出也仅显示前20个域名。这使得qshape即使在"deferred"队列非常大的情况下也非常有用,否则读取整个"deferred"队列可能需要过长时间。
默认情况下,qshape 显示"incoming" 和"active"队列的并集统计信息,这些队列是分析性能时最相关的队列。
可以请求一个替代的队列列表:
$ qshape deferred $ qshape incoming active deferred
这将显示"deferred"队列的年龄分布,或"incoming"、"active"和"deferred"队列的并集。
命令行选项控制显示的"桶"数量、最小桶的年龄限制、显示父域计数等。"-h"选项输出可用开关的摘要。
使用 qshape 进行故障排除
qshape 输出中的大数字表示发往(或声称来自)特定域的大量消息。应能快速识别哪些域在队列发送者或接收者计数中占主导地位,以及邮件洪流的大致开始和结束时间。
问题目标或发送域出现在输出表格的左上角附近。请记住,"active"队列最多可容纳 20000 个($qmgr_message_active_limit)消息。要检查是否已达到此限制,请使用:
$ qshape -s active (显示发送者统计信息)
如果总发送者数量低于 20000,则 "active" 队列 尚未饱和,任何高流量发送者域会在输出顶部附近显示。
使用 oqmgr(8),"active" 队列 也限制为最多 20000 个收件人地址($qmgr_message_recipient_limit)。要检查此限制是否已耗尽,请使用:
$ qshape active (显示收件人统计信息)
找到高流量域后,通常有必要在日志中搜索与这些域相关的最近消息。
# 查找发送到 example.com 的邮件 # $ tail -10000 /var/log/maillog | grep -E -i ': to=<;.*@example\.com>;,' | less # 查找来自 example.com 的消息 # $ tail -10000 /var/log/maillog | grep -E -i ': from=<;.*@example\.com>;,' | less
您可能需要深入查看某些特定队列 ID:
# 查找特定队列 ID 的所有消息。 # $ tail -10000 /var/log/maillog | grep -E ': 2B2173FF68: '
还需查看日志中的队列管理器警告消息。这些警告可能提示减少拥塞的策略。
$ grep -E 'qmgr.*(panic|fatal|error|warning):' /var/log/maillog
如果所有方法均无效,请尝试联系 Postfix 邮件列表寻求帮助,但请务必附上 qshape(1) 输出结果的前 10 或 20 行。
示例 1:健康队列
在仅查看 "incoming" 和 "active" 队列 时,在正常条件下(无拥塞),"incoming" 和 "active" 队列 几乎为空。邮件几乎与到达系统时一样快地离开系统,或在"active" 队列中被延迟,而不会造成拥塞。
$ qshape (显示 "incoming" 和 "active" 队列 状态) T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 5 0 0 0 1 0 0 0 1 1 2 meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
如果分别查看两个队列,"incoming" 队列 空置或可能短暂包含一两条消息,而 "active" 队列 包含更多消息且停留时间较长:
$ qshape incoming T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 0 0 0 0 0 0 0 0 0 0 0 $ qshape active T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 5 0 0 0 1 0 0 0 1 1 2 meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
示例 2:延迟队列中充满字典攻击弹回
这是来自一个服务器,其中部分托管域尚未启用收件人验证。对未验证域名的字典攻击导致退信散射。退信占据队列,但通过适当调优,它们不会填满"incoming"或"active"队列。延迟邮件的高流量并非直接警报原因。
$ qshape deferred | head T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 2234 4 2 5 9 31 57 108 201 464 1353 heyhihellothere.com 207 0 0 1 1 6 6 8 25 68 92 pleazerzoneprod.com 105 0 0 0 0 0 0 0 5 44 56 groups.msn.com 63 2 1 2 4 4 14 14 14 8 0 orion.toppoint.de 49 0 0 0 1 0 2 4 3 16 23 kali.com.cn 46 0 0 0 0 1 0 2 6 12 25 meri.uwasa.fi 44 0 0 0 0 1 0 2 8 11 22 gjr.paknet.com.pk 43 1 0 0 1 1 3 3 6 12 16 aristotle.algonet.se 41 0 0 0 0 0 1 2 11 12 15
显示的域名大多是批量邮件发送者,且所有流量均属于时间分布的尾部,表明短期到达率适中。较大的数字和较低的邮件年龄更表明当前存在问题。只要"active"和"incoming"队列较短,旧邮件仍未送达大多无害。我们还可以看到,groups.msn.com 的未送达邮件是以低速稳定流的形式出现,而非集中式的字典攻击,且该攻击已结束。
$ qshape -s deferred | head T 5 10 20 40 80 160 320 640 1280 1280+ 总计 2193 4 4 5 8 33 56 104 205 465 1309 MAILER-DAEMON 1709 4 4 5 8 33 55 101 198 452 849 example.com 263 0 0 0 0 0 0 0 0 2 261 example.org 209 0 0 0 0 0 1 3 6 11 188 example.net 6 0 0 0 0 0 0 0 0 0 6 example.edu 3 0 0 0 0 0 0 0 0 0 3 example.gov 2 0 0 0 0 0 0 0 1 0 1 example.mil 1 0 0 0 0 0 0 0 0 0 1
查看发件人分布,我们发现正如预期,大多数消息都是退信。
示例 3:活动队列中的拥塞
此示例摘自2004年2月Postfix用户列表中的讨论。报告称,尽管交付代理进程限制非常高,但"active"和"incoming"队列"仍处于拥塞状态且未缩小。该线程已存档在: http://groups.google.com/[email protected]. tw 和 http://archives.neohapsis.com/archives/postfix/2004-02/thread.html#1371
使用较旧版本的 qshape(1) 快速确定所有消息仅发往少数几个目的地:
$ qshape (显示 "incoming" 和 "active" 队列 状态) T A 5 10 20 40 80 160 320 320+ 总计 11775 9996 0 0 1 1 42 94 221 1420 user.sourceforge.net 7678 7678 0 0 0 0 0 0 0 0 lists.sourceforge.net 2313 2313 0 0 0 0 0 0 0 0 gzd.gotdns.com 102 0 0 0 0 0 0 0 2 100
"A"列显示了"活动队列"中的消息数量,而编号列显示了"延迟队列"的总数。当消息数量达到 10000 条(Postfix 1.x 的 "active" 队列 大小限制)时,"active" 队列 已满。 "incoming" 队列 正在快速增长。
在明确了问题目的地后,管理员迅速找到了问题并进行了修复。从日志中获取相同信息要困难得多。虽然仔细阅读mailq(1)输出应能得到类似结果,但通过逐条查看队列消息来评估问题严重程度要困难得多。
示例 4:高流量目标队列积压
当您发送大量邮件的目标站点不可用或响应缓慢时,邮件将迅速积压在"deferred"队列中,甚至可能进入"active"队列。qshape 输出将在所有与问题开始时间重叠的年龄桶中显示目标域的大量数据:
$ qshape deferred | head T 5 10 20 40 80 160 320 640 1280 1280+ TOTAL 5000 200 200 400 800 1600 1000 200 200 200 200 highvolume.com 4000 160 160 320 640 1280 1440 0 0 0 0 ...
这里,目的地"highvolume.com"继续积累延迟邮件。 "incoming" 和 "active" 队列 正常,但 "deferred" 队列 在 1 到 2 小时前开始增长,并持续增长。
如果高流量目的地未发生故障,而是运行缓慢,则可能在"active"队列中看到类似的拥堵。"Active"队列拥堵是更严重的警报;可能需要采取措施确保邮件被延迟发送,甚至添加一个access(5)规则,要求发件人稍后重试。
如果高流量目的地频繁出现连续连接被所有 MX 主机拒绝或"421 服务器繁忙错误",队列管理器可能会将目的地标记为"已死",尽管这些错误具有临时性。该目的地将在 $minimal_backoff_time 计时器过期后再次尝试。如果错误突发足够频繁,可能在目的地再次被标记为"已死"之前仅有少量邮件被投递。在某些情况下,通过在 "smtp_connection_cache_destinations" 中包含的表中列出适当的下一跳域,启用静态(而非按需)连接缓存可能有助于降低错误率,因为大多数消息将复用现有连接。
最常观察到出现此类错误突发情况的邮件传输代理(MTA)是 Microsoft Exchange,其在高负载下会拒绝连接。部分部署在 Exchange 服务器前端的代理病毒扫描程序会将被拒绝的连接以"421"错误形式转发给客户端。
注意,现在可以通过错误配置 anvil(8) 服务使 Postfix 表现出类似的异常行为。请勿使用anvil(8)进行稳态速率限制,其设计目的是(非故意)防止 DoS 攻击,且设置的速率限制应非常宽松!
如果发现需要向一个经常出现短暂错误爆发且连接缓存无法解决问题的目标发送大量邮件,有一个微妙的解决方法。
- Postfix 版本 2.5 及更高版本:
- 在 master.cf 中为目标设置一个专用的 "smtp" 传输的克隆。在下面的示例中,我们将它命名为 "fragile"。
- 在 master.cf 中为克隆的 smtp 传输配置一个合理的进程限制(通常为 10-20 之间)。
重要!!!在 main.cf 中为克隆的 smtp 传输配置一个较大的每个目标伪队列失败限制。
/etc/postfix/main.cf: transport_maps = hash:/etc/postfix/transport 易碎目的地并发失败队列限制 = 100 易碎目的地并发限制 = 20 /etc/postfix/transport: example.com fragile: /etc/postfix/master.cf: # 服务类型 权限 是否启用 启动方式 最大进程数 命令 易碎 unix - - n - 20 smtp
参见文档中关于 default_destination_concurrency_failed_cohort_limit 和 default_destination_concurrency_limit。
- 较早版本的 Postfix:
- 在 master.cf 中为目标设置一个专用的 "smtp" 传输的克隆。在下面的示例中,我们将它命名为 "fragile"。
- 在 master.cf 中为传输配置一个合理的进程限制(通常为 10-20 之间的数值)。
重要!!!在 main.cf 中为该传输配置一个非常大的初始和目标并发限制(例如 2000)。
/etc/postfix/main.cf: transport_maps = hash:/etc/postfix/transport initial_destination_concurrency = 2000 fragile_destination_concurrency_limit = 2000 /etc/postfix/transport: example.com fragile: /etc/postfix/master.cf: # 服务类型 权限 是否启用 chroot 唤醒 最大进程数 命令 易碎 unix - - n - 20 smtp
此配置的效果是,在不标记目标为已死的情况下,最多可容忍 2000 个连续错误,同时总并发数保持在合理范围(10-20 个进程)。此技巧仅适用于非常特殊的情况:向一个能够处理高吞吐量但因错误突发而反复被限流的通道发送大量邮件。
当目标即使在 Postfix 进程限制降至 1 后仍无法处理负载时,可采取的最后手段是在交付尝试之间插入短暂延迟。
Postfix 2.5 及更高版本:
- 在 master.cf 中为问题目的地设置一个专用的 "smtp" 传输克隆。在下面的示例中,我们将其命名为 "slow"。
在 main.cf 中配置向同一目的地发送邮件之间的短暂延迟。
/etc/postfix/main.cf: transport_maps = hash:/etc/postfix/transport 慢速目的地速率延迟 = 1 慢速目的地并发失败队列限制 = 100 /etc/postfix/transport: example.com 慢速: /etc/postfix/master.cf: # 服务类型 权限 chroot 唤醒 最大进程数 命令 slow unix - - n - - smtp
参见 default_destination_rate_delay 的文档。
此解决方案强制 Postfix smtp(8) 客户端在向同一目的地发送邮件之间等待 $slow_destination_rate_delay 秒。
重要提示!!需要设置较大的 slow_destination_concurrency_failed_cohort_limit 值。这可防止 Postfix 在仅出现一次连接或握手错误后就推迟所有发往同一目的地的邮件(原因在于非零的 slow_destination_rate_delay 会强制设置每个目的地的并发数为 1)。
较早版本的 Postfix:
- 在传输映射条目中,将问题目的地的主要下一跳设置为死主机。
在 master.cf 文件中,为传输映射指定问题目的地的 fallback_relay,并设置一个较小的smtp_connect_timeout值。
/etc/postfix/main.cf: transport_maps = hash:/etc/postfix/transport /etc/postfix/transport: example.com slow:[dead.host] /etc/postfix/master.cf: # 服务类型 权限 chroot 唤醒 最大进程数 命令 slow unix - - n - 1 smtp -o fallback_relay=problem.example.com -o smtp_connect_timeout=1 -o smtp_connection_cache_on_demand=no
此解决方案强制 Postfix smtp(8) 客户端在两次投递之间等待 $smtp_connect_timeout 秒。连接缓存功能已禁用,以防止客户端跳过已断开连接的主机。
Postfix 队列目录
以下各节描述 Postfix 队列:其用途、正常行为表现以及如何诊断异常行为。
"maildrop" 队列 ;
通过 Postfix sendmail(1) 命令提交但尚未由 pickup(8) 服务,等待在 "maildrop" 队列 中处理。即使 Postfix 系统未运行,也可以向 "maildrop" 队列 中添加消息。一旦 Postfix 启动,这些消息将开始被处理。
"maildrop"队列由单线程的pickup(8)服务定期扫描队列目录,或在收到postdrop(1) 程序通知后,由单线程 pickup(8) 服务定期扫描队列目录或在收到新消息到达通知时清空。postdrop(1) 程序是一个 setgid 辅助程序,允许无特权的 Postfix sendmail(1) 程序将邮件注入 "maildrop" 队列,并通知 pickup(8) 服务其到达。
所有进入 Postfix 主队列的邮件均通过 cleanup(8) 服务处理。清理服务负责信封和标题重写、标题和正文正则表达式检查、自动处理密件抄送收件人、milter内容处理,以及可靠地将消息插入Postfix "incoming" 队列。
在 cleanup(8) 服务中,如果标题或正文的正则表达式检查未占用过多 CPU 资源,或未出现其他软件占用所有可用 CPU 资源的情况,Postfix 的性能将受磁盘 I/O 限制。pickup(8) 服务将消息注入队列的速率主要由磁盘访问时间决定,因为 cleanup(8) 服务必须在返回成功前将消息提交到稳定存储。同样,postdrop(1) 程序将消息写入 "maildrop" 目录时也是如此。
由于拾取服务是单线程的,它每次只能以不超过清理服务磁盘 I/O 延迟(加上 CPU 时间,如果 CPU 时间不可忽略)的速率传递一条消息。
此队列中的拥塞表明本地消息提交速率过高,或者清理服务(cleanup(8) 服务中 CPU 消耗过高,或(Postfix ≥ 2.3)高延迟 milters。
注意,一旦"active" 队列 填满,清理服务将尝试通过暂停每个消息的 $in_flow_delay 来减缓消息注入。在此情况下,"maildrop"队列的拥塞可能是下游拥塞的后果,而非其自身问题。
注意,不应尝试通过 pickup(8) 服务发送大量邮件。高流量站点应避免使用通过 Postfix sendmail(1) 和 postdrop(1) 重新注入扫描邮件的"简单"内容过滤器。
本地提交邮件的高到达率可能表明存在未捕获的转发循环,或通知程序运行失控。尽量将本地邮件注入量控制在适中水平。
"postsuper -r" 命令可将选定邮件放入 "maildrop" 队列 进行重新处理。此功能主要用于重置过期的 content_filter 设置。使用 "postsuper -r" 重新排队大量邮件可能会导致 "maildrop" 队列 的大小明显增加。
"hold" 队列 ;
管理员可以定义 "smtpd" access(5) 访问策略,或 cleanup(8) 头部/正文检查,导致消息自动从正常处理中分流并无限期放置在"hold"队列中。放置在"hold"队列中的消息将一直保留在那里,直到管理员干预。位于"hold"队列"中的消息不会进行定期投递尝试。可使用postsuper(1)命令手动将消息释放到"deferred"队列"。
消息可能在"hold"队列中停留的时间超过 $maximal_queue_lifetime。如果需要从"hold"队列中释放此类"旧"消息,通常应使用"postsuper -r"将其移动到"maildrop"队列,以便消息获得新的时间戳并获得多次投递机会。"年轻"的消息可以直接使用"postsuper -H"命令移动到"deferred"队列。
"hold"队列对Postfix性能影响较小,监控"hold"队列通常更多是为了跟踪垃圾邮件和恶意软件,而非性能问题。
"incoming"队列 ;
所有进入 Postfix 队列的新邮件都会由 cleanup(8) 服务写入 "incoming" 队列。新队列文件由 "postfix" 用户创建,访问权限位(或模式)为 0600。当队列文件准备好进行进一步处理时,cleanup(8) 服务会将队列文件的模式更改为 0700,并通知队列管理器有新邮件到达。队列管理器会忽略模式为 0600 的不完整队列文件,因为这些文件仍在由 cleanup 写入。
队列管理器扫描 "incoming" 队列,并将任何新邮件移动到 "active" 队列,前提是 "active" 队列 的资源限制未被超过。默认情况下,"active" 队列 最多可容纳 20000 条消息。当"活动"队列消息限制达到上限时,队列管理器将停止扫描"传入"队列(以及"延迟"队列,详见下文)。
在正常情况下,"incoming" 队列 几乎为空(仅包含模式 0600 的文件),队列管理器可在消息可用时立即将其导入 "active" 队列。
"incoming"队列会在消息输入速率超过队列管理器将消息导入"active"队列的速率时增长。队列管理器的主要瓶颈是磁盘 I/O 和对 trivial-rewrite 服务的查找查询。如果队列管理器经常无法跟上,请考虑不要使用"慢速"查找服务(如 MySQL、LDAP 等)进行传输查找,或加快提供查找服务的主机速度。如果问题是 I/O 饥饿,请考虑将队列跨更多磁盘条带化、使用带电池写缓存的更快控制器,或进行其他硬件改进。至少确保队列目录在底层文件系统支持的情况下以 "noatime" 选项挂载。
in_flow_delay 参数用于在队列管理器开始落后时限制输入速率。cleanup(8) 服务将在无法从队列管理器获取"令牌"时,暂停 $in_flow_delay 秒后再创建新的队列文件。
由于大多数情况下cleanup(8)进程的数量受SMTP服务器并发连接数限制,输入速率最多可超过输出速率"SMTP连接数" / $in_flow_delay 消息每秒。
默认进程限制为 100,且 in_flow_delay 为 1 秒,这种耦合强度足以限制单个失控注入器每秒发送 1 条消息,但不足以抵消来自多个来源的过高输入速率。
如果服务器正遭受多方向的猛烈攻击,建议将in_flow_delay 参数的值设置为 10 秒,但仅在满足以下条件时才进行此操作:当 "incoming" 队列 在 "active" 队列 未满且 trivial-rewrite 服务使用快速传输查找机制时仍在增长。
"active" 队列 ;
队列管理器是一个交付代理调度器;它的工作是确保在指定的资源限制内快速且公平地将邮件交付到所有目的地。
"active" 队列 与操作系统的进程运行队列有些类似。队列中的消息已准备好发送(可运行),但不一定正在发送(运行)。
虽然大多数 Postfix 管理员将"active"队列视为磁盘上的一个目录,但真正的"active"队列是队列管理进程内存中的一组数据结构。
位于"maildrop"、"hold"、"incoming" 和 "deferred" 队列(见下文)中的消息不占用内存;它们安全地存储在磁盘上,等待轮到它们被处理。 "active" 队列中的消息的信封信息在内存中管理,这使队列管理器能够进行全局调度,将可用的交付代理进程分配给 "active" 队列中的适当消息。
在"active" 队列 中,(多收件人)消息会被拆分为共享相同传输/下一跳组合的收件人组;组大小受传输的收件人并发限制。
多个收件人组(来自一个或多个消息)按传输/下一跳组合分组后排入交付队列。传输的目标并发限制决定了每个下一跳的并发交付尝试次数。传输的收件人并发限制为 1 的情况特殊:这些传输按实际收件人地址而非下一跳进行分组,从而产生按收件人而非按域的并发限制。按收件人限制适用于最终投递到邮箱而非中继到远程服务器的情景。
当一个或多个目的地从队列中读取消息的速度慢于对应的消息输入速率时,会在"活动队列"中发生拥塞。
输入到"active"队列的消息来自"incoming"队列中的新邮件,以及"deferred"队列中邮件的重试。如果"deferred" 队列"变得非常大,旧邮件的重试可能会主导新邮件的到达速率。拥有更多 CPU、更快磁盘和更大网络带宽的系统可以处理更大的"deferred" 队列",但作为经验法则,"deferred" 队列" 的规模应在 100,000 到 1,000,000 条消息之间,超过此"限制"后性能可能下降。当队列达到如此规模时,系统应停止接受新邮件,或将积压邮件"暂停处理",直至底层问题解决(前提是系统仍有足够容量处理新邮件)。
当目标系统长时间不可用时,队列管理器会将其标记为"已死",并立即将所有发往该目标的邮件推迟处理,不再尝试分配给投递代理。在此情况下,消息将迅速离开"活动队列"并进入"延迟队列"(在 Postfix < 2.4 中,此操作由队列管理器直接执行;在 Postfix ≥ 2.4 中,通过"retry"投递代理实现)。
当目的地本身较慢,或存在导致过高到达率的问题时,"active"队列将不断增长,并被发往拥堵目的地的邮件占据。
缓解拥堵的唯一方法是降低输入速率或提高吞吐量。提高吞吐量需要增加并发数或降低投递延迟。
对于高流量站点,关键的调优参数是分配给"smtp"和"relay"传输的"smtp"交付代理数量。高流量站点通常会向许多不同目的地发送邮件,其中许多可能不可用或速度较慢,因此可用交付代理中相当一部分会被阻塞,等待慢速站点响应。此外,跨全球发送的邮件会产生较大的 SMTP 命令响应延迟,因此要实现高消息吞吐量,必须使用更多的并发交付代理。
默认的"smtp"进程限制为100,对于大多数站点已足够,甚至可能需要降低带宽较低的站点(当网络带宽已饱和时,增加并发数无意义)。当在"空闲"系统(CPU、磁盘I/O和网络未耗尽)中发现队列增长时,剩余的拥塞原因在于面对高平均延迟时并发数不足。如果出站 SMTP 连接数(包括 ESTABLISHED 或 SYN_SENT 状态)达到进程限制,且邮件发送缓慢而系统和网络未负载,请提高 "smtp" 和/或 "relay" 进程限制!
当高流量目的地由多个MX主机提供服务且这些主机通常具有较低交付延迟时,若其中一个MX主机不可用导致SMTP连接超时,性能将大幅下降。例如,若存在2个权重相同的MX主机,SMTP连接超时时间为30秒且其中一个MX主机不可用,平均SMTP连接将耗时约15秒完成。在默认的每个目的地并发连接限制为 20 个连接的情况下,吞吐量将降至每秒仅略高于 1 条消息。
避免一个或多个 MX 主机不可用时出现瓶颈的最佳方法是使用连接缓存。连接缓存功能在 Postfix 2.2 中引入,并默认在目标主机队列中存在邮件时按需启用。当连接缓存对特定目标启用时,已建立的连接将被复用以发送后续消息,这减少了每条消息发送所需的连接数,并在目标 MX 主机部分不可用时仍能保持良好的吞吐量。
如果连接缓存不可用(Postfix <; 2.2)或无法提供足够的延迟减少,尤其是用于转发邮件到"自有"域的"relay"传输协议,建议将 SMTP 连接超时时间设置为低于默认值(1-5 秒),并将目标并发连接限制设置为高于默认值。这将进一步减少延迟并提供更多并发连接,以在延迟增加时维持吞吐量。
为非自有域设置过高的并发限制可能被接收系统视为敌对行为,并可能采取措施防止您占用目标系统的资源。防御措施可能大幅降低您的吞吐量或完全阻断访问。在未与目标域管理员协调的情况下,请勿为远程域设置激进的并发限制。
若必要,为选定的高流量目的地专用并调优自定义传输。 "relay" 传输用于转发邮件至您的服务器作为主或备用 MX 主机的域。此类邮件可能占您邮件流量的相当大比例。请使用 "relay" 而非 "smtp" 传输发送至这些域。使用"relay"传输协议会为这些目的地分配独立的投递代理池,并允许单独调整超时和并发限制。
另一个常见的拥塞原因是无故清空整个"deferred"队列"。"deferred"队列存储了可能无法送达且送达失败时间较长的邮件(超时)。因此,面对较大的"deferred"队列(清空它!)这一常见反应,很可能适得其反,并通常会加剧拥堵。除非您预计队列中的大部分内容最近已可交付(例如在断开连接后 relayhost 恢复正常),否则请勿清空"deferred" 队列"。
注意:每次队列管理器重启时,"active" 队列 目录中可能已存在消息,但内存中的"真实" "active" 队列 为空。为恢复内存状态,队列管理器会将所有 "active" 队列中的消息回写入"incoming"队列,然后使用其正常的"incoming"队列扫描来重新填充"active"队列。将所有消息来回移动、重新执行传输表(trivial-rewrite(8) 解析服务)查找,以及将消息重新导入内存的过程非常耗时。无论如何,应避免频繁重启队列管理器(例如通过频繁执行"postfix reload")。
"延迟"队列 ;
当一条消息的所有可交付收件人都已成功交付,但部分收件人因临时原因交付失败(可能稍后成功)时,该消息会被放入"延迟"队列。
队列管理器会定期扫描"延迟"队列。扫描间隔由queue_run_delay参数控制。当正在进行"延迟"队列扫描时,如果同时正在进行"入站"队列扫描(理想情况下这些扫描应很快完成,因为"入站"队列应保持较短),队列管理器会在" incoming" 队列 和 "deferred" 队列 之间进行切换。这种 "循环" 策略可防止 "incoming" 或 "deferred" 队列 出现消息饥饿。
每次"延迟"队列的扫描仅将"延迟"队列中的一部分消息重新放入"活动"队列进行重试。这是因为每个被延迟的队列中的消息在被延迟时都会被分配一个"冷却"时间。这是通过将队列文件的修改时间时间偏移到未来来实现的。如果队列文件的修改时间尚未到达,则该队列文件不具备重新尝试的资格。
"冷却"时间至少为 $minimal_backoff_time,最多为 $maximal_backoff_time。下一次重试时间通过将消息在队列中的年龄乘以2,并向上或向下调整以确保在限制范围内来确定。这意味着较新的消息最初会被更频繁地重试。
如果高流量站点经常出现大型"延迟"队列,可能需要调整queue_run_delay、minimal_backoff_time 和 maximal_backoff_time,以在首次失败时提供足够短的延迟(Postfix ≥ 2.4 默认具有合理较低的最小重试时间),并在多次失败后可能使用更长的延迟,以减少旧消息的重传率,从而减少"active" 队列"中之前被推迟的邮件数量。如果您希望将minimal_backoff_time设置为非常低的值,还可考虑降低queue_run_delay,但需注意更频繁的扫描会增加磁盘 I/O 需求。
导致"deferred" 队列 过大的常见原因是在 SMTP 输入阶段未验证收件人。由于垃圾邮件发送者会从无法追踪的发件人地址发起字典攻击,无效收件人地址的退信会堵塞"deferred"队列"(在高流量情况下会成比例地堵塞"active"队列)。强烈建议通过使用local_recipient_maps和relay_recipient_maps 参数。即使退信迅速处理完毕,它们仍会向伪造邮件的无辜受害者发送大量垃圾邮件。为避免此情况,请勿接受发往无效收件人的邮件。
当一个拥有大量延迟邮件的主机长时间离线时,整个 "延迟" 队列 可能同时达到重试时间。这可能导致主机恢复后 "active" 队列 异常拥堵。若邮件在短暂拥堵后再次被延迟,该现象可能每隔 maximal_backoff_time 秒重复发生。或许,未来 Postfix 版本会添加一个随机偏移量到重试时间(或采用多种策略组合),以减少完全清空"deferred" 队列"的概率。
致谢
qshape(1) 程序由摩根士丹利的 Victor Duchovni 开发,他也是本文档初始版本的作者。