先看现象。一台静态资源站把图片目录挂在 NAS 的 NFS 上,nginx 1.18 默认 sendfile on,用户反馈部分图片打开是空白,curl 看返回 200 而 Content-Length 是 0,换个浏览器重试又能正常显示。

真正麻烦的地方在于,几个原因的现象几乎一样。内核 sendfile 直接从页缓存往 socket 送数据,NFS 上的文件属性有缓存,长度读到旧的。还有一种是文件刚写完属性还没回写,nginx 拿到的大小是 0,就按 0 字节发出去。

少数情况下是NFS 挂载参数 acregmin 默认几十秒,属性过期前所有进程看到的元数据都是旧的。

不容易想到的是,图片长期被 CDN 预热脚本批量写入,同一时刻既有读又有写,撞上的概率更高。

上来先把返回 200 且长度为零的请求筛出来

awk '$9==200 && $10==0 {print $7}' /var/log/nginx/access.log | head -20

再往下在 nginx 里关掉 sendfile 与 tcp_nopush

sed -i 's/sendfile on;/sendfile off; tcp_nopush off;/' /etc/nginx/nginx.conf && nginx -t && nginx -s reload

接着换成本地盘对比复现情况

rsync -a /mnt/nas/images/ /data/images/ && curl -sI http://127.0.0.1/images/logo.png | head -3

末了把 NFS 属性缓存时间调短再观察

mount -o remount,acregmin=3,acregmax=10 /mnt/nas

恢复的判定其实很直接。连续抽样一百张图片,Content-Length 与本地文件大小一致且没有零字节响应,access 日志里不再出现长度为 0 的 200。

补一句。关 sendfile 会多吃一次内存拷贝,静态站量大时优先把资源放本地盘,NFS 只做备份目录。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。

作者 9823189