自古以来,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…

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

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

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

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

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,可能版本不同,会有一些问题,可以自…

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 …

C#10 和 .NET6 代码跨平台开发

零、前言有数千页长的编程书籍,旨在成为 C# 语言、.NET 库、网站、服务、桌面和移动应用等应用模型的综合参考。这本书不一样。它简洁明了,旨在成为一本轻快有趣的书,每一个主题都有实用的实践演练。总体叙述的广度是以某种深度为代价的&…

linux之tar命令使用总结

1、使用原因 刚才在linux平台需要安装Clion的时候,下载得到CLion-2016.3.2.tar.gz 这个gz的压缩文件,所以需要解压到当前文件夹 2、简单解压到当前文件 解压当前文件夹命令 tar -zxvf CLion-2016.3.2.tar.gz 效果如下 3、tar命令介绍 -c: 建立压缩档案 -x:解压 -t:…

体验 正式发布 的OSM v1.0.0 版本

2021年10月份发布了OSM 1.0 RC[1],在过去的几个月里,OSM 的贡献者一直在努力为 v1.0.0 版本的发布做准备。2022年2月1日,OSM 团队正式发布 1.0.0 版本[2]。OSM 从最初的发布到现在已经走了很长的路,团队继续专注于社区需要的关键和…

数据流图的画法

数据流图的画法 数据流图也称为数据流程图date flow diagram , DFD,是一种便于用户理解和分析系统数据流程的图形工具,他摆脱了系统和详细内容,精确的在逻辑上描写叙述系统的功能、输入、输出和数据存储等,是系统逻辑模型的重要组…

MFC继承表

转载于:https://www.cnblogs.com/Lthis/p/4264967.html

linux之fdisk查看分区和mkfs.ext3删除分区和mount挂载和e2label添加卷标使用总结

一、使用fdisk、mkfs.ext3、和mount、e2lable的原因 有个分区挂载不上,然后需要格式化分区,还需要添加卷标 二、fdisk、mkfs.ext3、mount、e2lable命令介绍 1、fdisk命令介绍 1)、了解分区 分区是将一个硬盘驱动器分成若干个逻辑驱动器,分区是把硬盘连续的区块当做一个…

linux c之strncpy函数和strncmp函数最简单使用总结

1.原型声明: char * strncpy(char *dest,const char *src, size_t n); strncmp() 用来比较两个字符串的前n个字符,区分大小写,其原型为: int strncmp ( const char * str1, const char * str2, size_t n ); 若str1与str2的前n…

阻止你变现的,从来都不是开源许可证

文 | lola_chen出品 | OSC开源社区(ID:oschina2013)之前,《GPL 转闭源?法院判决:一日 GPL 终身 GPL》一文提出一个冷门却又重要的知识点:GPL 许可证之下的开源项目,可以分叉出来闭源…

03-递归

数据结构和算法 基于《算法图解》—Aditya Bhargava和《数据结构》—严蔚敏 第3章 递归 3.1 递归 假设在一堆嵌套的盒子里找钥匙,对比循环和递归。 使用循环解决: #使用while循环:只要盒子堆不是空,就从中取出一个盒子&#x…

linux c之提示format‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ [-Wformat

1、问题 有个long int data;我输出的时候printf("data is %d", data);出现下面警告 自己竟然不知道 长整型怎么打印出来,日了狗。 2、解决办法 md,m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大…

Asp.Net Core部署:早知道,还是docker!以及一点碎碎念

前言AspNetCore技术栈在我们团队里的使用也有一段时间了,之前的部署方式一直是本地编译之后上传可执行文件到服务器,使用supervisor来管理进程这种很原始的方式。参考之前的文章:Asp.Net Core学习笔记:(五)…

04-快速排序

数据结构和算法 基于《算法图解》—Aditya Bhargava 和《数据结构》—严蔚敏 第4章 快速排序 4.1 分而治之 divide and conquer , 简称D&C:一种著名的递归式问题解决方法。 例子1: 假设你是农场主,有一小块土地。要求将这块地均匀地分…