【Go】优雅的读取http请求或响应的数据

原文链接:https://blog.thinkeridea.com/201901/go/you_ya_de_du_qu_http_qing_qiu_huo_xiang_ying_de_shu_ju.html

http.Request.Bodyhttp.Response.Body 中读取数据方法或许很多,标准库中大多数使用 ioutil.ReadAll 方法一次读取所有数据,如果是 json 格式的数据还可以使用 json.NewDecoderio.Reader 创建一个解析器,假使使用 pprof 来分析程序总是会发现 bytes.makeSlice 分配了大量内存,且总是排行第一,今天就这个问题来说一下如何高效优雅的读取 http 中的数据。

背景介绍

我们有许多 api 服务,全部采用 json 数据格式,请求体就是整个 json 字符串,当一个请求到服务端会经过一些业务处理,然后再请求后面更多的服务,所有的服务之间都用 http 协议来通信(啊, 为啥不用 RPC,因为所有的服务都会对第三方开放,http + json 更好对接),大多数请求数据大小在 1K~4K,响应的数据在 1K~8K,早期所有的服务都使用 ioutil.ReadAll 来读取数据,随着流量增加使用 pprof 来分析发现 bytes.makeSlice 总是排在第一,并且占用了整个程序 1/10 的内存分配,我决定针对这个问题进行优化,下面是整个优化过程的记录。

pprof 分析

这里使用 https://github.com/thinkeridea/go-extend/blob/master/exnet/exhttp/expprof/pprof.go 中的 API 来实现生产环境的 /debug/pprof 监测接口,没有使用标准库的 net/http/pprof 包因为会自动注册路由,且长期开放 API,这个包可以设定 API 是否开放,并在规定时间后自动关闭接口,避免存在工具嗅探。

服务部署上线稳定后(大约过了一天半),通过 curl 下载 allocs 数据,然后使用下面的命令查看分析。

$ go tool pprof allocs
File: xxx
Type: alloc_space
Time: Jan 25, 2019 at 3:02pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 604.62GB, 44.50% of 1358.61GB total
Dropped 776 nodes (cum <= 6.79GB)
Showing top 10 nodes out of 155
flat flat% sum% cum cum%
111.40GB 8.20% 8.20% 111.40GB 8.20% bytes.makeSlice
107.72GB 7.93% 16.13% 107.72GB 7.93% github.com/sirupsen/logrus.(*Entry).WithFields
65.94GB 4.85% 20.98% 65.94GB 4.85% strings.Replace
54.10GB 3.98% 24.96% 56.03GB 4.12% github.com/json-iterator/go.(*frozenConfig).Marshal
47.54GB 3.50% 28.46% 47.54GB 3.50% net/url.unescape
47.11GB 3.47% 31.93% 48.16GB 3.55% github.com/json-iterator/go.(*Iterator).readStringSlowPath
46.63GB 3.43% 35.36% 103.04GB 7.58% handlers.(*AdserviceHandler).returnAd
42.43GB 3.12% 38.49% 84.62GB 6.23% models.LogItemsToBytes
42.22GB 3.11% 41.59% 42.22GB 3.11% strings.Join
39.52GB 2.91% 44.50% 87.06GB 6.41% net/url.parseQuery

从结果中可以看出采集期间一共分配了 1358.61GB top 10 占用了 44.50% 其中 bytes.makeSlice 占了接近 1/10,那么看看都是谁在调用 bytes.makeSlice 吧。

(pprof) web bytes.makeSlice

从上图可以看出调用 bytes.makeSlice 的最终方法是 ioutil.ReadAll, (受篇幅影响就没有截取 ioutil.ReadAll 上面的方法了),而 90% 都是 ioutil.ReadAll 读取 http 数据调用,找到地方先别急想优化方案,先看看为啥 ioutil.ReadAll 会导致这么多内存分配。

func readAll(r io.Reader, capacity int64) (b []byte, err error) {
var buf bytes.Buffer
// If the buffer overflows, we will get bytes.ErrTooLarge.
// Return that as an error. Any other panic remains.
defer func() {
e := recover()
if e == nil {
return
}
if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
err = panicErr
} else {
panic(e)
}
}()
if int64(int(capacity)) == capacity {
buf.Grow(int(capacity))
}
_, err = buf.ReadFrom(r)
return buf.Bytes(), err
} func ReadAll(r io.Reader) ([]byte, error) {
return readAll(r, bytes.MinRead)
}

以上是标准库 ioutil.ReadAll 的代码,每次会创建一个 var buf bytes.Buffer 并且初始化 buf.Grow(int(capacity)) 的大小为 bytes.MinRead, 这个值呢就是 512,按这个 buffer 的大小读取一次数据需要分配 2~16 次内存,天啊简直不能忍,我自己创建一个 buffer 好不好。

看一下火焰图

【Go】优雅的读取http请求或响应的数据的更多相关文章

  1. 【Go】优雅的读取http请求或响应的数据-续

    原文链接:https://blog.thinkeridea.com/201902/go/you_ya_de_du_qu_http_qing_qiu_huo_xiang_ying_de_shu_ju_2 ...

  2. Ajax发送GET、POST请求和响应XML数据案例

    1.新建工程 新建一个java web工程,新建一个Servlet文件 AServlet.java,用于返回get和post请求. public class AServlet extends Http ...

  3. spring基础---->请求与响应的参数(一)

    这里面我们主要介绍一下spring中关于请求和响应参数数据的问题.爱,从来就是一件千回百转的事.不曾被离弃,不曾受伤害,怎懂得爱人?爱,原来是一种经历. spring中的请求与响应 一.spring中 ...

  4. struts2基础——请求与响应、获取web资源

    一.请求与响应 Action1.含义:(1) struts.xml 中的 action 元素,也指 from 表单的 action 属性,总之代表一个 struts2 请求.(2) 用于处理 Stru ...

  5. 写一个ActionFilter检测WebApi接口请求和响应

    我们一般用日志记录每次Action的请求和响应,方便接口出错后排查,不过如果每个Action方法内都写操作日志太麻烦,而且客户端传递了错误JSON或XML,没法对应强类型参数,请求没法进入方法内, 把 ...

  6. http请求返回响应码的意思

    HTTP 状态响应码 意思详解/大全 HTTP状态码(HTTP Status Code)是用以表示网页服务器HTTP响应状态的3位数字代码.它由 RFC 2616 规范定义的,并得到RFC 2518. ...

  7. CakePHP之请求与响应对象

    请求与响应对象 请求与响应对象在 CakePHP 2.0 是新增加的.在之前的版本中,这两个对象是由数组表示的,而相关的方法是分散在RequestHandlerComponent,Router,Dis ...

  8. 老李分享:HTTP协议之请求和响应

    老李分享:HTTP协议之请求和响应   HTTP请求头详解: GET http://www.foo.com/ HTTP/1.1 GET是请求方式,请求方式有GET/POST http://www.fo ...

  9. python爬虫(二)_HTTP的请求和响应

    HTTP和HTTPS HTTP(HyperText Transfer Protocol,超文本传输协议):是一种发布和接收HTML页面的方法 HTTPS(HyperText Transfer Prot ...

随机推荐

  1. 初识大数据(二. Hadoop是什么)

    hadoop是一个由Apache基金会所发布的用于大规模集群上的分布式系统并行编程基础框架.目前已经是大数据领域最流行的开发架构.并且已经从HDFS.MapReduce.Hbase三大核心组件成长为一 ...

  2. OpenXml修改word特定内容

    采用OpenXml来修改word特定内容,如下: word: OpenXml修改word之前: OpenXml修改word之后: 代码: string path = @"C:\Users\A ...

  3. 恢复oracle数据从delete

    今天维护系统的时候没仔细看,误删了50行数据,然后想起来以前学过delete语句删除的数据是可以回复的,但是那个时候比较慌乱,也没有心情仔细看,反而是想到了一个歪招解决了问题,我有个良好的嗜好就是经常 ...

  4. Rabbitmq的使用及Web监控工具使用

    本文转载自:https://www.cnblogs.com/gossip/p/4475978.html windows安装手册请参考:http://www.rabbitmq.com/install-w ...

  5. ZJOI2019二试游记

    ZJOI2019二试游记 Day -2 今天就要去被虐了!开一篇占个坑.禁赛警告 Day -1 早上zzy,下午zzq,无限懵逼... 过来的时候Sooke,memset0,老K坐我旁边,瑟瑟发抖.. ...

  6. [深入学习Web安全](11)之XSS玩法

    [深入学习Web安全](11)之XSS玩法 本文转自:i春秋社区 前言这篇paper,我们将学习如何优雅的调戏XSS.我们会教大家一些不常用的,但很实用的XSS姿势.我们在正式进入主题之前,先来说一下 ...

  7. Java常用的经典排序算法:冒泡排序与选择排序

     一.冒泡排序                             冒泡排序(Bubble Sort)是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为 ...

  8. Eclipse常用20个快捷键

    Eclipse常用20个快捷键 1. Ctrl+F : 本文查找Find与替换Replace 2. Ctrl+H : 全局搜索,可按照文件类型搜索 3. Ctrl+1 : 快速修复,能快速的显示光标所 ...

  9. 第87节:Java中的Bootstrap基础与SQL入门

    第87节:Java中的Bootstrap基础与SQL入门 前言复习 什么是JQ? : write less do more 写更少的代码,做更多的事 找出所有兄弟: $("div" ...

  10. 卷积神经网络CNN的原理(二)---公式推导

    卷积神经网络与普通神经网络的区别在于,卷积神经网络包含多个由卷积层和池化层构成的特征抽取器.在卷积神经网络的卷积层中,一个神经元只与部分邻层神经元连接.在CNN的一个卷积层中,通常包含若干个特征平面( ...