首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >运维之nginx sendfile 0拷贝报错文件句柄不存在

运维之nginx sendfile 0拷贝报错文件句柄不存在

作者头像
SRE运维实践
发布于 2025-06-13 06:39:56
发布于 2025-06-13 06:39:56
6300
代码可运行
举报
文章被收录于专栏:SRE运维实践SRE运维实践
运行总次数:0
代码可运行

序言

架构变动,组织调整,人事地震,都是神仙在斗法,哪有那么多的新花招,不过是新来一批要分蛋糕的人,怎么占领地盘而已。

一个东西再熟悉,接触的场景多了,也会有解决不了的问题,而且可能解决问题的时间越来越长,例如在运维的时候,出现延迟抖动,例如在配置的时候,出现一些莫名其妙的报错。

nginx在sse场景下的报错

1 背景

在某天运维的时候,突然收到一个故障,在nginx代理进行上传一个文件的时候,文件稍微大点在十几M的时候,就会出现error_http2_PROTOCOL,但是在小文件上传的时候,就不会有任何问题,粗略一看,还以为是证书问题,但是仔细一听,小文件又没有问题。

2 nginx woker oom

这个报错也算是比较熟悉了,一般在上传文件的时候,如果占用的内存过多未释放,那么就基本上判定是内存导致的oom,因为nginx是运行在容器中,内存是有限制,从而有可能出现woker process被杀死,从而出现这个问题,但是检查了一下错误日志,里面并没有发现process exited的字样,看了下监控,也没出现woker process oom,从而排除此问题。

查看error log之后,发现里面有具体的报错信息:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sendfile() failed(9 bad file descriptor) while sending 
request to upstream

3 sendfile报错

根据这个报错信息,基本上判定了是和sendfile参数有关,sendfile参数主要是用来优化提高nginx的性能的,默认基本上都会打开,但是这个的优化主要是为了优化静态文件的传输,从而直接0拷贝,nginx发送系统调用sendfile直接从内核中将数据发送给网卡,减少用户空间到内核空间的拷贝。

这是一个上传请求,post请求,并且body比较大,而且nginx开启了proxy_request_buffering,也就是会将body进行临时存储到磁盘中,在这种情况下,才会触发sendfile的调用,否则动态请求是不会用sendfile调用的。

4 修改参数查看对应的报错信息

首先构造了客户端对应的curl请求,从而能更好的模拟客户端的报错信息,在查看curl的报错的时候,报错如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
curl: (18)transfer closed with outstanding read data remaining

报错信息表示在读取数据的时候,传输通道被关闭了。

禁止sendfile调用,配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sendfile off;

再次进行请求,发现一切变得正常。

关闭客户端的临时落盘,配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
proxy_request_buffering off;

再次进行请求,发现一切也会变得正常,此时虽然开启了sendfile配置,但是在日志中查看,实际上没有进行sendfile调用的,直接客户端和服务端进行交互。

5 搞不清楚为啥,问问AI

现在的AI那么多,有chatgpt,有aws的,都问了一遍,发现都是牛头不对马嘴,例如说要检查文件目录的权限,检查临时文件的权限,emmm,AI也是一个搜索,必须根据你的关键词才能回答到正确的地方。

在使用curl请求的时候,发现这个请求会有不停的响应,一问才知道这是一个SSE的场景,也就是和websocket差不多,服务端会主动推送信息到客户端中,从而再次问AI,会答的就基本差不多了。

主要报错的原因是:sendfile()的异步执行特性与nginx的同步资源管理机制产生竞态条件,在SSE长连接的场景中,临时文件的描述符过早被关闭。

5 验证

虽然AI已经给出了答案,但是并不一定可靠,瞎说的AI见得太多了,能编造出花里胡哨的答案,从而如何进行验证也是一个问题。

重新编译openresty,将--with-debug模块加入到其中,从而使用debug的日志信息来查看对应的日志,实际上看不到明显的东西,只能看到sendfile报错。

使用streace -TTTf -p pid,追踪对应的系统调用,主要在查看对应句柄的打开与关闭中,会发现有其中的状态。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
接受到upstream发过来的SSE响应
close关闭临时文件句柄,其实这个时候就是nginx提前关闭了连接
nginx发送响应给客户端(access 日志记录的是200响应)
sendfile继续将临时文件发送给upstream,但是临时文件被删除
写入日志alert 信息
关闭upstream连接

找一个干净的环境,进行debug调试,否则debug日志太多,你根本无法分辨对应的信息,从而也就证明了,临时文件被提前删除,从而导致了sendfile失败,主要还是因为sendfile是异步的,nginx无法感知这个已经结束了,本来这个临时文件的清理工作是在请求结束之后再清理的,但是在这个SSE长连接场景中,nginx会收到响应,从而误认为这个请求结束了,进行关闭了临时文件,这是一种竞争关系,什么时候清理生成的临时文件,也是一个考验。

在同时,也进行了一下抓包,在抓包文件中,当upstream发送信息的时候,nginx会不断地发送reset,重置连接,也就是关闭了连接。谁发送reset包,大概率情况下就不是一个好人了。

在其中,其实临时文件不被删除,也不能解决这个问题,使用配置:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
client_body_in_single_buffer on;
client_body_in_file_only on;

启用这两个配置之后,临时文件会一直保存在对应的目录中,也可以用来调试调试。

其实还有一种解法,就是在nginx后端再弄个网关,网关一般的配置都是禁用了proxy_request_buffering和proxy_buffering,从而也能完美的解决这个问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SRE运维实践 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档