遇到大文件下载、批量抓取或者视频流占用带宽的情况时,单个连接把出口带宽吃满并不罕见。Apache 官方内置的 mod_ratelimit 模块就是用来限制响应数据发送速率的,但它的作用对象和配置方式经常被误读成请求频率限制。这篇文章直接围绕 mod_ratelimit 的启用、参数、作用域和验证方法展开,帮助你在不修改业务代码的前提下完成带宽控制。

一、mod_ratelimit 到底限制的是什么
首先要厘清一个概念:mod_ratelimit 限制的是响应内容的传输速率,而不是单位时间内的请求数量。也就是说,它控制的是 Apache 向客户端发送数据的速度,比如每秒发送多少 KiB,而不会阻止客户端发起多少次请求。很多场景下这两者被混为一谈,实际上如果需求是限制某个 IP 每秒访问接口的次数,应该考虑 mod_evasive、mod_qos 或者应用层的令牌桶限流,而不是 mod_ratelimit。
该模块的工作方式是在 Apache 的输出过滤器链中插入一个速率控制过滤器。当响应开始向客户端发送数据时,过滤器会持续统计已经发送的字节数和经过的时间,并通过插入微小延迟来保证整体速度不超过设定值。因为它是基于连接的输出流来工作的,所以对静态文件、动态生成内容、代理响应都有作用。不过对于已经经过压缩处理的内容,mod_ratelimit 控制的是压缩后数据流的速率,这一点在调试时要特别注意,不要直接拿未压缩文件大小去估算限速后的传输时间。
从适用场景看,mod_ratelimit 最适合用来保护下载目录、限制 ISO 或压缩包分发速度、避免单个用户占用过多服务器带宽。它不适合做精确到毫秒级的请求频率控制,也不适合替代防火墙层面的连接数限制。理解了这些边界,后面配置起来才能避免预期偏差。
二、启用模块并设置基础限速
Apache 2.4 系列通常已经包含 mod_ratelimit,但是否加载取决于发行版和编译选项。可以先通过 apachectl -M | grep ratelimit 或 httpd -M | grep ratelimit 查看模块状态。如果没有任何输出,说明模块没有被加载。在 Debian 或 Ubuntu 上可以使用 sudo a2enmod ratelimit 启用,然后重启 Apache;在 RHEL、CentOS 或源码编译环境中,需要确认 httpd.conf 中存在 LoadModule ratelimit_module modules/mod_ratelimit.so 这行配置,如果没有则需要手动添加。
启用模块后,基础限速配置非常简单。核心只有两条指令:用 SetOutputFilter RATE_LIMIT 开启速率过滤器,再用 SetEnv rate-limit 400 指定速率为 400 KiB/s。这里要注意单位是 KiB/s,而不是 kbps,也不是每秒请求数。以下是一个针对整个虚拟主机的示例:
<VirtualHost *:80>
ServerName ipipp.com
DocumentRoot /var/www/html
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400
</VirtualHost>
rate-limit 环境变量的值必须是正整数,设置为 0 不会限速,反而可能被忽略或产生意外行为,因此不要用 0 来关闭限速。如果希望某个目录不限速,应该在相应作用域中不启用 SetOutputFilter RATE_LIMIT,或者使用更精确的过滤条件。
除了持续速率,mod_ratelimit 还支持 rate-initial-burst 变量,单位同样是 KiB。它表示在正式开始限速之前允许以最高速度发送的初始数据量。例如小文件可以快速下载完,而大文件超过突发阈值后才开始按固定速率传输。配置示例:
SetOutputFilter RATE_LIMIT SetEnv rate-limit 200 SetEnv rate-initial-burst 2048
上面配置表示前 2048 KiB 数据不受限速,之后按 200 KiB/s 发送。这个参数对改善用户体验很有帮助,尤其是下载页面上包含的预览图或小体积附件时,用户不会因为严格限速而等待过久。
三、按目录和文件类型精准限速
全局限速虽然简单,但生产环境中往往只需要对特定目录或特定文件类型做限制。可以借助 Apache 的作用域容器来实现,比如 <Directory>、<Location> 和 <FilesMatch>。在目录级别设置时,只有访问该目录下文件才会触发速率控制,其他资源不受影响。下面示例对下载目录限速 300 KiB/s,并给予 1 MiB 初始突发:
<Directory "/var/www/html/downloads">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 300
SetEnv rate-initial-burst 1024
</Directory>
如果需要针对文件扩展名做限制,可以用 <FilesMatch> 配合正则表达式。比如只对 .iso、.zip 和 .tar.gz 文件限速 150 KiB/s,其他文件不做处理。配置如下:
<FilesMatch "\.(iso|zip|tar\.gz)$">
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 150
</FilesMatch>
这里要特别说明,<FilesMatch> 匹配的是请求文件名部分,不是完整 URL 路径。如果希望按 URL 路径限制,比如只限制 /api/export/ 下的导出文件,使用 <Location> 会更准确。另外多个作用域同时匹配时,Apache 会根据作用域合并规则处理,通常最具体的作用域设置会优先生效,但过滤器本身不会叠加。若配置后感觉限速值不符合预期,可以缩小作用域范围来排查。
还需要注意,如果同时使用了压缩模块如 mod_deflate,过滤器的顺序会影响实际限速效果。mod_ratelimit 的过滤器默认会尽早插入,但不同 Apache 版本存在差异。如果发现限速明显偏离设定值,可以尝试在 <Directory> 中调整 SetOutputFilter 的顺序,或者临时关闭压缩来对比测试。
四、验证限速效果与常见排错
配置完成之后,用命令行工具验证效果最直接。curl 可以显示下载速度,例如执行 curl -o /dev/null -w '%{speed_download}\n' http://ipipp.com/downloads/bigfile.iso,输出的单位是字节每秒。把结果除以 1024 就可以得到 KiB/s,与配置值比较即可确认是否生效。如果限速后速度仍然接近满带宽,重点检查模块是否加载成功,以及 SetOutputFilter RATE_LIMIT 是否处于正确的作用域内。
查看模块加载情况使用 apachectl -M | grep ratelimit,如果确认模块存在但限速不生效,可以检查 Apache 日志中是否有过滤器相关警告。某些情况下,代理模块或应用网关会在 Apache 之前处理响应,mod_ratelimit 可能无法直接作用于最终发送给客户端的数据。此时建议在 Apache 最外层的虚拟主机上配置限速,或者检查反向代理设置是否绕过了 Apache 的输出过滤器。
限速值设置过低也会带来问题,例如 50 KiB/s 下载 500 MB 文件需要将近三小时,用户很可能中断连接,反而影响体验。对于大文件分发,建议先计算目标传输时间,再结合 rate-initial-burst 给一个合理的初始速度。另外,浏览器端可能因为 TCP 超时或用户主动取消而断连,这属于正常现象,不代表限速失败。如果只是临时限制某个 IP 或某次活动流量,建议配合其他条件指令动态调整,不要长期使用激进限速。

