在Nginx里配置日志、限流、降级和熔断,并不是把几个模块单独打开就完事。它们之间需要形成一条完整的处理链条:先通过日志记录真实请求状态,再用限流挡住突发流量,当后端出现异常时迅速降级或熔断,并且每一次拦截都要能在日志里被区分出来。否则线上出现大量503时,你很难判断到底是限流触发、降级返回,还是上游服务已经不可用。

一、Nginx日志记录:先让每一次请求都有据可查
默认的access_log格式通常只包含时间、请求行、状态码和User-Agent,用来做基础访问统计够用,但排查限流和降级问题时缺少几个关键字段:上游节点地址、上游响应状态、上游耗时,以及本次请求是否被限流。自定义log_format是第一步。下面这段配置在日志中加入了请求耗时、上游地址和上游状态码,后续无论是分析慢请求还是确认哪个上游节点返回错误,都能直接从日志里拿到证据。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'rt=$request_time ua=$upstream_addr us=$upstream_status urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
日志落盘时建议开启缓冲,避免每次请求都直接写磁盘拖慢Nginx。access_log指令支持buffer和flush参数,例如access_log /var/log/nginx/access.log main buffer=64k flush=5s。这样日志先写入内存,累积到64k或者超过5秒才刷盘,能在高并发下减少IO压力。对于一些静态资源请求,可以在location中单独关闭访问日志,只保留API和动态请求的记录,避免日志被图片和CSS请求淹没。
除了访问日志,error_log也需要关注。limit_req模块触发限流时会在error_log里写入limiting requests,excess等信息;上游连接失败则会记录connect() failed或upstream timed out。给error_log设置warn或error级别,并把这些关键字接入监控,可以在问题扩大前收到告警。
二、限流配置:用limit_req和limit_conn控制请求速率
limit_req_zone基于共享内存定义限流维度,通常按客户端IP或者接口路径来限制。比如一分钟内对一个API最多允许60次请求,平均每秒1次,但瞬时流量不可能完全均匀,所以还需要burst参数给一个缓冲队列。下面的配置限制每个IP访问/api/的速率为10r/s,允许20个突发请求,并且突发部分不做延迟处理。
http {
limit_req_zone $binary_remote_addr zone=api_req:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_req burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
burst和nodelay需要根据实际业务调整。如果去掉nodelay,burst内的请求会排队等待,虽然不会直接拒绝,但请求耗时会明显增加;如果保留nodelay,超过rate但不超过burst的请求立即转发,超过burst才返回429或503。用429状态码比503更符合HTTP语义,因为503通常表示服务端不可用,而429明确表示客户端请求过于频繁,也方便在日志系统中与真正的服务故障区分开。
除了请求频率,并发连接数也需要限制。limit_conn_zone和limit_conn可以防止单个IP建立过多连接,把服务器连接资源占满。配合limit_req一起使用,一个控制连接数,一个控制请求速率,能让边缘保护更稳定。实际配置中,建议把limit_conn放在server级别,把limit_req放在具体location中,避免全局限流影响正常页面访问。
三、降级与熔断:从快速返回兜底内容到摘除故障节点
降级的核心思路是:当后端服务不可用或者响应异常时,Nginx不再继续等待或转发,而是直接返回一个预设的降级响应。可以先开启proxy_intercept_errors,让Nginx拦截上游返回的502、504等错误,再通过error_page把请求交给一个内部降级location。下面这个例子中,如果后端返回502或504,客户端会收到一个JSON格式的降级提示,而不是Nginx默认的HTML错误页。
location /api/ {
proxy_pass http://backend;
proxy_intercept_errors on;
error_page 502 504 = @fallback;
}
location @fallback {
default_type application/json;
return 503 '{"code":503,"msg":"service temporarily unavailable"}';
}
熔断则比被动降级更主动,它要求Nginx在一段时间内持续观察上游节点的失败情况,达到阈值后暂时停止把请求转发给该节点。Nginx开源版内置的max_fails和fail_timeout可以实现基础熔断:如果一个节点在fail_timeout时间内失败次数达到max_fails,该节点会被暂时摘除,新请求不再发过去,直到超时后重新探测。下面的upstream配置中,只要某个上游连续失败3次,30秒内就不会再接收请求。
upstream backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
keepalive 32;
}
实际使用中需要注意,max_fails统计的是TCP连接或请求失败,不区分具体HTTP状态码。如果后端返回500但连接正常,默认不一定触发熔断。对于需要按错误码或响应时间做熔断的场景,可以引入OpenResty的lua_shared_dict手动计数,在access_by_lua阶段判断失败率和阈值,把熔断逻辑做得更细粒度。不过对于大多数中小规模服务,max_fails加上主动健康检查已经足够挡住明显的节点故障。
四、串联记录:让限流、降级、熔断在日志里清晰可辨
把日志、限流、降级和熔断放到同一个配置里,最需要解决的是事件区分问题。限流返回429,降级和熔断都可能返回503,如果只看状态码很难分辨。一种简单做法是给不同触发点设置不同的状态码或响应头,比如限流统一返回429,降级返回503并在响应体里带上特定标识,熔断事件则通过upstream_status和upstream_addr为空或为不可用节点来判断。
下面是一份整合配置,在/api/路径上启用自定义日志格式、按IP限流、上游被动熔断以及错误降级。其中access_log使用了前面定义的main格式,既记录了请求耗时,也记录了上游状态和地址,后续可以直接用awk或日志平台按$status、$upstream_addr这些字段聚合分析。
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent rt=$request_time '
'ua=$upstream_addr us=$upstream_status urt=$upstream_response_time';
limit_req_zone $binary_remote_addr zone=api_req:10m rate=20r/s;
upstream backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 80;
access_log /var/log/nginx/api_access.log main buffer=64k flush=5s;
location /api/ {
limit_req zone=api_req burst=30 nodelay;
limit_req_status 429;
proxy_pass http://backend;
proxy_intercept_errors on;
error_page 502 504 = @fallback;
}
location @fallback {
default_type application/json;
return 503 '{"code":503,"msg":"service temporarily unavailable"}';
}
}
}
在监控侧,可以定期统计api_access.log中各状态码占比。429数量突然上升说明限流正在起效,503伴随upstream_status为502或504则说明降级或熔断已触发。把error_log中的limiting requests、upstream timed out等关键字接入告警,就能在服务压力变大时及时收到通知。Nginx这一层做得再完善,也需要配合日志采集和告警规则,才能把记录变成真正的可观测性。