golang 服务器处理上传时,如果 form 文件过大,居然会往/tmp 目录写文件,要怎么优雅的处理呢?

17 天前
 zihuyishi

我用的 gin 库,发现如果上传文件较大(大于 10mb ),就会往系统/tmp 目录写文件,然而由于是运行在容器里,一般也不会去清理/tmp ,导致容器占用空间越来越大。写了好几年 golang 了,第一次注意到这个问题。 我查了下文档,基本上需要自己去主动调用 MultipartForm 的 RemoveAll 方法

	if c.Request.MultipartForm != nil {
		defer c.Request.MultipartForm.RemoveAll()
	}

主要想吐槽的是,写了这么久 go ,好像从来没在任何教程或者 example 里看到有人调用这个方法..而且 gin 为啥自己不去主动调用呢,还是说其实有更优雅的写法,但是我不知道?

2600 次点击
所在节点    Go 编程语言
38 条回复
julyclyde
17 天前
定期重启一下
wunonglin
17 天前
服务本身不处理,丢给 s3 去存
hingle
17 天前
zihuyishi
17 天前
@wunonglin 本来就是传给 s3 的,但是 golang 的 multipart-form 在文件大于一个值时默认行为就是往/tmp 目录下写文件。如果没主动去调用 RemeveAll 他居然就留着/tmp 下文件不管了...
zihuyishi
17 天前
@hingle 好像还真是,所以归根到底问题是 gin 框架的?他自己没有调用 RemoveAll?
hingle
17 天前
@zihuyishi gin 用的就是标准库的 http.Server ,会调用 RemoveAll 的啊

是不是你客户端那边一直没断开连接?或者出现一些异常导致没发送 FIN 之类的。
zihuyishi
17 天前
@hingle 我在 debug/pprof 里面看链接都断开了呀,而且上传请求也都是正常的返回,难道有些 middleware 会改变一些默认行为?
nicoljiang
17 天前
不考虑直传给 s3 吗(中转各方面也不划算)?
zsj1029
17 天前
流处理可不可以,哦对了,form 表单好像不行
gaeco
16 天前
python 也这样吧
FrankAdler
16 天前
如果你能预估出来上传的文件大小范围,而且内存充足,可以调大 MaxMultipartMemory ,可以减少临时文件使用,默认是 32MB 。
bv
16 天前
1. 这一块是标准库实现的逻辑,当 HTTP 请求响应结束后,会自动删除临时文件,无需手动调用 RemoveAll 。
2. 虽然可以手动控制 req.ParseMultipartForm(maxMemory int64) 参数来缓解,但是无法根治该问题。
3. 上传的临时文件越来越多,竟然没有自动删除,我觉得你需要排查一下这个问题。

自动删除逻辑: https://github.com/golang/go/blob/0f8ab2db177baee7b04182f5641693df3b212aa9/src/net/http/server.go#L1718-L1720
FrankAdler
16 天前
我在 gin 那看到你提的 pr 和 issue 了,我发现自己的项目也有这个问题,起初我以为是 gin 的 bug ,后来排查下来发现其实是我自己引起的,确实很隐蔽
先说结论,你可能跟我一样在 middleware 里使用了 ctx.Request = xxx 的操作,导致 http.serve 创建的 req 变掉了
而自动清理临时文件是在 finishRequest 方法调用,依赖的是 w.req.MultipartForm != nil ,如果你在前面覆盖了 req ,那就和这里 w.req 指向不一样了,
MultipartForm 最开始是没有初始化的,算是按需初始,也就是执行完 middleware 到了 handler 后打算获取 file 的时候,初始化后赋值给 req ,但是这时候已经是新的 req 了,而 finishRequest 还在调用旧的 req
bv
16 天前
@FrankAdler #13 你这个方向靠谱
DefoliationM
16 天前
不要用自带的,自己用 mutilpart form 解析就不会写临时文件了。
zihuyishi
13 天前
@FrankAdler 好像还真是这个,我为了加 opentelemetry 自己替换了一个 request. 所以我还是要自己主动在替换掉的地方处理一下?
FrankAdler
13 天前
@zihuyishi #16 知道根因了,你自己想办法解决吧,无非就是去掉替换 Req 或者主动调用 RemoveAll 了,或者加一个 middleware 在最后也来个 req.MultipartForm != nil
zihuyishi
13 天前
@FrankAdler 再加一个 middleware 去最后调用 RemoveAll 好像也不是太稳妥,因为可能最后拿到的 Request 不是持有 MultipartForm 的 Request ,可能还是原地改 Request 的去调用比较好?
bv
13 天前
@zihuyishi #16 我没用过 opentelemetry ,不太理解为什么加 opentelemetry 要替换原来的 Request ,看看有没有什么写法:再不替换原来的 Request 情况下加入 opentelemetry 。
bv
13 天前
@bv #19 替换 Request 容易给自己埋坑,你发现了 File 没删除你需要打补丁去删除 MultipartFile ,过几天又发现 req.Body 没有自动 Close ,是不是还要打补丁

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1141198

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX