Golang Go语言中 json.Unmarshal 深拷贝性能太差怎么办?

发布于 1周前 作者 yibo5220 来自 Go语言
  1. 项目 A 需要使用项目 B 提供的配置文件
  2. 项目 B 提供的配置文件是以 json 字符串格式存在,保存在 redis
  3. redis 中的配置文件是会变化的
  4. A 项目需要频繁使用配置文件,就需要不断做 json.Unmarshal()。性能很差
  5. 如果把 json.Unmarshal() 之后的 object 保存在内存中,减少 json.Unmarhsal() 的操作。不同 goroutine 拿到的 object 就是浅拷贝的,并发不安全。

大佬们如何解决这个问题呢?


Golang Go语言中 json.Unmarshal 深拷贝性能太差怎么办?

更多关于Golang Go语言中 json.Unmarshal 深拷贝性能太差怎么办?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

23 回复

性能很差是多差? cpu 满了吗?

配置更新的频率是多高?必须要每次用都从 redis 中取吗? 5 分钟刷新一次可不可以,发布订阅模式更新可不可以?

每次用都是要整个 json 配置吗,能否拆出高频变化的 key 单独存 redis ?

更多关于Golang Go语言中 json.Unmarshal 深拷贝性能太差怎么办?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


具体一点呢 数据呢。json 多大,耗时多久,cpu 占用多少?

一般是通过人来解决而不是代码,大家约定好配置只读不可以改。
很在乎这个问题的话,如果调用的频率不是特别高,可以直接换 jsoniter/sonic ,对于大部分业务来说性能足够了

一般来说都是本地 cache 下,过期时间看业务。

如果实在怀疑是 Unmarshal 问题,欢迎使用我厂的 https://github.com/bytedance/sonic

似乎可以试试 sonic ?不需要你手动做缓存啥的方案了。
https://github.com/bytedance/sonic/blob/main/docs/INTRODUCTION_ZH_CN.md

“如果把 json.Unmarshal() 之后的 object 保存在内存中,减少 json.Unmarhsal() 的操作。不同 goroutine 拿到的 object 就是浅拷贝的,并发不安全。”

你这 json 配置难道解析后还要修改?不修改的话解析完成设置变量的时候上锁写个 singleton 不就好了,怎么会并发不安全

如果配置很大, 可以考虑在 Redis 里加个 config_version 字段, 项目 B 在修改 config 的时候先修改 config_version. 项目 A 先比较内存里的 config_version 和 redis 里的 config_version, 不一致再去更新配置.

结构固定直接走代码生成不就行了

建议代码生成,Java 的 getter 有点过时了,考虑 Record 模式吧😋😁

Redis 是支持订阅的可以让 a 在更改的时候发布一下

性能有问题肯定是这个 Json 太大了。这么大的数据,就不能用 hash 存,非得 string ?

完全不能理解 因为频繁使用配置文件, 然后 json.Unmarshal 导致 性能差
你一天是要读几百亿次配置文件么, 不然怎么会有性能差的问题?

感谢大佬回复。具体细节已 append 了。

其实核心问题是:
每个请求来的时候,需要使用配置,使用配置时是否使用同一个对象
如果使用同一个对象,如何保证没有开发去写这个对象。(比如 config 中有 map ,如何不让开发去写,在代码层面一定程度上控制就行)
如果不使用同一个对象,如何更节省资源地深拷贝一个对象。

redis 里的信息有并发问题就加锁呗,不是说你从一个大 string 换成 hash 才出现的问题,即使是使用大 string ,你 unmarshal 后修改,再写进去也有并发问题啊;
另一个方面,json unmarshal 慢是因为用到了反射,如果你事先知道 struct 的具体结构的话,其实用 easyjson 应该是最快的,但是有额外代码生成,op 可以了解一下 easyjson

要不试试 fastjson: https://github.com/valyala/fastjson
这个不转化为 object ,直接操作 JSON 字符串,占用资源和内存更小,性能也更高。

写 go 的应该会背 sync map 的八股文吧,
为什么有并发的情况下不抄这个的实现,
而要是每次都 Unmarshal json?

外面封装下,修改内容的时候才做一份拷贝, 如果是读取直接引用就可以。

我就说一个问题吧,配置只要加一个版本号,单独通过 redis 读 versionKey 就可以确保 config 是否修改,是否需要进行一次刷新,你配置不会天天改,5 分钟才检查一次,如果有修改才运行一次你说的慢操作,这有多大开销?

如果大量业务依赖这个配置本身,需要尽可能的保证实时性,直接上队列消费不是很好嘛。

不嫌麻烦其实可以自己在配置的结构体上写个 Copy 方法的,硬编码的 Copy 连反射都不用就没性能问题;

一个配置,竟然会有人往里写?人的问题很大?

go 官方这个原生 json 库性能确实不太高。普通业务没什么问题,高并发场景官方库性能真不行,比如做日志实时消费不停处理 json 这种,我们压测性能确实一般,不要迷性官方库一定就性能好。

在Go语言中,使用json.Unmarshal进行深拷贝确实可能带来性能上的开销,尤其是当处理的数据量较大或需要频繁操作时。以下是一些优化建议:

  1. 避免不必要的序列化与反序列化: 如果可能,直接操作数据结构而不是通过JSON进行拷贝。直接复制结构体或切片通常比通过JSON序列化和反序列化要快得多。

  2. 使用高效的深拷贝库: 考虑使用专门的深拷贝库,如copystructuredeepcopy,这些库针对深拷贝进行了优化,性能通常优于json.Unmarshal

  3. 优化数据结构: 如果数据结构复杂且嵌套层级多,尝试简化结构,减少嵌套层级,这可以减少序列化和反序列化的开销。

  4. 缓存结果: 如果数据不会频繁变化,可以考虑缓存序列化或反序列化的结果,避免重复操作。

  5. 使用高效的编码/解码方式: 除了JSON,还可以考虑使用其他高效的编码/解码方式,如Protobuf、MessagePack等,这些格式通常具有更高的性能。

  6. 并发处理: 如果处理的数据量大且操作时间长,可以考虑使用并发处理来分担负载,虽然这不会直接提高单次操作的性能,但可以提升整体吞吐量。

综上所述,根据具体应用场景选择合适的方法进行优化,可以有效提升性能。如果问题依然存在,建议对性能瓶颈进行详细分析,以便找到更具体的优化方案。

回到顶部