Jackson 序列化的隐性成本
我们常以为接口的瓶颈在数据库或业务逻辑,但在高并发、海量请求下,真正吞噬 CPU 的,可能是“把对象变成 JSON”的那一步。当监控把序列化时间单独拆出来,你会惊讶它能让账单失控。这篇《The Hidden Cost of Jackson Serialization》对我启发很大:默认好用的 Jackson,在某些场景可能成为热路径的成本中心。下面顺手分享给大家参考,以下内容翻译整理自 《The Hidden Cost of Jackson Serialization》。
Jackson 很强大,直到你看到它真正让你付出了什么代价。我们的 REST API 正在大把大把的花钱。每个 JSON 响应要消耗 3–5ms 的 CPU 时间。把它乘以每天 5000 万次请求,你就会得到一张能让 CTO 掉眼泪的 AWS 账单。罪魁祸首?Jackson。Java 生态里最流行的 JSON 库,那个大家几乎不假思索就会用的默认选项。
事情是怎么开始的?
我们有一个标准的 Spring Boot 微服务,很普通。
@RestController
public class UserController {@GetMapping("/users/{id}")public User getUser(@PathVariable Long id) {return userService.findById(id);}
}
干净、简单,跟每篇 Spring Boot 教程教你的几乎一样。
Spring Boot 默认用 Jackson 把 Java 对象转换成 JSON。你不用配置什么,它就能工作。
直到你看了指标数据。
当头棒喝
我们的监控面板显示出一些奇怪的东西:
- 数据库查询时间:8ms
- 业务逻辑:2ms
- JSON 序列化:47ms
等等,什么?
实际工作只花了 10ms。把结果转换成 JSON 花了 47ms。就像你做饭用了 2 分钟,装盘却花了 10 分钟。
我以为是测量误差,于是跑了一个 profiler。
Method Time Calls
-------------------------------- ------- -------
Jackson.writeValueAsString() 47ms 1
UserService.findById() 8ms 1
不是。Jackson 确实在每次请求里,用 47ms 序列化一个简单的 User 对象。
排查开始
我抓起我们的 User 实体,看了看:
@Entity
public class User {private Long id;private String email;private String firstName;private String lastName;@OneToMany(fetch = FetchType.EAGER)private List<Order> orders;@OneToMany(fetch = FetchType.EAGER)private List<Address> addresses;@ManyToMany(fetch = FetchType.EAGER)private List