V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
hzjseasea
V2EX  ›  Go 编程语言

求助,关于 http body 的传输

  •  
  •   hzjseasea · 2022-08-25 10:01:15 +08:00 · 2786 次点击
    这是一个创建于 582 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有一个场景是要把前一个请求 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))
    }
    
    24 条回复    2022-08-25 17:55:30 +08:00
    ScepterZ
        1
    ScepterZ  
       2022-08-25 10:10:03 +08:00
    你再发起请求的时候,不还是得把它全都读出来么,感觉没啥用
    qianxiaoxiao
        2
    qianxiaoxiao  
       2022-08-25 10:17:09 +08:00
    只能改源码或者 重写 http client
    因为 http.Get() 之后 数据已经存放在 buf 里面了
    hzjseasea
        3
    hzjseasea  
    OP
       2022-08-25 10:20:27 +08:00
    @ScepterZ 像 23 这两个例子不就少读写各一次吗
    hzjseasea
        4
    hzjseasea  
    OP
       2022-08-25 10:21:21 +08:00
    @qianxiaoxiao 是这样的吗 我还以为这个 io.Pipe 可以边读边写
    GogoGo666
        5
    GogoGo666  
       2022-08-25 10:22:05 +08:00
    赞同 1 楼
    kkhaike
        6
    kkhaike  
       2022-08-25 10:33:58 +08:00
    直接吧 Get 的 body 作为 io.Reader 传入 Post 的 Request 不就行了。。
    c332030
        7
    c332030  
       2022-08-25 10:39:31 +08:00
    直接用流操作,IOUtils.copy 就行
    c332030
        8
    c332030  
       2022-08-25 10:40:50 +08:00
    哦,你这不是 java😂
    KouShuiYu
        9
    KouShuiYu  
       2022-08-25 10:41:09 +08:00   ❤️ 1
    https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream
    body 是一个可读取的二进制流,如果不处理 body 的话可以不用全部读到内存中,
    hxysnail
        10
    hxysnail  
       2022-08-25 10:56:03 +08:00   ❤️ 1
    直接将 body 取出来,传给 post 不就好了吗? body 是一个读流,数据会预读,数据量大的话是不会全部读到内存里面。post 请求会循环读取数据,并发给服务器。因此,可以认为是不断从读流读取数据,然后写到一个写流上。这个拷贝过程会复制所有数据,不过是分批进行的,不会将全部数据读到内存。
    hzjseasea
        11
    hzjseasea  
    OP
       2022-08-25 11:08:44 +08:00
    @kkhaike
    @KouShuiYu
    @c332030
    嗷嗷嗷谢谢哈,这样我就清楚了,本来我也是想着把 get 跟 post 两个东西放在一起的,不过 post 里有些东西不能开放出来所以想要着用上面三种方式,如果和 @hxysnail 老哥说的一样的话,那我就用第二种方式了,不用担心了。 感谢
    warcraft1236
        12
    warcraft1236  
       2022-08-25 11:24:11 +08:00
    这不正好是 zero copy 可以做的事情吗?性能还更好
    runningman
        13
    runningman  
       2022-08-25 13:31:25 +08:00
    body 很大本身就是个错误吧
    jjwjiang
        14
    jjwjiang  
       2022-08-25 13:55:49 +08:00
    body 大到你需要考虑这种问题的时候,你应该先去操心网络吞吐量和网速的问题了,我感觉纯属杞人忧天
    proxytoworld
        15
    proxytoworld  
       2022-08-25 14:29:02 +08:00
    sujin190
        16
    sujin190  
       2022-08-25 14:35:29 +08:00 via Android
    @runningman 大概率文件下载上传吧,比如微信这个 jssdk 上传的想变永久自己存就得下下来再上传到七牛、又拍、oss 之类的,这样的 body 大就是自然的了
    zjsxwc
        17
    zjsxwc  
       2022-08-25 14:53:28 +08:00
    你服务器 Get 文件大小确实没有限制,但你把这个文件 Post 到另一个服务器时,
    你 Post 发出的请求不可能没有大小限制,
    我记得 Nginx 默认对于 Request 大小限制 client_max_body_size 值是 1M 。

    1M 的“大文件"算大吗?
    hzjseasea
        18
    hzjseasea  
    OP
       2022-08-25 15:33:30 +08:00
    @jjwjiang 技术不大行,只想尽可能把自己想到的可能会出现的问题解决一下
    hzjseasea
        19
    hzjseasea  
    OP
       2022-08-25 15:35:05 +08:00
    @sujin190 对的,普遍都是 10G-20G 的
    hzjseasea
        20
    hzjseasea  
    OP
       2022-08-25 15:40:05 +08:00
    @zjsxwc emm Nginx 的配置我看不到哈,其实我现在碰到的场景就跟 16 楼说的类似,端到端之间做数据备份,没走线下的物理备份。
    runningman
        21
    runningman  
       2022-08-25 15:51:23 +08:00
    @sujin190 那应该使用 类似这种 https://gitee.com/no-src/gofs
    hzjseasea
        22
    hzjseasea  
    OP
       2022-08-25 15:59:02 +08:00
    我去看了一下阿里的 oss 用的也是第二种方式
    https://help.aliyun.com/document_detail/88617.html
    sujin190
        23
    sujin190  
       2022-08-25 16:31:51 +08:00   ❤️ 1
    @hzjseasea #19 如果你 http.Get 会使用 DefaultClient ,DefaultClient 默认使用的 DefaultTransport 的默认缓冲区是 4k ,超过这个大小就不会读取到内存了,如果你需要定制,自己创建 Client 和 Transport 就可以把,resp.Body 本来就 Reader 类型之间可以放到下一个 post 请求方法里吧,如果你的下一个 post 请求还需要参数,那么如上面所说用 io.Pipe copy 就可以了,DefaultTransport 的写缓冲区也是 4k ,所以单个请求的最大内存就是 8k 了
    Chinsung
        24
    Chinsung  
       2022-08-25 17:55:30 +08:00
    前面的本质上已经都到内存了,这种得在比较底层实现网卡里的直接修改内容才能实现内存都不过吧,只有网卡缓冲区直接对拷,才能实现真正的极致性能,到了内存差不多的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2740 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 12:34 · PVG 20:34 · LAX 05:34 · JFK 08:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.