跳转到主要内容

于 2025年04月22日 摘录自 Postfix Performance Tuning

Postfix 性能调优的目的

本文档中的提示和技巧可帮助您提升已正常运行的 Postfix 系统的性能。如果您的 Postfix 系统无法接收或发送邮件,则需先解决这些问题,并参考 DEBUG_README 文档进行指导。

对于外部内容过滤器的性能调优,请先阅读FILTER_READMESMTPD_PROXY_README文档中的相关信息。然后确保内容过滤器代码中不存在延迟。尽可能避免对具有高延迟或延迟波动大的外部数据源进行查询。您的内容过滤器将以较低的并发度运行以避免 CPU/内存不足,如果出现任何延迟,内容过滤器吞吐量将受到影响。高负载环境应避免使用 RBL 查询、复杂的数据库查询等操作。

关于邮件接收性能的主题:

邮件投递性能相关主题:

其他 Postfix 性能调优主题:

以下工具可用于在人工负载下测量邮件系统性能。它们通常不随 Postfix 安装。

通用邮件接收性能提示

  • 阅读并理解 maildrop 队列incoming queue,以及active queue的讨论内容,请参阅QSHAPE_README文档。
  • 运行本地名称服务器以减少由于 DNS 查询导致的延迟。如果您运行多个 Postfix 系统,请将每个本地名称服务器指向一个共享转发服务器,以减少上游网络链路上的查询次数。
  • 通过指定域过滤器消除不必要的 LDAP 查询。这将消除对远程域中地址的查询,并消除对部分地址的查询。有关详细信息,请参阅 ldap_table(5)

当 Postfix 对 SMTP 客户端响应缓慢时:

  • 查找明显的故障迹象,如 DEBUG_README 文档中所述,并首先解决这些问题。
  • 关闭您的 header_checksbody_checks 模式,并查看问题是否消失。
  • 关闭 chroot 操作,如 DEBUG_README 文档中所述,并查看问题是否解决。
  • 如果 Postfix 日志中将 SMTP 客户端记录为 "unknown",则存在名称服务问题:名称服务器有问题,或 resolv.conf 文件包含错误信息,或某些包过滤器正在阻止 DNS 请求或响应。
  • 如果 smtpd(8) 进程的数量已达到 master.cf 中指定的进程限制,新的 SMTP 客户端必须等待直到有进程可用。请参阅 STRESS_READMEPOSTSCREEN_README 文档,了解防止 SMTP 服务器过载的措施。

通过优化 SMTP 服务器进程提升性能

在 Postfix 2.0 及更早版本中,smtpd(8) 服务器在向 SMTP 客户端报告错误前会暂停。这种机制称为 tar pitting。然而,这些延迟也会减慢 Postfix 的运行速度。当smtpd(8)服务器响应缓慢时,会话耗时增加,因此需要更多smtpd(8)服务器进程来处理负载。当 Postfix 的 smtpd(8) 服务器进程限制达到上限时,新客户端必须等待直到有服务器进程可用。这意味着所有客户端都会体验到性能下降。

您可以通过关闭延迟来加快处理smtpd(8)服务器错误响应的速度:

/etc/postfix/main.cf:
# Postfix 2.1 及更高版本无需此设置
smtpd_error_sleep_time = 0

通过上述设置,Postfix 2.0 及更早版本可在相同数量的 SMTP 服务器进程下处理更多 SMTP 客户端。下一节将说明 Postfix 如何处理大量错误的客户端。

减慢错误较多的 SMTP 客户端

Postfix 的 smtpd(8) 服务器会为每个会话维护一个错误计数器。当消息成功传输时,错误计数会被重置;当客户端请求无法识别或无法实现时,当客户端请求违反访问限制时,或当发生其他错误时,错误计数会增加。

随着每个会话的错误计数增加,smtpd(8) 服务器会改变行为并开始在响应中插入延迟。此举旨在通过减慢运行过快的客户端来限制资源使用。该行为取决于 Postfix 版本。

重要提示:这些延迟也会减慢 Postfix 的运行速度。当延迟配置过高时,同时运行的 SMTP 会话数量将持续增加,直至达到 smtpd(8) 服务器进程限制,此时新 SMTP 客户端必须等待直到一个 smtpd(8) 服务器进程可用。

Postfix 版本 2.1 及更高版本:

Postfix 版本 2.0 及更早版本:

针对建立过多连接的客户端的措施

注意:这些功能使用 Postfix anvil(8) 服务,该服务在 Postfix 2.2 版本中引入。

Postfix smtpd(8) 服务器可以限制来自同一 SMTP 客户端的并发连接数,以及同一客户端的连接速率和某些 SMTP 命令的执行速率。这些统计信息由anvil(8)服务器维护(注意:如果anvil(8)出现故障,则连接限制将停止工作)。

重要提示:这些限制不得用于调节合法流量:否则邮件将遭受严重延迟。这些限制旨在保护smtpd(8)服务器免受不受控制客户端的滥用。

smtpd_client_connection_count_limit (默认值:50)

SMTP 客户端同时建立的最大连接数。

smtpd_client_connection_rate_limit (默认:无限制)

SMTP 客户端在由 anvil_rate_time_unit 指定的时间间隔内可建立的最大连接数(默认:60 秒)。

smtpd_client_message_rate_limit(默认:无限制)

SMTP 客户端在由 anvil_rate_time_unit 指定的时间间隔内可发送的最大消息请求数(默认:60 秒)。

smtpd_client_recipient_rate_limit(默认:无限制)

SMTP 客户端在由 anvil_rate_time_unit(默认:60 秒)指定的时间间隔内可指定的收件人地址最大数量。

smtpd_client_new_tls_session_rate_limit(默认:无限制)

SMTP 客户端在指定时间间隔内(由 anvil_rate_time_unit 指定)可协商的新 TLS 会话最大数量(不使用 TLS 会话缓存)。(默认:60 秒)

smtpd_client_auth_rate_limit(默认:无限制)

SMTP 客户端在由 anvil_rate_time_unit 指定的时间间隔内可发送的 AUTH 命令的最大数量(默认:60 秒)。在 Postfix 3.1 及更高版本中可用。

smtpd_client_event_limit_exceptions(默认值: $mynetworks

排除在上述连接和速率限制之外的 SMTP 客户端。

一般邮件投递性能提示

  • 阅读并理解 maildrop 队列incoming 队列活动队列延迟队列 的讨论,请参阅 QSHAPE_README 文档。
  • 如果出现传输缓慢的情况,请按照QSHAPE_README文档中的说明运行qshape工具。
  • 每次发送消息时包含多个收件人,而不是发送仅包含少数收件人的消息。
  • 通过 SMTP 发送邮件,而不是使用 /usr/sbin/sendmail。您可能需要调整 smtpd_recipient_limit 参数设置。
  • 避免因大量邮件提交而占用过多磁盘空间。通过调整并行提交数量和/或 Postfix 的 in_flow_delay 参数设置来优化邮件提交速率。
  • 运行本地名称服务器以减少由于 DNS 查询导致的延迟。如果运行多个 Postfix 系统,将每个本地名称服务器指向共享转发服务器以减少上游网络链路上的查询次数。
  • 减少 smtp_connect_timeoutsmtp_helo_timeout 值,以免 Postfix 在连接无响应的远程 SMTP 服务器时浪费大量时间。
  • 对于问题目的地,使用专用的邮件投递传输,并设置较短的超时时间和调整并发数。参见下文的"调整同时投递的数量"。
  • 对于首次尝试无法投递的邮件,使用fallback_relay主机。这台"坟场"机器可以使用更短的重试时间来处理难以到达的目的地。请参阅下文的"调整延迟邮件投递尝试的频率"。
  • 使用大型(64MB)持久写缓存来加快磁盘更新。这允许在系统崩溃时保持文件系统完整性的同时,对磁盘更新进行排序以实现最佳访问速度。
  • 使用固态磁盘(持久化内存磁盘)。这是一种昂贵的解决方案,应与短SMTP超时和一个fallback_relay"墓地"机器结合使用,该机器负责投递问题目的地的邮件。

调整同时交付的数量

尽管 Postfix 可配置为同时运行 1000 个 SMTP 客户端进程,但很少有场景需要它同时向同一远程系统建立 1000 个连接。为此,Postfix 内置了安全机制以避免所谓的"雷鸣群"问题。

Postfix 队列管理器实现了 TCP 慢启动流量控制策略的类比:在向某个站点发送邮件时,先发送少量邮件,然后在一切正常的情况下逐步增加并发数;在遇到拥塞时减少并发数。

  • initial_destination_concurrency 参数(默认值:5)控制在调整交付并发数之前,初始发送给同一目的地的消息数量。当然,此设置仅在未超过进程限制和特定邮件传输通道的目的地并发限制时有效。
  • default_destination_concurrency_limit 参数(默认值:20)控制同时发送到同一目的地的消息数量。您可以通过将master.cf中的条目名称后缀"_destination_concurrency_limit"来覆盖此设置。

传输特定的并发限制示例:

  • local_destination_concurrency_limit 参数(默认值:2)控制同时发送到同一本地收件人的消息数量。推荐的限制值较低,因为发送到同一邮箱的交付必须顺序进行,因此大规模并行交付没有意义。限制向同一收件人并发投递的另一个重要原因:如果收件人的 .forward 文件中包含耗时较长的 shell 命令,或者收件人是邮件列表管理员,您不希望同时运行过多该命令的实例。
  • 默认的smtp_destination_concurrency_limit值为20,似乎足以在不让系统崩溃的情况下明显增加系统负载。更改此值为更大数值时需谨慎。

上述并发限制的默认值在广泛的场景下表现良好。在网络拥塞时对这些参数进行盲目调整可能反而会加剧问题。特别是,高并发目标应绝不作为默认设置。此类设置仅应用于向少量高流量域发送邮件的传输协议。

需要高并发的一个常见场景是网关在互联网与内部邮件环境之间中继大量邮件。假设入站和出站邮件量相等,约一半的邮件将发往内部邮件中继服务器。由于内部邮件中继服务器仅从网关接收外部邮件,因此将网关配置为对内部 SMTP 服务器容量提出更高要求是合理的。

入站并发连接限制的调整无需通过试错法。一个高吞吐量的邮件枢纽应能轻松处理50或100(而非默认的20)个同时连接,尤其当网关转发至多个MX主机时。当所有MX主机正常运行并及时接受连接时,吞吐量将很高。如果任何MX主机不可用且完全无响应,平均连接延迟将至少升至1/N * $smtp_connect_timeout,其中N为MX主机数量。这将吞吐量限制为最多目标并发数 * N / $smtp_connect_timeout

例如,当目标并发数为 100 且有 2 个 MX 主机时,每个主机最多可处理 50 个并发连接。如果一个 MX 主机不可用且默认 SMTP 连接超时为 30 秒,则吞吐量限制为 100 * 2 / 30 ≈ 6 消息/秒。这表明,对于具有良好连接性和多个 MX 主机的高速率目的地,需要更低的连接超时时间,值可低至 5 秒甚至 1 秒,以防止在部分 MX 主机不可用但并非全部不可用时发生拥堵。

若必要,可将transport_destination_concurrency_limit(位于main. cf,因为这是一个队列管理器参数)和较低的 smtp_connect_timeout(在 master.cf 中,因为该参数没有针对特定传输的名称)用于中继传输以及任何专门用于特定高流量目的地的传输。

调整每次投递的收件人数

default_destination_recipient_limit 参数(默认值:50)控制 Postfix 投递代理每次发送电子邮件副本时包含的收件人数。您可以为特定的 Postfix 投递代理覆盖此设置。例如,"uucp_destination_recipient_limit = 100" 将限制每个 UUCP 投递的收件人数为 100。

如果电子邮件消息的收件人数超过某个目的地的限制,Postfix 队列管理器会将收件人列表拆分为较小的列表。Postfix 将尝试并行发送多份消息副本。

重要提示:增加每条消息的收件人限制时需谨慎;部分 SMTP 服务器在内存不足或达到硬收件人限制时会断开连接,导致消息无法送达。

smtpd_recipient_limit 参数(默认值:1000)控制 Postfix smtpd(8) 服务器在每次投递中接受的收件人数。默认限制值远高于任何合理 SMTP 客户端的发送量。此限制旨在保护本地邮件系统免受恶意客户端的攻击。

调整延迟邮件投递尝试的频率

当 Postfix 投递代理(smtp(8)local(8) 等)无法投递一条消息时,它可能会将责任归咎于消息本身,也可能归咎于接收方。

  • 当投递代理将责任归咎于消息时,队列管理器会为队列文件添加一个未来时间戳,使其在一段时间内不会被处理。默认情况下,冷却时间等于消息到达后经过的时间。这导致所谓的指数退避行为。
  • 当交付代理归咎于接收方(例如本地接收用户或远程主机)时,队列管理器不仅会将队列文件的时间戳向前推进,还会将接收方加入"死亡"列表,使其在一段时间内被跳过。

此过程由一系列小参数控制。

queue_run_delay(默认:300 秒;Postfix 2.4 之前:1000 秒)

队列管理器扫描队列以查找延迟邮件的频率。

minimal_backoff_time (默认:300 秒;Postfix 2.4 之前:1000 秒)

消息不会被查看的最短时间,以及避免访问"已死"目的地的最短时间。

maximal_backoff_time(默认:4000 秒)

消息在投递失败后不会被查看的最大时间。

maximal_queue_lifetime (默认值:5 天)

消息在队列中停留的时间,超过该时间后将被标记为无法投递并返回。若希望消息在首次投递失败后立即返回,请设置为 0。

bounce_queue_lifetime (默认:5 天,适用于 Postfix 2.1 及更高版本)

MAILER-DAEMON 消息在队列中停留的时间,超过该时间后将被视为无法投递。指定 0 表示仅尝试投递一次。

qmgr_message_recipient_limit (默认值:20000)

内存中队列管理器数据结构的大小。其中包括限制短期内存中"已死亡"目标的列表大小。无法放入该列表的目标将不会被添加。

transport_destination_concurrency_failed_cohort_limit

控制何时将目的地视为"已死"。此参数在 transport_destination_rate_delay 不为零且 transport_destination_concurrency_limit,或初始值较小的initial_destination_concurrency

重要提示:如果您增加了延迟邮件投递尝试的频率,或者频繁清空延迟邮件队列,可能会发现 Postfix 邮件投递性能实际上变差。具体症状如下:

  • 活动队列会被存在交付问题的邮件填满。新邮件仅在旧消息被延迟后才会进入活动队列。这是一个缓慢的过程,通常需要超时一个或多个 SMTP 连接。
  • 所有可用的 Postfix 投递代理都忙于尝试连接不可达站点等。新邮件必须等待直到有投递代理可用。这是一个缓慢的过程,通常需要超时一个或多个 SMTP 连接。

当邮件频繁被延迟时,解决问题总是比增加投递尝试频率更好。然而,如果你只能控制投递尝试频率,建议使用一个专用的fallback_relay "墓地"机器来处理不良目的地,以免这些目的地影响正常邮件投递的性能。

调整 Postfix 进程数量

default_process_limit 配置参数可直接控制 Postfix 运行的守护进程数量。从 Postfix 2.0 开始,默认限制为 100 个 SMTP 客户端进程、100 个 SMTP 服务器进程,依此类推。这可能会导致内存较少的系统或带宽较低的网络过载。

您可以通过在 main.cf 文件中指定一个非默认的 default_process_limit 来更改全局进程限制。例如,要运行最多 10 个 SMTP 客户端进程、10 个 SMTP 服务器进程,依此类推:

/etc/postfix/main.cfdefault_process_limit = 10

您需要执行 "postfix reload" 命令以使更改生效。此限制由 Postfix master(8) 守护进程强制执行,该守护进程在 main.cf 文件更改时不会自动读取该文件。

您可以通过编辑 master.cf 文件来覆盖特定 Postfix 守护进程的进程限制。例如,如果您不希望同时接收 100 个 SMTP 消息,但又不希望更改其他 Postfix 守护进程的进程限制,可以指定:

/etc/postfix/master.cf:
# ====================================================================
# 服务类型 权限 是否启用 运行环境 最大进程数 命令 + 参数
# (是) (是) (是) (从不) (100)
# ====================================================================.
 . .
smtp inet n - - - 10 smtpd.
 . .

调整系统中的进程数量

  • 当您增加 Postfix 进程限制时,MacOS X 会耗尽进程槽。以下方法适用于 OSX 10.4 和 OSX 10.5。

    MacOS X 内核参数可在 /etc/sysctl.conf 中指定。

    /etc/sysctl.conf:
    kern.maxproc=2048
    kern.maxprocperuid=2048
    

    遗憾的是,这些参数无法通过 "sysctl -w" 命令临时设置。您还需要在 /etc/launchd.conf 中添加以下内容,确保系统启动后 root 用户拥有正确的进程限制(2048)。否则,您需要始终以 root 用户身份运行 ulimit -u 2048,然后启动用户 shell,再启动相关进程才能生效。

    /etc/launchd.conf:
    limit maxproc 2048
    

    设置完成后重启系统。之后,限制将保持有效。

调整打开的文件或套接字数量

当 Postfix 打开过多文件或套接字时,进程将因致命错误而终止,系统可能记录"文件表已满"错误。

  • 根据 Postfix 和操作系统版本,若需每个进程超过 1024 个文件描述符,可能需要重新编译 Postfix:
    • 对于 Postfix 2.4 及更高版本,如果其编译时支持 BSD kqueue(2)(FreeBSD 4.1、NetBSD 2.0、OpenBSD 2.9)、Solaris 8 /dev/poll 或 Linux 2.6 epoll(4),则无需重新编译。
    • 否则,需要重新编译 Postfix 以覆盖默认的 FD_SETSIZE 值。
  • 减少进程数量,如上文"调整 Postfix 进程数量"中所述。较少的进程需要较少的打开文件和套接字。
  • 配置内核以支持更多打开的文件和套接字。具体细节因系统而异,并随操作系统版本变化。请务必参考系统调优指南验证以下信息:
    • 部分 FreeBSD 内核参数可在 /boot/loader.conf 中指定,部分可在 /etc/sysctl.conf 中指定或通过 sysctl 命令修改。具体取决于版本。

      kern.ipc.maxsockets="5000"
      kern.ipc.nmbclusters="65536"
      kern.maxproc="2048"
      kern.maxfiles="16384"
      kern.maxfilesperproc="16384"
      
    • Linux 内核参数可以在 /etc/sysctl.conf 中指定,或通过 sysctl 命令进行修改:

      fs.file-max=16384
      kernel.threads-max=2048
      
    • Solaris 内核参数可在 /etc/system 文件中指定,具体请参阅 Solaris FAQ 中"如何增加每个进程的文件描述符数量?"条目。

      * 设置文件描述符的硬限制
      set rlim_fd_max = 4096
      * 设置文件描述符的软限制
      set rlim_fd_cur = 1024