一次gson序列化导致高CPU使用问题

背景

在压测过程中,发现服务器的CPU使用率较高,造成整体的QPS和服务器的性能下降,需要进一步要分析原因,因此展开了相关分析。

如下图所示,CPU平均利用率在70%以上

分析

首先通过top等命令查询了具体的CPU耗费线程,发现很奇怪的是没有出现特别高的占用线程,但是会存在大量占用率不高的线程(占用值不多,但是数量比较多)

因此进一步展开使用jstack进行线程状态的统计分析,发现结果图下所示,存在大量的block线程:

打开看具体的堆栈后,结果如图所示,发现大量线程卡在了gson的这个反序列化方法上:

由于进一步查看了gson相关的文档和issue,发现已经有相关的issue如下:

至此问题应该比较清晰了,看来就是gson反序列化的时候,因为使用了dateFormater,且由于线程安全考虑进行了上锁,导致可能在大量反序列化日期数据的时候会造成线程阻塞,而由于jdk锁的升级机制,这里可能存在一些自旋锁在里面,加上大量的线程切换开销,导致了CPU升高。

具体也进一步查看了gson相关的源码,发现确实如此 (gson 2.8.x):

 
  1. // These methods need to be synchronized since JDK DateFormat classes are not thread-safe
  2. // See issue 162
  3. @Override
  4. public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
  5. synchronized (localFormat) {
  6. String dateFormatAsString = enUsFormat.format(src);
  7. return new JsonPrimitive(dateFormatAsString);
  8. }
  9. }

解决方案

那么清楚了原因后,问题就比较简单了。对于我们服务来说,之所以会产生这个现像主要是有个权限校验的切面类,里面的数据有几个日期字段,那么由于压测过程中线程较多因此导致冲突,所以会出现频繁加锁block的情况。

但是比较有意思的是,本来觉得这个可能是gson的一些bug,因此进一步查了相关文档和版本情况,发现google对这块有自己的看法如下:

看起来简单的升级一下可能并不能很好的解决问题,因此这里存在两个解决方案:

  1. 自定义反序列化的dateFormatter方法 (以不加锁的方式来处理,但是要注意考虑线程安全)
  2. 切换json序列化工具到其他lib

由于我们项目本身是引入了fastjson的,所以我们选择的方案2来处理,在修改后,相关的CPU使用和线程统计如下所示,发现问题解决(CPU使用率大概降低了20%左右):
 

至此,问题解决,但是考虑到部分小伙伴可能不想切换json序列化工具,且gson本身也是一个很优秀的json处理工具,也附上github上看到的一个自定义format的解决方案供参考(详见参考资料第3个链接):

 
  1. public class DateTimeSerializer implements JsonSerializer<Date>, JsonDeserializer<Date> {
  2. private String dateFormat;
  3. public DateTimeSerializer(String dateFormat) {
  4. this.dateFormat = dateFormat;
  5. }
  6. @Override
  7. public JsonElement serialize(Date date, Type typeOfSrc, JsonSerializationContext context) {
  8. // 这里是重点!!!
  9. // uses FastDateFormat singleton
  10. return new JsonPrimitive(DateFormatUtils.format(date, dateFormat));
  11. }
  12. @Override
  13. public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
  14. throws JsonParseException {
  15. try {
  16. return DateUtils.parseDate(json.getAsString(), dateFormat);
  17. } catch (ParseException e) {
  18. return null;
  19. }
  20. }
  21. }
  22. ----下面是使用初始化的地方----
  23. public class MyClass {
  24. private static Gson gson = new GsonBuilder()
  25. .registerTypeAdapter(Date.class, new DateTimeSerializer("yyyy-MM-dd HH:mm:ss"))// any format is fine
  26. .registerTypeAdapter(java.sql.Date.class, new DateTimeSerializer("yyyy-MM-dd HH:mm:ss"))
  27. .create();
  28. public static String foo_serialize(Object bar) {
  29. return gson.toJson(bar);
  30. }
  31. public static Bar foo_deserialize(String bar) {
  32. return gson.fromJson(bar,Bar.class);
  33. }
  34. }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有马大树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值