
1. 项目概述为什么我们需要Nginx限流在线上服务运维的日常里流量就像一条河平时温顺但遇到促销、热点事件或者恶意攻击时瞬间就可能变成汹涌的洪水。服务器资源是有限的一旦洪水冲垮了堤坝服务器轻则服务响应变慢重则直接宕机导致所有用户都无法访问。这时候“限流”就成了我们手中至关重要的“泄洪闸”和“分流渠”。它不是为了阻止用户访问而是为了保护服务确保在极限压力下核心业务依然能稳定运行为大部分用户提供可用的服务这是一种优雅的降级策略。Nginx作为高性能的HTTP和反向代理服务器几乎是我们技术栈中的标配。它部署在最前线所有的请求都先经过它。因此在Nginx这一层做限流具有天然的优势拦截早、开销小、影响范围可控。相比于在应用代码中嵌入限流逻辑Nginx限流对业务代码无侵入配置灵活生效迅速能有效防止流量直接冲击到后端的应用服务器或数据库。想象一下如果没有这个闸门一个突然爆发的爬虫请求可能直接打满你的数据库连接池导致正常的用户登录、查询都失败。而Nginx限流可以在请求到达应用之前就将超出限额的请求快速拒绝或延迟处理把压力隔离在外围。所以今天我们不谈空泛的理论直接上手。我会结合自己多年踩坑的经验带你从Nginx限流的两种核心模块ngx_http_limit_req_module和ngx_http_limit_conn_module的原理讲起然后一步步配置再到生产环境中的高级用法和避坑指南。目标是让你看完就能在自己的服务器上配出一套可靠的限流规则。2. 核心原理与模块拆解Nginx官方内置的限流功能主要依赖于两个模块它们分别针对不同的场景理解其原理是正确配置的前提。2.1 漏桶算法ngx_http_limit_req_module限制请求速率这个模块实现的是经典的“漏桶算法”。我们可以把漏桶想象成一个底部有固定大小出水口的桶。请求进水无论外部的请求流量多么不均匀、多么突发都会先进入这个桶。恒定速率出水桶底部的出口会以恒定的速率比如每秒10个处理请求让流量变得平滑。桶满则溢如果进水速度持续大于出水速度桶里的水待处理请求就会累积。当累积的请求超过桶的容量桶的大小时新进来的请求就会被直接拒绝返回503错误或延迟处理。在Nginx配置中对应的关键指令是limit_req_zone和limit_req。limit_req_zone用来定义“桶”的规格。它指定了限流的维度比如按客户端IP$binary_remote_addr、桶的大小rate如10r/s表示每秒10个请求以及存储这些状态信息的内存区大小zone。limit_req在具体的location中应用定义好的“桶”规则并可以设置桶的容量burst和是否延迟处理nodelay。关键理解点rate定义了长期的、平均的出水速率。burst定义了桶的容量用于应对短暂的突发流量。如果没有burst那么任何超过rate的请求都会被立即拒绝缺乏弹性。有了burst在突发流量来时请求可以先在桶里排队等待处理而不是直接被拒这更符合实际业务场景。2.2 令牌桶与连接数限制ngx_http_limit_conn_module限制并发连接数这个模块限制的是同一时刻的并发连接数。它针对的是像文件下载、大API响应这种会长时间占用连接的场景。限制并发连接数可以有效防止少数客户端耗尽服务器的连接资源。它的工作原理类似“令牌桶”的变种。我们定义一个“区域”zone并为某个键如IP分配最大并发数。每个新连接到来时会尝试获取一个“令牌”增加计数。如果当前并发数未超限则允许建立连接如果已超限则拒绝连接。连接关闭时归还“令牌”减少计数。对应的指令是limit_conn_zone和limit_conn。limit_conn_zone定义限制的维度和共享内存区。例如按IP限制limit_conn_zone $binary_remote_addr zoneperip:10m;。limit_conn在location或server块中应用限制如limit_conn perip 5;表示每个IP同时最多只能有5个连接。重要区别limit_req限制的是请求的速率单位时间内的请求数一个连接内可以发起多个请求。limit_conn限制的是TCP连接的数量一个连接建立后无论其上有多少请求都只算一个连接。通常针对API或普通网页访问用limit_req更多针对下载站或视频流用limit_conn更有效。3. 实战配置从基础到生产级理论清楚了我们进入实战。假设我们有一个名为api.yourservice.com的域名后端是应用服务。我们要保护/api/下的所有接口。3.1 基础配置按IP限制普通API请求速率首先我们在Nginx的http块中定义限流规则这通常在/etc/nginx/nginx.conf或/etc/nginx/conf.d/下的某个通用配置文件中。http { # 定义一个名为“limit_per_ip”的共享内存区大小10MB用于存储每个客户端IP$binary_remote_addr的请求状态。 # 速率限制为平均每秒5个请求5r/s。注意这里是定义“桶”的规格。 limit_req_zone $binary_remote_addr zonelimit_per_ip:10m rate5r/s; # 可选定义一个限制并发连接的zone每个IP最多10个并发连接。 limit_conn_zone $binary_remote_addr zoneconn_per_ip:10m; server { listen 80; server_name api.yourservice.com; location /api/ { # 应用请求速率限制。 # zonelimit_per_ip: 使用上面定义的zone。 # burst10: 桶的容量为10。这意味着当请求速率超过5r/s时最多允许10个请求排队等待。 # nodelay: 对于排队中的请求burst范围内的立即处理而不是按rate的速率延迟处理。 # 整体效果对于单个IP能瞬时处理最多10burst1当前个请求之后超出速率5r/s的请求会被延迟或拒绝当burst也耗尽后。 limit_req zonelimit_per_ip burst10 nodelay; # 应用并发连接数限制每个IP最多5个并发连接。 limit_conn conn_per_ip 5; # 超过限制时的错误日志级别。推荐设为error级别避免日志过多。 limit_req_log_level error; limit_conn_log_level error; # 当请求被限流拒绝时返回的状态码。默认是503Service Unavailable。 limit_req_status 503; limit_conn_status 503; # 你的代理配置或其他处理逻辑 proxy_pass http://backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } }注意$binary_remote_addr是二进制格式的客户端IP比字符串格式的$remote_addr更节省内存。10m的内存区可以存储大约16万个独立IP的状态1010241024 / 64 ≈ 163840。你需要根据预估的独立IP数量进行调整。3.2 进阶配置应对复杂场景基础配置能解决大部分问题但生产环境更复杂。场景一针对特定高消耗接口进行更严格的限流假设/api/v1/download是一个大文件下载接口消耗资源大我们需要对它进行更严格的限制。http { limit_req_zone $binary_remote_addr zonelimit_api_general:10m rate10r/s; limit_req_zone $binary_remote_addr zonelimit_download:10m rate1r/s; # 下载接口限流更严 server { location /api/ { limit_req zonelimit_api_general burst20 nodelay; proxy_pass http://backend; } location /api/v1/download { # 此location会继承上一层的limit_req吗不会Nginx的limit_req指令在同一层级不继承这里会覆盖。 limit_req zonelimit_download burst5 nodelay; # 同时也可以加强连接数限制 limit_conn conn_per_ip 2; proxy_pass http://backend; } } }场景二按业务维度限流如用户ID、API Key按IP限流可能误伤共用出口IP的企业用户或网吧用户。我们可以尝试按更细的维度比如登录用户的ID。http { # 使用$http_authorization头中的token或者通过$arg_user_id获取参数。但更常见的是在后端验证后通过响应头传递用户标识给Nginx。 # 这里假设应用在验证后设置了一个自定义请求头 X-User-ID 传递给Nginx需要配置proxy_set_header。 # 注意这种方式要求限流判断在Nginx的后续阶段如$http_x_user_id且需要确保该头部的可信度。 map $http_x_user_id $limit_user_key { default $binary_remote_addr; # 默认降级为按IP限流 ~. $http_x_user_id; # 如果能获取到用户ID则以其为key } limit_req_zone $limit_user_key zonelimit_by_user:20m rate30r/s; }实操心得按用户ID限流更精准但实现也更复杂。一种更常见的生产模式是Nginx按IP做第一层粗粒度限流防止完全失控在应用内部或API网关层再根据用户ID或API Key做第二层细粒度限流。Nginx的limit_req_zone的key可以是任何变量这给了我们灵活性但也要注意变量的计算开销和内存占用。场景三白名单与灰度发布豁免管理后台、内部监控系统的IP需要加入白名单不受限流规则影响。我们可以使用Nginx的geo和map指令。http { # 定义白名单IP地址 geo $limit { default 1; 10.0.0.0/8 0; # 内网IP段 192.168.1.100 0; # 特定管理IP } map $limit $limit_key { 1 $binary_remote_addr; # 非白名单按IP限流 0 ; # 白名单key为空字符串空key不会被限流zone追踪 } limit_req_zone $limit_key zonelimit_all:10m rate10r/s; server { location /api/ { limit_req zonelimit_all burst20 nodelay; # 注意白名单IP对应的$limit_key为空不会消耗zone中的记录。 proxy_pass http://backend; } } }4. 高级话题与生产环境考量配置写好了丢到生产环境就完事了吗远不止如此。下面这些点是决定你的限流策略是否真正可靠的关键。4.1 分布式环境下的限流挑战Nginx的限流是基于单台服务器内存的。如果你有多台Nginx做负载均衡每台Nginx只能看到它自己接收的流量无法进行全局限流。例如你对一个IP限制10r/s但该IP的请求被轮询到5台Nginx上每台Nginx看到的是2r/s都不会触发限流但总和达到了10r/s实际上突破了限制。解决方案一致性哈希负载均衡通过$binary_remote_addr等关键信息做一致性哈希确保同一个客户端的请求总是落到同一台Nginx上。这样每台Nginx的限流对该客户端就是准确的。在upstream配置中使用hash指令。upstream backend { hash $binary_remote_addr consistent; server 10.0.0.1:8080; server 10.0.0.2:8080; }使用分布式限流中间件在Nginx之后引入像RedisLua脚本OpenResty、Sentinel、Envoy等支持分布式限流的组件。Nginx只负责代理限流逻辑由这些组件完成。这是更彻底但架构也更复杂的方案。分层限流在Nginx层做较宽松的、基于IP的限流第一道防线在应用层或网关层做更精确的、基于用户或业务的分布式限流第二道防线。4.2 性能调优与监控内存区大小zone size10m不是固定的。你需要估算。每个状态在64位系统上大约占128字节。公式所需内存 独立键值数量 * 128字节。如果预估有10万独立IP就需要大约12MB内存。设置过小Nginx会在错误日志中报错limiting requests, excess: ... zone...并可能拒绝所有请求设置过大则浪费内存。burst和nodelay的权衡burst5 nodelay允许瞬时处理最多6个请求之后严格按照rate排队/拒绝。适合对延迟敏感、允许短暂突发的API。burst5不带nodelay允许5个请求排队并且这5个请求会按照rate规定的速率如1个/秒延迟处理。这会使流量曲线非常平滑但会增加排队请求的延迟。适合需要绝对平滑流量的场景。监控与日志务必开启错误日志error_log并关注限流相关的条目。你可以通过分析日志中503状态码的数量和来源IP来观察限流是否生效以及是否误伤。更高级的做法是将Nginx的访问日志包含$limit_req_status变量接入ELK或PrometheusGrafana可视化限流情况。4.3 常见陷阱与排查技巧配置不生效检查模块使用nginx -V 21 | grep limit确认limit_req_module和limit_conn_module已编译。作用域错误limit_req_zone必须放在http块内。limit_req必须放在http,server,location块内且注意location的匹配优先级更具体的location会覆盖父级的规则。语法错误使用nginx -t测试配置文件语法。限流过于激进或宽松计算raterate5r/s不是指每秒只能处理5个请求。在burst和nodelay的配合下它能处理瞬时更高的流量。你需要根据API的压测结果和业务容忍度来调整rate和burst。一个经验公式burst可以设置为rate * 2到rate * 5用于吸收正常波动。区分静态和动态资源对静态资源如图片、CSS、JS的限流可以非常宽松甚至不做限制因为它们通常由Nginx直接处理开销小。重点限制动态API接口。limit_conn对HTTP/2和WebSocket的影响HTTP/2使用多路复用一个连接上可以并发多个请求。limit_conn限制的是物理连接数一个HTTP/2连接可能承载几十个并发请求这可能导致限制效果不如预期。对于HTTP/2limit_req限制请求速率通常更有效。WebSocket是长连接。一个WebSocket连接会一直占用一个limit_conn计数。你需要为WebSocket服务设置单独的、更高的limit_conn值或者将其排除在普通连接限制之外。日志中大量limiting requests但服务似乎正常 这可能是因为burst参数设置得比较大请求在排队等待没有立即被拒绝。检查limit_req_status是否为默认的503以及应用侧是否收到了大量延迟的请求。可以通过在访问日志格式中添加$request_time和$upstream_response_time来观察请求的延迟情况。5. 真实案例防御CC攻击与秒杀场景最后分享两个我亲身处理的案例看看限流如何在实际中发挥作用。案例一防御低速率CC攻击攻击者并不用海量IP发起DDoS而是用几百个代理IP每个IP以略高于正常用户但又不触发常规警报的速率比如每秒2-3个请求持续访问一个登录接口或搜索接口目的是耗尽后端数据库资源。我们的策略在Nginx层针对登录/api/login和搜索/api/search这两个关键接口设置比普通接口更严格的规则limit_req_zone $binary_remote_addr zonelimit_sensitive:10m rate2r/s;burst3 nodelay。同时启用limit_conn作为补充防止单个IP建立过多连接。配置日志监控对频繁返回503的IP进行自动分析并联动防火墙将确认的恶意IP加入黑名单。这套组合拳下来攻击者的请求大部分在Nginx层就被拦截或延迟后端负载显著下降。案例二秒杀活动限流秒杀开始瞬间流量洪峰可能达到平时的数百倍。我们的目标不是阻止所有超额请求而是确保系统不崩溃让一部分幸运的请求成功。我们的策略分层限流接入层Nginx设置一个非常宽松但必要的全局IP限流例如rate50r/sburst100。目的是防止极少数客户端用脚本疯狂刷单把流量洪峰稍微“削平”一点。网关层Spring Cloud Gateway / Sentinel实施更精确的限流。总量限流对整个秒杀服务设置每秒最大请求数如10000 QPS超过部分快速失败返回“活动太火爆”页面。用户限流对已登录用户每人每秒只能请求1次秒杀接口。应用层在业务代码中最后一道防线使用Redis分布式锁扣减库存确保最终一致性。Nginx在这一体系中扮演了第一道、也是最快速的防线角色它的高性能确保了在最前端就能丢弃大量非法或过载的请求保护了后端的网关和应用服务。限流不是一劳永逸的配置而是一个需要结合业务监控、不断观察和调整的策略。开始时可以设置得相对宽松通过监控系统观察流量模式和系统负载逐步收紧规则。记住好的限流策略用户几乎感知不到它只在系统最危险的时刻默默发挥作用保障了大多数用户的体验。