日本服务器部署Web服务如何优化:从静态缓存、压缩到上游超时设置

在日本服务器上部署 Web 服务时,页面慢、偶发 504 和静态文件更新不生效,往往不是同一个问题。判断时不要先盲目增加超时时间:如果静态文件的首字节响应很快但下载时间长,优先检查压缩和缓存;如果动态请求的 upstream_response_time 持续升高,重点应放在上游应用处理;如果大量请求停留在连接阶段,则需要查看连接复用、文件描述符和监听状态。
现场排查可以按这个顺序进行:先区分静态请求与动态请求,再检查连接和响应计时;随后核对静态缓存头,确认压缩是否只作用于适合压缩的文本内容;最后根据上游连接、发送、读取三个阶段分别调整超时。每次只改一类参数,使用 nginx -t 校验后平滑加载,并通过响应头、访问日志和重复请求验证结果。
先从现象判断故障范围
下面的判断适用于常见的 Nginx 反向代理场景。实际判断应以访问日志中的时间字段和响应头为准,而不是只看浏览器体感。
| 现象 | 优先怀疑的原因 | 首个核验点 |
|---|---|---|
| CSS、JavaScript、字体每次都重新下载 | 缺少 Cache-Control、资源 URL 未版本化、缓存头被上游覆盖 | curl -I 查看缓存头和 ETag |
| 静态文件首字节很快,但文件下载时间较长 | 文本资源未压缩、压缩级别过高或客户端未发送压缩能力 | 查看 Content-Encoding、Vary 和实际下载大小 |
| 动态请求返回 502 | 上游端口不可达、上游进程提前断开或配置的上游地址错误 | 查看 upstream_status、upstream_connect_time 和 Nginx 错误日志 |
| 动态请求返回 504 | 上游连接或读取阶段超过设定时间 | 区分 connect_time、header_time、response_time |
| 只有慢客户端请求容易中断 | 客户端发送或接收速度低,send_timeout 等参数过短 | 对比 request_time 与 upstream_response_time |
| 修改了资源但用户仍看到旧内容 | URL 未变化且浏览器或中间缓存仍按旧的长期缓存策略处理 | 检查 URL 指纹、响应头和强制重新验证结果 |
这里的“超时”不是一个统一参数。proxy_connect_timeout 控制 Nginx 建立上游连接的等待时间,proxy_send_timeout 控制向上游发送请求时相邻写操作之间的等待时间,proxy_read_timeout 控制读取上游响应时相邻读操作之间的等待时间,send_timeout 则作用于向客户端发送响应的阶段。把它们全部调大,可能只是让故障更晚暴露。
修改前先拿到可对比的数据
确认软件和配置位置
以下命令适用于使用 systemd 管理 Nginx 的 Linux 系统。命令本身只读取状态,不会修改服务:
nginx -V 2>&1 | head -n 5
systemctl status nginx --no-pager
nginx -t
如果服务名称不是 nginx,先通过系统已有的服务列表确认实际名称,不要直接猜测。修改前应备份实际生效的配置文件或独立配置片段。备份路径和配置路径必须以 nginx -V 输出的 --conf-path 为准。
配置变更的基本回滚方式是:恢复变更前的备份文件,先执行 nginx -t,确认语法通过后再执行平滑加载。恢复前应保留当前文件,避免把一次错误覆盖变成无法追溯的状态。配置加载失败时,不要直接重启服务,先修正语法或恢复上一版。
分别测试静态和动态请求
使用实际域名和实际资源路径替换示例中的占位符:
curl -sS -D - -o /dev/null \
--compressed \
'https://你的域名/assets/app.版本号.js'
curl -sS -o /dev/null \
-w 'code=%{http_code}\nconnect=%{time_connect}\nstarttransfer=%{time_starttransfer}\ntotal=%{time_total}\nsize=%{size_download}\n' \
'https://你的域名/动态请求路径'
重点观察:
http_code:确认是否出现 4xx、502 或 504。time_connect:客户端到 Web 服务入口建立连接的耗时。time_starttransfer:收到首字节前的等待时间,通常包含入口处理和上游等待。time_total:包括响应传输在内的总耗时。size_download:结合是否启用压缩判断传输大小变化。
同一个请求至少重复测试几次,并分别测试静态资源、普通页面和确实会访问上游的动态接口。单次请求不能代表稳定状态,也不能据此直接提高全部超时参数。
先处理连接和响应阶段
连接参数的目标不是让连接保持得越久越好,而是在复用连接、连接占用和客户端体验之间取得平衡。
客户端连接复用
在 Nginx 的 http {} 配置上下文中,可以使用类似设置:
http {
keepalive_timeout 15s;
keepalive_requests 1000;
send_timeout 30s;
# 其他 http 级别配置
}
这组数值只是可用于测试的起点,不是适用于所有日本服务器的固定答案:
keepalive_timeout过短,会增加重复建立连接的次数;过长,则会让大量空闲连接占用资源。keepalive_requests过低,连接复用次数有限;过高时应结合单个连接的请求量和资源占用观察。send_timeout不是整个响应的总耗时限制,而是向客户端发送数据时相邻写操作之间的等待限制。慢客户端较多时,设置过短可能导致传输中断;设置过长则可能积累长期占用的连接。
如果怀疑连接数或文件描述符不足,可先执行:
ss -s
ss -lnt
再查看 Nginx 错误日志中是否存在连接耗尽、打开文件失败或监听队列异常等信息。只有在日志和系统状态能够证明连接资源不足时,才考虑检查 worker_connections、进程文件描述符上限等参数。单纯提高 worker_connections 并不会提升上游应用的处理能力,还可能受到系统文件描述符限制。
区分入口连接慢和上游响应慢
建议在 Nginx 的 http {} 上下文中加入带时间字段的访问日志。已有日志格式时,应在备份后增加新格式,不要直接删除原有字段:
http {
log_format timing
'$remote_addr - $host [$time_local] '
'"$request" $status $body_bytes_sent '
'request_time=$request_time '
'upstream_status=$upstream_status '
'upstream_connect_time=$upstream_connect_time '
'upstream_header_time=$upstream_header_time '
'upstream_response_time=$upstream_response_time';
access_log /var/log/nginx/access.log timing;
}
这些字段的含义如下:
request_time:Nginx 从接收请求到完成响应的总时间。upstream_connect_time:建立上游连接所用的时间。upstream_header_time:从连接上游到收到上游响应头的时间。upstream_response_time:从连接上游到读取完上游响应的时间,具体格式可能因多个上游尝试而出现多个值。upstream_status:上游返回的状态,若请求没有到达上游,可能为空。
如果 upstream_connect_time 很低,但 upstream_header_time 和 upstream_response_time 较高,问题更接近上游处理或上游等待。若上游时间很低但 request_time 明显更高,应继续检查响应发送、客户端接收速度和 Nginx 输出缓冲,而不是继续增加 proxy_read_timeout。
静态资源缓存要与更新方式匹配
缓存优化首先要解决“哪些资源可以长期缓存”,而不是给所有路径统一设置很长的有效期。
带版本号的资源使用长期缓存
如果 CSS、JavaScript、字体和图片的 URL 会随内容变化,例如:
/assets/app.8f31c.js
/assets/site.20260927.css
可以在 server {} 内使用独立的静态资源位置:
server {
listen 80;
server_name 你的域名;
root /var/www/site/public;
location ^~ /assets/ {
try_files $uri =404;
add_header Cache-Control "public, max-age=604800, immutable";
etag on;
}
}
这里的关键是资源内容变化时必须同步改变 URL。immutable 表示在有效期内客户端不必反复验证资源;如果部署时仍然覆盖同一个文件名,旧内容可能继续被使用,此时不应随意添加 immutable。
try_files $uri =404 可以避免静态资源不存在时被错误地转发到动态入口。对于资源路径,返回明确的 404 通常比让应用生成一个 HTML 页面更容易诊断,也避免把错误页面当作脚本或样式返回。
HTML 和用户相关内容不要照搬静态策略
页面入口和包含登录状态、权限信息或用户数据的响应,不应直接套用长期公共缓存。常见做法是:
- 内容不应落入缓存:使用适合业务的
private或no-store策略。 - 内容允许缓存但必须先确认是否变化:使用
no-cache,它表示使用前重新验证,并不等于完全不缓存。 - 内容可以按版本长期缓存:确保 URL、文件内容和发布流程严格绑定。
如果上游已经返回了 Cache-Control、ETag 或 Last-Modified,先确认 Nginx 是否重复添加了相互冲突的响应头。add_header 在不同配置层级存在继承规则,新增位置级别的 add_header 可能改变原有头部继承结果,不能只看配置片段而忽略最终响应。
验证缓存是否真的生效
检查响应头:
curl -sS -D - -o /dev/null \
'https://你的域名/assets/app.版本号.js'
重点确认:
- 是否存在预期的
Cache-Control。 - 是否存在
ETag或Last-Modified。 - 资源 URL 不变时,内容更新是否会导致旧缓存继续命中。
- 首次请求和带条件请求的状态是否符合预期,例如客户端携带
If-None-Match后是否返回 304。
如果修改了文件但内容没有变化,优先检查资源 URL 是否仍然相同、浏览器是否保留旧缓存、响应头是否被上游覆盖。不要仅通过清空服务器目录或强制重启服务来处理缓存问题,那通常不能解决客户端已经保存的旧响应。
压缩只作用于适合压缩的响应
文本类资源通常适合压缩,已经压缩过的图片、音频、视频和归档文件再次压缩,通常只会增加 CPU 消耗和处理时间。Nginx 的压缩配置可以放在 http {} 上下文:
http {
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_types
text/plain
text/css
application/javascript
application/json
application/xml
image/svg+xml;
}
需要注意几个判断点:
gzip_vary on会让响应带上Vary: Accept-Encoding,使不同压缩能力的客户端得到正确版本。gzip_min_length用于避免对很小的响应增加压缩开销,具体阈值应根据实际资源分布调整。gzip_comp_level越高不代表页面一定更快,因为压缩端 CPU 消耗会增加。应通过time_starttransfer、CPU 使用情况和响应大小共同判断。gzip_types按响应的Content-Type匹配,不是按文件扩展名猜测。上游错误设置 MIME 类型时,即使文件本身是文本,也可能没有压缩。- 浏览器或客户端未发送
Accept-Encoding时,Nginx 不应强制返回压缩内容。
压缩测试应使用实际 GET 请求:
curl -sS --compressed \
-D /tmp/headers.txt \
-o /dev/null \
'https://你的域名/assets/app.版本号.js'
grep -Ei '^(HTTP/|Content-Encoding:|Content-Length:|Transfer-Encoding:|Vary:)' \
/tmp/headers.txt
如果出现 Content-Encoding: gzip,说明客户端和服务端完成了压缩协商。若没有该字段,应检查客户端请求头、响应 Content-Type、Nginx 配置上下文以及上游是否已经设置了不允许压缩的响应头。
对流式输出、长连接响应或需要尽快逐段发送的数据,不要全局套用相同的压缩策略。压缩可能改变数据发送节奏,应该为这类路径单独验证,而不是只看普通 HTML 或 JavaScript 的结果。
上游超时要按阶段设置
先确认上游是否真的可用
假设应用监听在本机某个端口,先查看监听状态:
ss -lntp
如果应用提供了明确的健康检查路径,可以从日本服务器本机发起请求:
curl -sS -D - -o /dev/null \
'http://127.0.0.1:8080/实际健康检查路径'
只有在该路径确实存在、端口和协议确认无误时,才使用这条命令。连接被拒绝通常意味着端口没有监听或监听地址不匹配;连接成功但响应迟缓,则应查看上游应用自身的处理日志。不要把“本机端口可连接”误判成“所有业务请求都正常”。
为不同阶段设置独立参数
在 server {} 的动态位置中,可以使用如下示例:
upstream app_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name 你的域名;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
send_timeout 30s;
proxy_buffering on;
proxy_pass http://app_backend;
}
}
这段配置需要根据现有站点的监听方式、上游端口和域名替换占位符。示例中的时间值只适合作为初始验证值,不能当作业务 SLA,也不应未经测试直接用于所有接口。
各参数的调整依据如下:
proxy_connect_timeout过期:优先查上游端口、监听地址、进程负载和连接资源。提高这个值不能修复端口错误或上游未启动。proxy_send_timeout过期:可能是向上游发送请求时对方读取缓慢,也可能是请求体较大或上游处理连接异常。需要结合请求大小和上游日志判断。proxy_read_timeout过期:常见于上游长时间没有返回数据。应先确认是业务计算确实需要较长时间,还是上游卡住、等待资源或发生阻塞。send_timeout过期:更接近向客户端发送响应时的问题,不代表上游一定慢。
如果只有某一个长耗时接口确实需要更长的等待时间,应为该接口单独设置更长的 proxy_read_timeout,不要全局放大所有动态请求的等待窗口。对于持续输出或流式接口,还要单独评估 proxy_buffering;关闭缓冲可能改变响应行为并增加连接占用,只能在确认接口特性后使用。
用状态码和时间字段区分 502、504
- 502:重点检查上游地址、端口、协议、进程是否提前关闭连接,以及 Nginx 与上游之间的请求头或响应格式是否异常。
- 504:重点对照
upstream_connect_time、upstream_header_time和upstream_response_time。连接阶段超时与应用处理阶段超时,修复方向不同。 - 200 但页面仍慢:可能是上游已经快速返回,但响应体发送给客户端较慢,或者页面继续加载了大量未缓存、未压缩的资源。
- 部分请求快、部分请求慢:不要只看平均值,应按照接口、资源路径和状态码分别检索日志,确认是否只有特定请求触发慢路径。
一次变更后的验证和回滚
配置修改后,先校验,再加载:
nginx -t
sudo systemctl reload nginx
reload 前应确认当前服务由该 systemd 单元管理,并已经完成配置备份。加载后马上检查错误日志和新请求的访问日志。如果 nginx -t 失败,不要执行 reload;如果 reload 后出现大量新错误,应恢复上一版配置,重新执行 nginx -t,确认通过后再加载。
建议按以下顺序验证:
- 先看状态码:确认静态资源没有从 200 变成 404,动态请求没有新增 502 或 504。
- 再看响应头:检查静态资源的
Cache-Control、ETag、Content-Encoding和Vary是否符合预期。 - 再看计时字段:比较变更前后的
upstream_connect_time、upstream_header_time、upstream_response_time和request_time。 - 最后测重复请求:用相同 URL、相同请求头重复访问,确认缓存命中、条件请求和压缩协商没有互相冲突。
- 观察一段实际流量:确认错误不是只在测试请求中消失,而是没有出现连接数持续增长、上游排队增加或客户端传输中断。
最容易遗漏的是“配置已经改变,但实际响应没有改变”。这通常与配置没有加载、请求命中了另一个 server、位置匹配顺序不符合预期、上游覆盖了响应头,或客户端仍在使用旧缓存有关。最终应以 nginx -t、新产生的访问日志和真实响应头三者相互对应为准,而不是只看配置文件中是否出现了某个参数。