Go 服务遇到 too many open files 怎么办:FD 增长定位、修复与回退
凌晨的 Go 网关日志里突然出现 accept4: too many open files,重启后请求就恢复正常,这个现象很容易把排查思路直接带向“把 ulimit -n 调大”的捷径上。更稳妥的做法是先确认进程的文件描述符(FD)是否持续增长,再把增长来源分成 TCP 连接、普通文件、管道和事件句柄几类,补全资源生命周期的关闭逻辑后复查曲线,最后才决定是否调整上限。
遇到这类报错不要直接把系统FD上限拉满,优先定位泄漏点补全资源关闭逻辑,确认资源曲线稳定后再按需调整上限,同时配套告警和回退方案。
- 先核对进程实际 FD 数量和软硬上限,别把 shell 的
ulimit当成服务进程的真实运行值。 - 用
/proc/和/fd lsof -p区分 socket、日志文件、管道与匿名 inode 不同类型的句柄。 - 网络连接场景重点检查响应体释放、空闲连接回收和异常关闭路径;文件与管道场景则要逐个确认每次打开后的错误分支有没有漏处理。
- 修复后要观察 FD 曲线是否回到稳定区间,临时上调上限必须提前留好回退方案和告警门槛。
先确认是进程本身耗尽,还是服务管理器没传对上限
同一台机器上,终端里执行 ulimit -n 得到的只是当前 shell 会话的限制。Go 服务如果由 systemd、容器或其他进程管理器拉起,实际生效的限制值要以进程自己的配置为准。先记下 PID 和采样时间,再做一次基线记录:
pid=$(pgrep -xo order-gateway)
printf 'pid=%s\n' "$pid"
cat "/proc/$pid/limits" | grep -i 'open files'
printf 'fd_count='
find "/proc/$pid/fd" -maxdepth 1 -type l | wc -l
如果 FD 数量已经接近软上限,但业务连接数没有同步增加,优先怀疑文件、管道或连接存在未释放的泄漏。如果当前上限只有 1024,而服务稳定运行时已经需要几千条长连接,则需要先评估整体容量,再在服务管理配置里有计划地提升,而不是只改当前终端的临时值。
用 /proc 和 lsof 把 FD 增长分成四类排查
只看 FD 总数值很难定位问题。连续采样两三次,每次间隔 30 秒,观察总数和各类型占比是否同步上涨。下面的命令会把进程打开的对象按类型列出来:
lsof -nP -p "$pid" | awk 'NR > 1 {print $5}' | sort | uniq -c | sort -nr
lsof -nP -a -p "$pid" -iTCP | sed -n '1,20p'
ls -l "/proc/$pid/fd" | sed -n '1,20p'
| 看到的对象 | 常见含义 | 下一步排查方向 |
|---|---|---|
| IPv4/IPv6 socket | 连接池配置、客户端响应体或长连接未正常释放 | 对照请求量、连接状态和代码关闭路径逐一核对 |
| 普通文件 | 日志切割、临时文件或配置读取后没关闭 | 按文件名聚合统计,回看打开文件的所有错误分支 |
| FIFO/pipe | 子进程、管道或日志转发通道没正常收尾 | 检查进程退出与管道关闭的先后顺序 |
| anon_inode | 事件通知、epoll 或 Go 运行时相关句柄 | 结合 goroutine 状态、连接数和重启前后差异综合判断 |

网络 socket 占比高时,先查关闭路径和连接复用逻辑
Go HTTP 客户端开发里最常见的误判是:请求已经返回,就认为 socket 会自动回收。对需要读取响应内容的请求,调用方必须主动关闭 resp.Body;如果只读取一部分内容就直接返回,还可能破坏连接复用逻辑。服务端长连接则要额外核对 keep-alive 配置、超时规则和客户端是否真的正常断开。
func fetch(ctx context.Context, client *http.Client, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { return nil, err }
resp, err := client.Do(req)
if err != nil { return nil, err }
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 2= http.StatusBadRequest {
return nil, fmt.Errorf("upstream status %d", resp.StatusCode)
}
return body, nil
}
这里的检查点不是“加一行 Close 调用就结束”。可以用 pprof 看 goroutine 是否卡在下游读写环节,再用 lsof 持续观察 socket 数量变化。如果响应体已经正确关闭,但大量连接长期处于 CLOSE_WAIT,要继续排查服务端或代理的断开通知机制;如果是 ESTABLISHED 数值持续上涨,则重点关注连接池上限和请求超时配置。
普通文件和管道泄漏,要盯住异常错误分支
文件泄漏往往不是主流程造成的,而是“打开文件后,后续参数校验失败”这条分支忘了关闭资源。把关闭动作放在确认打开成功之后、紧邻打开操作的位置,代码评审时就能很清晰看出资源归属:
func readConfig(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil { return nil, err }
defer file.Close()
return io.ReadAll(io.LimitReader(file, 1
不要用“每次定时任务结束时统一清理”的逻辑替代局部关闭操作。统一清理通常意味着资源会在整个周期内持续堆积,遇到并发任务场景就更难判断是哪个逻辑留下的泄漏。如果服务会调用外部命令或 sidecar 组件,还要记录子进程 PID、退出状态和 pipe 关闭动作,避免只关闭管道一端。
修复后怎么复查,什么场景下允许上调上限
修复代码并重启服务后,先用相同的采样命令观察 10 到 15 分钟。理想结果不是 FD 数值立刻降到很低,而是流量回到正常水平后曲线进入平稳区间,没有每分钟固定上涨的斜率。
for i in 1 2 3 4 5; do
date '+%H:%M:%S'
find "/proc/$pid/fd" -maxdepth 1 -type l | wc -l
sleep 30
done
临时提升上限可以给问题修复争取缓冲时间,但必须同时配套设置告警规则。比如软上限设为 65536 时,在使用率到 70% 时触发提醒、85% 时进入人工确认流程;如果 FD 还在持续上涨,不要用更大的上限数字掩盖泄漏问题。回退时恢复 systemd 的 LimitNOFILE 或容器的对应限制配置,并确认滚动重启后的新进程继承了预期的限制值。

把这次故障变成可复用的告警和复盘项
监控里至少要保留进程 FD 使用量、软上限占比、TCP 连接状态和请求错误率四条核心曲线。单独监控 too many open files 的日志通常已经太晚,比例类告警能在 accept 失败之前就给出足够的缓冲时间。
复盘时把“谁打开、谁关闭、关闭失败怎么兜底”的规则写到对应接口或函数的责任边界里;新增 HTTP 客户端、日志切割器和子进程管道逻辑时,补一个高并发或重复调用场景的测试用例。这样下次看到 FD 上涨,直接按对象分类和采样曲线排查,不必从重启服务开始兜圈子。
相关问题
只调大 ulimit 能彻底解决 too many open files 吗?
不能。它只能扩大可用额度,不能修复资源持续泄漏的问题。只有在确认 FD 曲线稳定、业务确实需要更多长连接时,才适合配合容量评估做调整。
为什么 Go 服务重启后问题会暂时消失?
重启会关闭进程持有的所有文件、socket 和管道,所以计数会直接归零;如果代码逻辑没有改动,流量恢复后泄漏会再次出现。
lsof 没装时还能怎么查?
可以直接读取 /proc/ 和 /proc/,先确认数量与对象类型,再决定是否补装诊断工具。
小结
处理 too many open files 的关键不是记住某个固定上限数字,而是建立“进程限制 → FD 类型 → 代码关闭点 → 修复后曲线验证”的完整证据链。把上限调整作为带告警、带回退的容量操作,才能避免服务每隔几天就得靠重启续命。
Go context.WithCancelCause 实战:并发任务链如何传递真正的失败原因
- 上一篇
- Go context.WithCancelCause 实战:并发任务链如何传递真正的失败原因
- 下一篇
- Go JSON 请求体过大怎么拦:MaxBytesReader、413 和连接复查
-
- Golang · Go问答 | 6分钟前 |
- Go os.ProcessState.SysUsage 为什么不同系统结果不同
- 135浏览 收藏
-
- Golang · Go问答 | 10分钟前 |
- Go url.PathUnescape 遇到不完整百分号编码怎样处理
- 426浏览 收藏
-
- Golang · Go问答 | 12分钟前 |
- Go HTTP 服务端为什么读不到请求体第二次内容
- 232浏览 收藏
-
- Golang · Go问答 | 13分钟前 |
- Go ServeMux 路由冲突怎样定位注册顺序
- 149浏览 收藏
-
- Golang · Go问答 | 14分钟前 |
- Go time.Time.IsZero 为什么不等于 Unix 时间戳为 0
- 278浏览 收藏
-
- Golang · Go问答 | 20分钟前 |
- Go context 超时了为什么下游请求还没停
- 490浏览 收藏
-
- Golang · Go问答 | 20分钟前 |
- Go goroutine 为什么退出后 channel 仍然阻塞
- 234浏览 收藏
-
- Golang · Go问答 | 21分钟前 |
- Go defer 在循环里为什么没有及时关闭文件
- 289浏览 收藏
-
- Golang · Go问答 | 23分钟前 | 接口 · 标准库 · IO · 性能 · Go问答 · Go Writer 字符串写入 WriteString io.StringWriter io.WriteString
- Go io.StringWriter 怎样让类型支持高效写入字符串
- 421浏览 收藏
-
- Golang · Go问答 | 35分钟前 | 切片 · 标准库 · 性能 · 编码 · Go问答 · Go 文本格式化 encoding.TextAppender AppendText TextMarshaler 低分配
- Go encoding.TextAppender 怎样减少文本格式化分配
- 342浏览 收藏
-
- Golang · Go问答 | 39分钟前 |
- Go slices.Collect 空迭代器返回 nil 的接口处理方案
- 384浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 594次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 633次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 666次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 633次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 450次使用
-
- 新能源汽车充电设施巡检记录的设置方法
- 2026-10-04 422浏览
-
- Golang实现HTTP编程请求和响应
- 2022-12-28 101浏览
-
- golangNewRequest/gorequest实现http请求的示例代码
- 2023-01-24 343浏览
-
- 一文详解Golang中net/http包的实现原理
- 2022-12-29 419浏览
-
- 快速掌握Go语言HTTP标准库的实现方法
- 2022-12-30 327浏览

