有一个场景是要把前一个请求 get 到的 body 原模原样的拿下来作为另外一个 post 请求的请求体,然后我就有一个疑问,对于小体积的 body 来说无所谓,哪怕是直接将 body copy 一份都没事,但是对于大体积的 body 来说,如果直接用第一种方式 copy 一份的话,那整个内容都会保存在内存里,高并发的环境下对于内存小的机器会不会直接撑爆了,然后我就想了下面两种方式,第二种用函数返回的方式我觉得也是存在内存里面的,用 pipe 的方式更像是流式的方式传输? 望指教
// 直接 copy 一份
func GetBody() ([]byte, error) {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
return body, nil
}
func main() {
body, _ := GetBody()
// ... deal with body
fmt.Println(body)
}
// 直接将 response body 抛出来
func GetBody() (io.ReadCloser, error) {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
return nil, err
}
return resp.Body, nil
}
func main() {
body, err := GetBody()
if err != nil {
fmt.Println(err)
}
defer body.Close()
// ... deal with body
}
// 通过 io.pipe 的方式传输
func PipeBody(w *io.PipeWriter) {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
log.Println(err)
}
defer resp.Body.Close()
io.Copy(w, resp.Body)
}
func main() {
reader, writer := io.Pipe()
go func() {
defer writer.Close()
PipeBody(writer)
}()
// ... deal with body, for example
body, _ := ioutil.ReadAll(reader)
fmt.Println(string(body))
}
![]() |
1
ScepterZ 2022-08-25 10:10:03 +08:00
你再发起请求的时候,不还是得把它全都读出来么,感觉没啥用
|
![]() |
2
qianxiaoxiao 2022-08-25 10:17:09 +08:00
只能改源码或者 重写 http client
因为 http.Get() 之后 数据已经存放在 buf 里面了 |
![]() |
4
hzjseasea OP @qianxiaoxiao 是这样的吗 我还以为这个 io.Pipe 可以边读边写
|
![]() |
5
GogoGo666 2022-08-25 10:22:05 +08:00
赞同 1 楼
|
![]() |
6
kkhaike 2022-08-25 10:33:58 +08:00
直接吧 Get 的 body 作为 io.Reader 传入 Post 的 Request 不就行了。。
|
![]() |
7
c332030 2022-08-25 10:39:31 +08:00
直接用流操作,IOUtils.copy 就行
|
![]() |
8
c332030 2022-08-25 10:40:50 +08:00
哦,你这不是 java😂
|
![]() |
9
KouShuiYu 2022-08-25 10:41:09 +08:00 ![]() https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream
body 是一个可读取的二进制流,如果不处理 body 的话可以不用全部读到内存中, |
10
hxysnail 2022-08-25 10:56:03 +08:00 ![]() 直接将 body 取出来,传给 post 不就好了吗? body 是一个读流,数据会预读,数据量大的话是不会全部读到内存里面。post 请求会循环读取数据,并发给服务器。因此,可以认为是不断从读流读取数据,然后写到一个写流上。这个拷贝过程会复制所有数据,不过是分批进行的,不会将全部数据读到内存。
|
![]() |
11
hzjseasea OP |
12
warcraft1236 2022-08-25 11:24:11 +08:00
这不正好是 zero copy 可以做的事情吗?性能还更好
|
13
runningman 2022-08-25 13:31:25 +08:00
body 很大本身就是个错误吧
|
14
jjwjiang 2022-08-25 13:55:49 +08:00
body 大到你需要考虑这种问题的时候,你应该先去操心网络吞吐量和网速的问题了,我感觉纯属杞人忧天
|
![]() |
15
proxytoworld 2022-08-25 14:29:02 +08:00
|
![]() |
16
sujin190 2022-08-25 14:35:29 +08:00 via Android
@runningman 大概率文件下载上传吧,比如微信这个 jssdk 上传的想变永久自己存就得下下来再上传到七牛、又拍、oss 之类的,这样的 body 大就是自然的了
|
![]() |
17
zjsxwc 2022-08-25 14:53:28 +08:00
你服务器 Get 文件大小确实没有限制,但你把这个文件 Post 到另一个服务器时,
你 Post 发出的请求不可能没有大小限制, 我记得 Nginx 默认对于 Request 大小限制 client_max_body_size 值是 1M 。 1M 的“大文件"算大吗? |
![]() |
20
hzjseasea OP @zjsxwc emm Nginx 的配置我看不到哈,其实我现在碰到的场景就跟 16 楼说的类似,端到端之间做数据备份,没走线下的物理备份。
|
21
runningman 2022-08-25 15:51:23 +08:00
@sujin190 那应该使用 类似这种 https://gitee.com/no-src/gofs
|
![]() |
22
hzjseasea OP 我去看了一下阿里的 oss 用的也是第二种方式
https://help.aliyun.com/document_detail/88617.html |
![]() |
23
sujin190 2022-08-25 16:31:51 +08:00 ![]() @hzjseasea #19 如果你 http.Get 会使用 DefaultClient ,DefaultClient 默认使用的 DefaultTransport 的默认缓冲区是 4k ,超过这个大小就不会读取到内存了,如果你需要定制,自己创建 Client 和 Transport 就可以把,resp.Body 本来就 Reader 类型之间可以放到下一个 post 请求方法里吧,如果你的下一个 post 请求还需要参数,那么如上面所说用 io.Pipe copy 就可以了,DefaultTransport 的写缓冲区也是 4k ,所以单个请求的最大内存就是 8k 了
|
24
Chinsung 2022-08-25 17:55:30 +08:00
前面的本质上已经都到内存了,这种得在比较底层实现网卡里的直接修改内容才能实现内存都不过吧,只有网卡缓冲区直接对拷,才能实现真正的极致性能,到了内存差不多的
|