一桩关于Json序列化引发的惨案(Go系统)

news2024/7/5 0:06:11

文章目录

  • 前言
  • 突然崩了
  • 排查问题
  • 关于go的json库
    • 什么是反射
  • 解决大结构体序列化的性能问题
    • 干掉大结构体
    • 减少反射使用
      • 一些好用的第三方序列化包
    • 自定义序列化
  • 写在最后

前言

一个风和日丽的下午,线上系统突然开始报警(系统温馨提示,您的服务接口响应耗时已突破1s )
(-_-|||)
在这里插入图片描述
突破天际的耗时线(并且伴随着实例OOM重启问题)!!!

当时脑门汗蹭的一下的就冒出来了,趁老板没杀过来之前立马开始紧急排查处理~

突然崩了

由于最近没有发布,这波服务崩的莫名其妙,为了不影响线上业务,观察服务异常情况和资源监控后,首先对每个实例的资源配置进行了提升

重新发布之后,系统算是稳定下来了,然而接口耗时仍然比出问题之前要高了接近2倍。

排查问题

针对服务性能异常的排查,笔者在之前的文章《线上GO服务出现GC故障,我当时就急了》 中已经提到过,这里就不再赘述。

通过pprof采样线上cpu耗时之后,锁定了有问题的代码片段
在这里插入图片描述在这里插入图片描述
问题已经很明显了,由于上报结果集中需要序列化的结构体太大,占用了过多的CPU和内存资源,导致线上服务性能急剧下降,并且产生了OOM问题。

问题很快被修复了,但是也引发了笔者对于json序列化性能的思考:抛开常规的优化手段不谈,单从json序列化本身是否还有优化空间呢?

关于go的json库

这里不得不先向不了解的朋友们简单介绍一下go的标准json库 : )

Go 语言的标准 json 库是一个 encoding/json 包,它提供了一系列的 API 函数,用于从 Go 对象生成 json 文档,以及从 json 文档中填充 Go 对象。它的序列化和反序列化主要通过包中的 json.Marshal()json.Unmarshal() 方法实现。

Go 语言中的 json 序列化过程不需要被序列化的对象预先实现任何接口,它会通过反射获取结构体或者数组中的值并以树形的结构递归地进行编码。

那么问题来了,首先需要明确的是,go中反射操作的性能是比较差的,而标准库恰好又大量使用反射获取值,较为耗费 CPU 配置;其次频繁分配对象,也会带来内存分配和 GC 的开销;

什么是反射

前面提到了go语言的反射,反射是什么,为什么反射操作会带来性能问题,这里给出一些简单的解释;

Go 的反射是指在运行时动态地获取和操作对象的类型和值的能力,它主要通过 reflect 包中的 TypeValue 类型来实现。Go 的反射比较耗性能的原因主要有以下几点:

  1. 反射操作需要调用一系列的函数,而不是直接访问内存地址,这会增加函数调用的开销和栈空间的占用。
  2. 反射操作需要使用类型断言和类型转换,这会增加运行时的类型检查和内存分配的开销。
  3. 反射操作需要使用反射包中的方法,而不是编译器优化过的内置方法,这会降低执行效率和缓存命中率。
  4. 反射操作需要使用递归和状态机等技术,这会增加逻辑复杂度和计算量。

根据一些性能测试,Go 的反射操作可能比正常操作慢几十倍甚至几百倍。因此,在不必要的情况下,应该尽量避免使用反射,或者使用一些高性能的反射库来优化反射性能。

解决大结构体序列化的性能问题

干掉大结构体

是的没错,如果解决不了问题,那么我们考虑解决引发问题的对象~(听着怪怪的)

思考一下为什么业务中会出现需要序列化大结构体的场景,是不是所有字段都是被需要的?服务中我们提倡按需加载,对于序列化也是一样的。

我们可以根据需要定一个专门用于序列化的结构体,该结构体中只包含我们需要的字段;

对于可迭代的数据结构来说,我们还可以进行分批处理,减少单次序列化的压力;

减少反射使用

如何这样还不够,又或者被序列化对象确实很大,那么我们可以考虑不使用标准json库,替换成其他底层实现中反射使用少或者完全不使用反射的第三方json库;

一些好用的第三方序列化包

关于第三方高性能json库的详细评测和使用建议,可以参考这篇文章:《探究|Go JSON 三方包哪家强?》

自定义序列化

如果不考虑泛用性,追求极致的性能的情况下,我们还可以祭出终极杀招,自定义序列化协议

由于使用场景很小,这里简单抛砖引玉给出一个例子,我们可以基于go语言 encoding/binary 库对结构体进行轻量级序列化和反序列化。这种方式的优点是简单高效,缺点嘛也很明显,有一定的开发和维护成本

关于encoding/binary的更多使用姿势本篇就不多赘述了,下面上一个简单的序列化例子,小伙伴们自行感受下

package main

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	"io"
)

type User struct {
	Name     string
	Age      int64
	IsSingle bool
}

type Encoder struct {
	w io.Writer
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{w: w}
}

func (e *Encoder) encode(user *User) {
	_ = binary.Write(e.w, binary.LittleEndian, uint16(len(user.Name))) // 非固定长度数据需要定义读取长度
	_ = binary.Write(e.w, binary.LittleEndian, []byte(user.Name))

	_ = binary.Write(e.w, binary.BigEndian, user.Age)
	_ = binary.Write(e.w, binary.LittleEndian, user.IsSingle)
}

type Decoder struct {
	r io.Reader
}

func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{r: r}
}

func (d Decoder) decode(user *User) {
	var namelen uint16
	_ = binary.Read(d.r, binary.LittleEndian, &namelen)
	nameBytes := make([]byte, namelen)
	_ = binary.Read(d.r, binary.LittleEndian, &nameBytes)
	user.Name = string(nameBytes)

	var age int64
	_ = binary.Read(d.r, binary.BigEndian, &age)
	user.Age = age

	var single bool
	_ = binary.Read(d.r, binary.LittleEndian, &single)
	user.IsSingle = single
}

func main() {
	user1 := &User{
		Name:     "小A",
		Age:      20,
		IsSingle: true,
	}
	var buf bytes.Buffer
	base64Writer := base64.NewEncoder(base64.StdEncoding, &buf)
	compressor := gzip.NewWriter(base64Writer)
	encoder := NewEncoder(compressor)
	encoder.encode(user1) // 编码

	_ = compressor.Close()
	bin := buf.Bytes()

	base64Reader := base64.NewDecoder(base64.StdEncoding, bytes.NewReader(bin))
	decompressor, _ := gzip.NewReader(base64Reader)
	decoder := NewDecoder(decompressor)
	var newUser1 User
	decoder.decode(&newUser1) // 解码

	fmt.Println(newUser1)
}

写在最后

日常业务流程中,我们不可避免的需要与各种各样的序列化场景打交道(最常见的,日志上报),在使用序列化的过程中,我们需要时刻做到对数据量的把控,做到心中有数。避免出现因序列化缓影响线上正常业务请求。

问题解决,可以安心睡一觉了~~

在这里插入图片描述

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

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

相关文章

部署Git服务器

哈喽,大家好,本次为大家演示如何部署git服务器. 首先要准备gitea和phpstudy_pro phpstudy一路nest即可,可以帮你安装mysql和阿帕奇。 登录127.0.0.1:3000注册gitea即可。 如何在上传公钥的时候出现500的错误,加入这句便可解决…

SpringBoot+Vue+Element-ui实现文件下载

目录 1.后端代码部分 2.前端代码部分 3.效果展示 1.后端代码部分 GetMapping("downloadFile")public void downloadFile(RequestParam("filename") String filename,HttpServletResponse response) throws Exception {// 告知浏览器这是一个字节流&…

WebSocket connection to “wss://xxx/xxx“ failed

用了https后,需要用nginx作websocket地址的转发才能使用wss,别直接用端口访问 location /ws/ {proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade"; proxy_pass http://localhost:10001/…

JVM源码剖析之SymbolTable和StringTable

很多读者在观看JVM相关的书籍时会看到SymbolTable和StringTable,书中的三言二语介绍的不是很清楚,并且读者的水平有限,导致无法理解SymbolTable和StringTable。所以特意写此篇图文并茂的文章来彻底理解SymbolTable和StringTable这两张表。 版…

Java面试Day16

1.Dubbo 是什么?是否了解过它的架构设计? Dubbo官网:Apache Dubbo Dubbo是一个高性能、轻量级的开源Java RPC框架,它提供了完整的RPC协议栈,包括服务发布、服务引用、负载均衡、容错、服务治理和服务监控等功能&#…

构造函数使用初始化列表+模板

构造函数使用初始化列表模板 注意对应关系&#xff1a; Stack(int size) ;template<class DataType> inline Stack<DataType>::Stack(int size) : stacksize(size), top(0) {item new DataType[stacksize];if (item ! nullptr) cout << "成功初始化栈&…

再见!Fastjson!

你为何仍用Fastjson&#xff1f; 原因可以说出5678种&#xff0c;总而言之言而总之&#xff0c;你不&#xff08;敢&#xff09;切换的原因或许只有一个&#xff1a;Fastjson的静态方法调用用着省心&#xff1b;最重要的是&#xff0c;对其它JSON库&#xff08;如Jackson/Gson…

【生活分享】《哈姆雷特》· 苏州站 - 一次有趣的音乐剧体验

平时博客里大部分都是写技术相关的文章&#xff0c;感觉内容还是比较单一的&#xff0c;也想分享一下最近的一些有趣的生活~ 6月30号的时候&#xff0c;便订好了徐俊导演编排的7月2号场《哈姆雷特》音乐剧。而实际上&#xff0c;苏州场7月1号就上映了&#xff0c;感觉那天太晚……

C++图形开发(6):落下后能弹起的小球

文章目录 1.重复下落的小球2.落下后能弹起的小球3.能上下反弹的小球4.符合重力的能上下反弹的小球 今天我们来尝试实现一个落地后可以弹起的小球 1.重复下落的小球 首先&#xff0c;我们要来实现一个小球的重复下落 我们知道&#xff0c;在前面的代码中&#xff08;详见C图形…

云his源码:医疗卫生信息系统

伴随着以5G、云计算、大数据、人工智能和物联网等新兴技术的发展&#xff0c;医疗行业也呈现了数字化的发展趋势。如何让医疗领域中的服务提供方、需求方、支付方以及供应链等参与方实现业务协同及数据协同&#xff0c;是目前医疗行业所需的能力。 HIS系统&#xff0c;即“医疗…

点云数据分类及滤波方法

如何获取点云数据 传统的点云获取技术包括非接触式测量和接触式测量两种&#xff0c;它们的主要区别在于&#xff0c;在测量过程中测头是否与工件的表面相接触。 非接触式测量是利用光学原理的方法采集数据&#xff0c;例如结构光法、测距法以及干涉法等。该方法的优点在于测…

vue项目中的vue.config.js配置文件中的proxy代理post一直在预检

记录一个比较有意思的bug,帮别人调试遇到的&#xff0c;一个哥们一直在群里问了好几次同一个问题了&#xff0c;他配置了代理&#xff0c;请求一直在发送&#xff0c;postman测试没问题&#xff0c;可以成功接收到&#xff0c;但是在项目前端请求&#xff0c;确实一点响应没有&…

黑芝麻智能科技、上海紫先面试(部分)(未完全解析)

黑芝麻智能科技 Hystrix可以限流吗&#xff1f;客户端限流&#xff0c;是限制对下游&#xff08;被调用方&#xff09;的访问&#xff0c;不是对本服务限流。从HystrixCommand的.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)也可以看出来&#xff0c…

C++ -- 异常

文章目录 1. C语言传统处理2. C异常概念3. 异常的使用3.1 异常抛出并没有被捕获3.2 正确使用3.3 捕获异常采用类型匹配就近原则3.4 catch(...)可以捕获任意类型异常3.5 抛出派生类对象使用基类捕获3.6 异常重新抛出3.7 匹配规则3.8 异常规范3.9 异常安全 4. 自定义异常体系5. 异…

05.动态协议OSPF基础

文章目录 **OSPF简介****OSPF和RIP的区别****RIPV2和OSPFV2相同点****不同点** OSPF基础概念OSPF区域OSPF路由类型区域划分的要求**OSPF多区域****Router ID****Router ID选取规则****度量值** OSPF数据包OSPF状态机条件匹配 OSPF的工作过程OSPF基本配置**OSPF的拓展配置** OSP…

番外12:获取晶体管漏极电流源平面的仿真波形

番外12&#xff1a;获取晶体管漏极电流源平面的仿真波形 老是有同学反应&#xff0c;仿真中无法获取漏极电流源平面的仿真波形&#xff0c;在此给出解释与解决办法。 1、ADS能够访问到部分器件的电流源平面波形 对于某些器件&#xff0c;比如说CGH40010F&#xff0c;可以在仿…

硬核了解一下内核链表

一&#xff0c;内核链表定义 言以蔽之&#xff0c;内核链表就是个带头结点的循环双链表。 普通的链表是将next指针定义成为与该结构体一样的类型&#xff0c;这样做通用性不好。与普通的链表的定义和使用方式不一样&#xff0c;内核的链表定义成为了一种通用的结构&#xff1a…

vue打包到生产环境

1.进入到项目根目录执行 npm run build此时会自动打包在dist目录下 2.安装服务 npm install -g serve3.启动 serve dist以上是生产环境打包的过程。 npm run dev 是开发环境, npm run build 是生产环境

Springboot 错用list.stream , 遭遇list浅拷贝偷袭,实战图解

前言 相信很多看客都听闻过深拷贝、浅拷贝 &#xff0c; 但是在日常使用的过程中&#xff0c;是否真的有关心过或者遭遇过呢&#xff1f; 不啰嗦&#xff0c;一起来看看。 正文 接下来我通过示例&#xff0c;来复现一下 list.stream浅拷贝 这个事 &#xff1a; 首先是一个对象…

Nacos服务注册成功,RestTemplate调用服务提供者时空指针异常(已解决)

发现问题并进行记录 目录 项目场景&#xff1a;问题描述原因分析解决 项目场景&#xff1a; RestTemplate也可以做服务调用(进行测试) 目的:解决该问题,不考虑其他远程调用方式(Fegin,Dubbo) 项目Pom文件 Spring Boot ----2.6.3 Spring Cloud------2021.0.1 Spring Cloud Al…