自古以来,JSON序列化就是兵家必争之地

95d4d60861d6e833b9641cbddd362bf3.gif

上文讲到使用ioutil.ReadAll读取大的Response Body,出现读取Body超时的问题。

01

前人引路

Stackoverflow[1]morganbaz的看法是:

使用iotil.ReadAll去读取go语言里大的Response Body,是非常低效的; 另外如果Response Body足够大,还有内存泄漏的风险。

data,err:=  iotil.ReadAll(r)
if err != nil {return err
}
json.Unmarshal(data, &v)

有一个更有效的方式来解析json数据,会用到Decoder类型

err := json.NewDecoder(r).Decode(&v)
if err != nil {return err
}

这种方式从内存和时间角度,不但更简洁,而且更高效。

Decoder不需要分配一个巨大的字节内存来容纳数据读取——它可以简单地重用一个很小的缓冲区来获取所有的数据并渐进式解析。这为内存分配节省了大量时间,并消除了GC的压力•JSON Decoder可以在第一个数据块进入时开始解析数据——它不需要等待所有东西完成下载。

02

后人乘凉

我针对前人的思路补充两点。

①.官方ioutil.ReadAll是通过初始大小为512字节的切片来读取reader,我们的response body大概50M, 很明显会频繁触发切片扩容,产生不必要的内存分配,给gc也带来压力。

go切片扩容的时机:需求小于256字节,按照2倍扩容;超过256字节,按照1.25倍扩容。

    ② .怎么理解morganbaz所说的带来的内存泄漏的风险?

内存泄漏是指程序已动态分配的堆内存由于某种原因未释放,造成系统内存浪费,导致程序运行速度减慢升职系统崩溃等严重后果。

ioutil.ReadAll读取大的Body会触发切片扩容,讲道理这种做法只会带来内存浪费,最终会被gc释放,原作者为什么会强调有内存泄漏的风险?

我咨询了一些童靴,对于需要长时间运行的高并发服务器程序,不及时释放内存也可能导致最终耗尽系统所有内存,这是一种隐式内存泄漏。

03

JSON序列化是兵家必争之地

morganbaz大佬提出使用标准库encoding/json来边读边反序列化, 减少内存分配, 加快反序列化速度。

自古以来,JSON序列化就是兵家必争之地[2],各大语言内部均对序列化有不同的实现思路,性能相差较大。

下面使用高性能json序列化库json-iterator与原生ioutil.ReadAll+ json.Unmarshal方式做对比。

顺便也检验我最近实践pprof[3]的成果

# go get "github.com/json-iterator/go"
package mainimport ("bytes""flag""log""net/http""os""runtime/pprof""time"jsoniter "github.com/json-iterator/go"
)var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file.")
var memprofile = flag.String("memprofile", "", "write  mem profile to file")func main() {flag.Parse()if *cpuprofile != "" {f, err := os.Create(*cpuprofile)if err != nil {log.Fatal(err)}pprof.StartCPUProfile(f)defer pprof.StopCPUProfile()}c := &http.Client{Timeout: 60 * time.Second,// Transport: tr,}body := sendRequest(c, http.MethodPost)log.Println("response body length:", body)if *memprofile != "" {f, err := os.Create(*memprofile)if err != nil {log.Fatal("could not create memory profile: ", err)}defer f.Close() // error handling omitted for exampleif err := pprof.WriteHeapProfile(f); err != nil {log.Fatal("could not write memory profile: ", err)}}
}func sendRequest(client *http.Client, method string) int {endpoint := "http://xxxxx.com/table/instance?method=batch_query"expr := "idc in (logicidc_hd1,logicidc_hd2,officeidc_hd1)"var json = jsoniter.ConfigCompatibleWithStandardLibraryjsonData, err := json.Marshal([]string{expr})log.Println("开始请求:" + time.Now().Format("2006-01-02 15:04:05.010"))response, err := client.Post(endpoint, "application/json", bytes.NewBuffer(jsonData))if err != nil {log.Fatalf("Error sending request to api endpoint, %+v", err)}log.Println("服务端处理结束, 准备接收Response:" + time.Now().Format("2006-01-02 15:04:05.010"))defer response.Body.Close()var resp Responsevar records = make(map[string][]Record)resp.Data = &recordserr= json.NewDecoder(response.Body).Decode(&resp)if err != nil {log.Fatalf("Couldn't parse response body, %+v", err)}log.Println("客户端读取+解析结束:" + time.Now().Format("2006-01-02 15:04:05.010"))var result = make(map[string]*Data, len(records))for _, r := range records[expr] {result[r.Ins.Id] = &Data{Active: "0", IsProduct: true}}return len(result)
}
# 省略了反序列化的object type

内存对比

非单纯序列化对比,前者对后者优化的效果反馈。

0fa7bf667d7bb428ae91be4ff179063e.png

                    --- json-iterator边读 边反序列化 ---

cbcd960020e68d3dd427ce50f051ce29.png

            --- io.ReadAll + json.Unmarshal 反序列化---

我们可以点进去看io.ReadAll + json.Unmarshal内存耗在哪里?

Total:     59.59MB    59.59MB (flat, cum)   100%626            .          .           func ReadAll(r Reader) ([]byte, error) { 627            .          .               b := make([]byte, 0, 512) 628            .          .               for { 629            .          .                   if len(b) == cap(b) { 630            .          .                       // Add more capacity (let append pick how much). 631      59.59MB    59.59MB                       b = append(b, 0)[:len(b)] 632            .          .                   } 633            .          .                   n, err := r.Read(b[len(b):cap(b)]) 634            .          .                   b = b[:len(b)+n] 635            .          .                   if err != nil { 636            .          .                       if err == EOF {

从上图也可以印证io.ReadAll  为存储整个Response.Body对初始512字节的切片不断扩容, 产生常驻内存59M。

你还可以对比alloc_space 分配内存 ,(alloc_space、inuse_space 的差值可粗略理解为gc释放的部分)。

d016020aff9f8563914059a17c1a6f02.pngb9dbd0e020e27ba076821155d3cf1e60.png

从结果看json-iterator相比io.ReadAll + json.Unmarshal 动态分配的内存还是比较小的。

ref:排查go开发的HttpClient读取Body超时

04

我的收获

1.ioutil.ReadAll 读取大的response.body的风险:性能差且有内存泄漏的风险。2.隐式内存泄漏:对于高并发、长时间运行的web程序,不及时释放内存最终也会导致内存耗尽。3.json 序列化是兵家必争之地, json-iterator 是兼容标准encode/json api 用法的高性能序列化器。4.pprof 内存诊断的姿势 & 调试指标的意义。

引用链接

[1] Stackoverflow: https://stackoverflow.com/questions/52539695/alternative-to-ioutil-readall-in-go
[2] 自古以来,JSON序列化就是兵家必争之地: https://yalantis.com/blog/speed-up-json-encoding-decoding/
[3] 实践pprof: https://segmentfault.com/a/1190000016412013

有态度的马甲建立了真● 高质量交流群:大佬汇聚、无事静默、有事激活、深度思考。

0b875469eb84d12391183ba9d8100461.png

2ab2126598ff51777f146949a2e610f7.gif

年终总结:2021技术文大盘点  |  打包过去,面向未来

项目总结:麻雀虽小,五脏俱全

理念总结:实话实说:只会.NET,会让我们一直处于鄙视链、食物链的下游

云原生系列: 什么是云原生?

点“3432eb2f9bf9832ead5aa09811653cc9.gif戳“在看03d97e0c24497e3a17771124bc259efd.gif

体现态度很有必要!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/291934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验三《实时系统的移植》 20145222黄亚奇 20145213祁玮

北京电子科技学院(BESTI) 实 验 报 告 封 面 课程:信息安全系统设计基础 班级:1452 姓名: 黄亚奇 祁玮 学号: 20145222 20145213 成绩: 指导教师:娄嘉鹏 实验日期:2016.1…

linux c之通过popen和pclose函数创建管道执行shell 运行命令使用总结

1、函数介绍 popen 和 pclose 函数 操作是创建一个管道链接到另一个进程,然后读其输出或向其输入端发送数据。标准 I/O 库提供了两个函数 popen 和 pclose 函数,这两个函数实现的操作是:创建一个管道,调用 fork 创建一个子进程,关闭管道的不使用端,执行一个 shell 以运行…

python内置函数 pdf_关于Python巧妙而强大的内置函数

python内置了一些非常巧妙而且强大的内置函数,对初学者来说,一般不怎么用到,我也是用了一段时间python之后才发现,哇还有这么好的函数,这个函数都是经典的而且经过严格测试的,可以一下子省了你原来很多事情&#xff0c…

nginx获得response自定义的header

Response header send by upstream is $upstream_http_x_uuid http://wiki.nginx.org/HttpUpstreamModule#.24upstream_http_.24HEADER $upstream_http_$HEADER Arbitrary HTTP protocol headers, for example: $upstream_http_host$http_x_* is header sent by client. 转载于…

欢迎来到元宇宙的虚拟世界

众所周知,由于这次疫情,给我们的生活造成了很多不便。现在,让我们进入未来科技世界, 我想很多人都听说过 元宇宙 这个词,大家可能对这个新的概念有很多疑问, 什么是元宇宙? 这是什么新技术? 在即将到来的未来会带来怎样的变化&a…

checking size of char… configure: error: cannot compute sizeof (char) 解决方法

今天在编译freeswitch时 报了这个错误,看config.log 发现最后在测试一些lib的时候出错了。 先按configure的--with选项处理无效。config.log依然同样的错误 最后export LD_LIBRARY_PATH/lib/:/usr/lib/:/usr/local/lib 再configure就可以了。 参考http://www.recoye…

jQuery子页面获取父页面元素

$("input[typecheckbox]:checked",window.opener.document);//适用于打开窗口的父页面元素获取 $("input[typecheckbox]:checked",parent.document);//适用于iframe子页面获取父页面元素 转载于:https://www.cnblogs.com/lxcmyf/p/6189478.html

linux c之通过管道父子进程实现同步通信

1、父进程向子进程写数据 进程的概念和父进程向子进程写数据,我们之前有一片博客介绍过 http://blog.csdn.net/u011068702/article/details/54914774 linux c之管道的介绍、创建关闭和简单读写(父进程向子进程写入数据) 2、父子进程实现同…

attiny13a程序实例_ATtiny13A图文构成

Features•High Performance, Low Power AVR8-Bit Microcontroller•Advanced RISC Architecture–120 Powerful Instructions – Most Single Clock Cycle Execution–32 x 8 General Purpose Working Registers–Fully Static Operation–Up to 20 MIPS Througput at 20 MHz•…

MySQL导入数据load data infile用法

MySQL导入数据load data infile用法 基本语法: load data [low_priority] [local] infile file_name txt [replace | ignore] into table tbl_name [fields [terminated byt] [OPTIONALLY] enclosed by ] [escaped by\ ]] [lines terminated byn] [ignore number li…

11G Oracle RAC添加新表空间时数据文件误放置到本地文件系统的修正

今天看了一篇文章说是误将新创建的表空间的数据文件放置在了本地系统而不是共享存储上。是Oracle的中文技术支持博客题目是:RAC中误将数据文件创建在本地盘时的修正于是我想11G 也兼容这些操作的方法,但是11G的新特性有一点就是可以直接支持ASM文件系统直…

sql练习(针对Mysql)

创建表: 1 DROP TABLE DEPT;2 --部门表3 CREATE TABLE DEPT(4 DEPTNO int PRIMARY KEY,5 DNAME VARCHAR(14) , --部门名称6 LOC VARCHAR(13) ---部门地址7 ) ;8 9 CREATE TABLE DEPT( 10 DEPTNO int PRIMARY KEY, 11 DNAME VARCHAR(14) , 12 …

.NET6之MiniAPI(十二):引入EntityFramewor

说明:本篇重点说明MiniAPI引入EntityFramework,EF的使用不是本篇的重点本篇是在MiniAPI中使用EntityFramework,所以先奉上创建数据的脚本,数据库是SQL Server,可能版本不同,会有一些问题,可以自…

linux c之命名管道简单使用

1、介绍FIFO(命名管道) 管道有局限性,只能在具有亲缘关系的进程间通信,但是命名管道克服了这个问题,可以实现无亲缘关系的进程之间的通信 API介绍: /* FIFO 命名管道 */ /* * 函数功能:功能和管道类似; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #i…

C# 微信v3退款

1.退款需要退款证书。C#都是用p12的证书,双击证书导入,密码是mch_id(商户号) 2.调用微信退款接口进行退款操作 1 string respstring.Empty 2 string cert Server.MapPath("apiclient_cert.p12"); 3 string passwo…

ios 销毁当前页面重新开启_问:如何强制销毁iOS中的视图控制器?

是否可以在iOS中回收/强制销毁UIViewController?问:如何强制销毁iOS中的视图控制器?我用这github project得到一个定制UIViewControllerTransition:这里是程序的流程:vc1礼物给nav到的rootVc是vc2在vc2有是UIButton。点…

2021 开源社年度报告:开心开源

# 引言 #2020年的开源社年度报告仿佛还在昨天,一转眼,2021年都已经过完了。在去年的年度报告上,我们说2020是动荡不安的一年。结果2021年,简直可以说是动荡加剧,令人应接不暇的一年。迫于疫情的影响,我们一…

luajit日记-FFI库

2019独角兽企业重金招聘Python工程师标准>>> LuaJIT FFI LibraryThe FFI library allows calling external C functions and using C data structures from pure Lua code. The FFI library largely obviates the need to write tedious manual Lua/C bindings in …

linux c之access方法介绍

1、函数介绍 access函数检查调用进程是否可以对指定的文件执行某种操作 api: int access(const char * pathname, int mode) athname:需要检测的文件路劲名 mode:需要测试的操作模式 mode参数介绍: R_OK 测试读许可权 W_OK 测试写许可权 X_OK 测试执行许可…

P4 前端编译器p4c-bm、后端编译器bmv2命令安装 make error问题

参考:Github 安装p4c-bm: sudo pip install -r requirements.txtsudo pip install -r requirements_v1_1.txt //if you are interested in compiling P4 v1.1 programssudo python setup.py install 测试: p4c-bmv2 -h 弹出相关信息&#xff…