前言

版本上线时发现fastjsontoString方法的返回的字符串与与之前版本的toString方法返回的字符串不相同,这导致依赖toString进行md5计算所得到的结果不相同,更进一步导致其他依赖该md5值的插件发现和之前的md5值不相等而重启,导致数据存在丢失情况。

源码

从项目中抽取出该模块代码,并进行了适当修改,但未改变整个处理逻辑,源码如下。


package main; import com.alibaba.fastjson.JSONObject; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; public class Main {
public static void main(String[] args) {
JSONObject obj = new JSONObject(); obj.put("the_plugin_id", "the_plugin_id");
obj.put("the_plugin_name", "the_plugin_name");
obj.put("the_plugin_version", "the_plugin_version");
obj.put("the_plugin_md5", "the_plugin_md5");
obj.put("the_extend_info1", "the_extend_info1");
obj.put("the_extend_info2", "the_extend_info2");
obj.put("the_extend_info3", "the_extend_info3");
obj.put("the_extend_info4", "the_extend_info4"); System.out.println(obj.toString());
System.out.println("md5 ==> " + getMD5String(obj.toString()));
} private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z'}; static public String getMD5String(String source) { String retString = null; if (source == null) {
return retString;
} try {
StringBuffer sb = new StringBuffer();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(source.getBytes(), 0, source.length());
byte[] retBytes = md.digest();
for (byte b : retBytes) {
sb.append(hexDigits[(b >> 4) & 0x0f]);
sb.append(hexDigits[b & 0x0f]);
} retString = sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} return retString;
}
}

原因猜想

  • 首先怀疑是由于fastjson版本不一致的问题导致toString方法返回的字符串不相同,待比对jar后发现均依赖fastjson1.2.3版本,排除由于fastjson版本问题导致。
  • 再者怀疑是由于上线时将JDK1.7替换到1.8导致,即是由于JDK升级引起该问题,下面是验证过程。

分析验证

为验证是否是由于JDK升级导致该问题,分别使用不同JDK运行上述程序,得到结果如下。

  • JDK1.7运行结果

{"the_extend_info1":"the_extend_info1","the_plugin_version":"the_plugin_version","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_name":"the_plugin_name","the_plugin_id":"the_plugin_id","the_plugin_md5":"the_plugin_md5"}

md5 ==> 87d74d87982fe1063a325c5aa97a9ef5

格式化JSON字符串如下


{"the_extend_info1":"the_extend_info1","the_plugin_version":"the_plugin_version","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_name":"the_plugin_name","the_plugin_id":"the_plugin_id","the_plugin_md5":"the_plugin_md5"}
  • JDK1.8运行结果

{"the_plugin_md5":"the_plugin_md5","the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_version":"the_plugin_version"}

md5 ==> fc8f7f526f5f37141f2fea3a03950f52

格式化JSON字符串如下


{"the_plugin_md5":"the_plugin_md5","the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4","the_plugin_version":"the_plugin_version"}

对比JDK1.7JDK1.8下运行结果可知toString方法返回的结果并不相同,这也就导致md5计算的不相同,进一步导致其他依赖性的问题。

更进一步

当使用JSONObject obj = new JSONObject();创建JSONObject时,跟踪源码可以看到其会调用JSONObject(int, boolean)型构造函数,并且会使用HashMap维护插入的键值对,这是关键所在。

HashMapJDK1.7JDK1.8中底层有不同的逻辑,JDK1.8的桶中会维护链表 + 红黑树结构,该结果是对JDK1.7的优化,JDK1.7中维护链表结构,在桶中元素较多而未达到再哈希的条件时查找效率会比较低下,而JDK1.8当桶中元素个数达到一定数量时会将链表转化为红黑树,这样便能提高查询效率,有兴趣的读者可查阅JDK1.7JDK1.8的源码,JDK1.8源码分析传送门

解决方案

由前面分析可知,直接使用JSONObject obj = new JSONObject()的方法生成JSONObject对象时,其底层会使用HashMap维护键值对,而HashMap是和JDK版本相关的,所以最好的解决方案应该是能和JDK版本解耦的,而在JSONObject的构造函数中,可以自定义传入Map,这样就由指定Map维护插入的键值对。可使用LinkedHashMap来维护插入键值对,并且还会维护插入的顺序。这样便能保证在不同JDK版本下使用toString方法得到的字符串均相同。

方案验证

使用JSONObject obj = new JSONObject(new LinkedHashMap<String, Object>());代替之前的JSONObject obj = new JSONObject();即可。

  • JDK1.7运行结果

{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}

md5 ==> 5c7725cd161d53f1e25a6a5c55b62c1f

格式化JSON字符串如下


{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}
  • JDK1.8运行结果

{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}

md5 ==> 5c7725cd161d53f1e25a6a5c55b62c1f

格式化JSON字符串如下


{"the_plugin_id":"the_plugin_id","the_plugin_name":"the_plugin_name","the_plugin_version":"the_plugin_version","the_plugin_md5":"the_plugin_md5","the_extend_info1":"the_extend_info1","the_extend_info2":"the_extend_info2","the_extend_info3":"the_extend_info3","the_extend_info4":"the_extend_info4"}

对比在不同JDK下运行的结果,可以发现toString方法获得的字符串是完全相同的,md5值也是完全相同的,即验证了方案的正确性。

总结

在遇到问题时,特别是现网问题时,需要冷静分析,大胆猜想,小心求证,一点点找到突破口,这次的排坑过程大致如上所记录。

【问题排查】fastjson线上排坑记的更多相关文章

  1. 【转】Vue 脱坑记 - 查漏补缺(汇总下群里高频询问的xxx及给出不靠谱的解决方案)

    前言 文章内容覆盖范围,芝麻绿豆的破问题都有,不止于vue; 给出的是方案,但不是手把手一字一句的给你说十万个为什么! 有三类人不适合此篇文章: “喜欢站在道德制高点的圣母婊” – 适合去教堂 “无理 ...

  2. Vue 脱坑记

    问题汇总 Q:安装超时(install timeout) 方案有这么些: cnpm : 国内对npm的镜像版本 /* cnpm website: https://npm.taobao.org/ */ ...

  3. Spark踩坑记——从RDD看集群调度

    [TOC] 前言 在Spark的使用中,性能的调优配置过程中,查阅了很多资料,之前自己总结过两篇小博文Spark踩坑记--初试和Spark踩坑记--数据库(Hbase+Mysql),第一篇概况的归纳了 ...

  4. Spring Cloud Config采坑记

    1. Spring Cloud Config采坑记 1.1. 问题 在本地运行没问题,本地客户端服务能连上本地服务端服务,可一旦上线,发现本地连不上线上的服务 服务端添加security登录加密,客户 ...

  5. Spring @Transactional踩坑记

    @Transactional踩坑记 总述 ​ Spring在1.2引入@Transactional注解, 该注解的引入使得我们可以简单地通过在方法或者类上添加@Transactional注解,实现事务 ...

  6. Arduino+AS608指纹锁避坑记

    Arduino+AS608指纹锁避坑记 .title { text-align: center; margin-bottom: 0.2em } .subtitle { text-align: cent ...

  7. Spark踩坑记——Spark Streaming+Kafka

    [TOC] 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端,我们利用了spark strea ...

  8. Spark踩坑记——数据库(Hbase+Mysql)

    [TOC] 前言 在使用Spark Streaming的过程中对于计算产生结果的进行持久化时,我们往往需要操作数据库,去统计或者改变一些值.最近一个实时消费者处理任务,在使用spark streami ...

  9. .NET Core爬坑记 1.0 项目文件

    前言: 之所以要写这个系列是因为在移植项目到ASP.NET Core平台的过程中,遇到了一些“新变化”,这些变化有编译方面的.有API方面的,今天要讲的是编译方面的一些问题.我把它们整理后分享出来,以 ...

随机推荐

  1. JAVA设计模式:蝇量模式

    声明:转载请说明来源:http://www.cnblogs.com/pony1223/p/7554686.html 一.引出蝇量模式 现在假设有一个项目,这个项目是为公园设计一个景观的部署,那么这个时 ...

  2. Spring第四篇【Intellij idea环境下、Struts2和Spring整合】

    前言 Spring的第二和第三篇已经讲解了Spring的基本要点了[也就是Core模块]-本博文主要讲解Spring怎么与Struts2框架整合- Struts2和Spring的整合关键点: acti ...

  3. Android 之内容提供者 内容解析者 内容观察者

    contentProvider:ContentProvider在Android中的作用是对外提供数据,除了可以为所在应用提供数据外,还可以共享数据给其他应用,这是Android中解决应用之间数据共享的 ...

  4. 你的专属定制——JQuery自定义插件

        前  言 絮叨絮叨 jQuery是一个快速.简洁的JavaScript框架,是继Prototype之后又一个优秀的JavaScript代码库(或JavaScript框架).jQuery设计的宗 ...

  5. 《JavaScript闯关记》视频版硬广

    <JavaScript闯关记>视频版硬广 stone 在菜航工作时,兼任内部培训讲师,主要负责 JavaScript 基础培训,2016年整理的<JavaScript闯关记>课 ...

  6. js 倒计时(服务器时间同步)

    首先说一下,为什么要服务器时间同步, 因为服务器时间和本地电脑时间存在一定的时间差.有些对时效性要求非常高的应用,例如时时彩开奖,是不能容忍这种时间差存在的. 方案1:每次倒计时去服务端请求时间 // ...

  7. Lodop 动态加载模板,动态加载数据

    最近需要使用Lodop打印控件,所以就研究了一下,期间从网上找了诸多的东西,基本全是对HTML进行打印的,没有找到我想要的,就只好自己动手丰衣足食. 这篇文章主要讲述的是Lodop与数据的结合使用,官 ...

  8. Vue2.0 探索之路——生命周期和钩子函数

    beforecreate :可以在这加个loading事件 created :在这结束loading,还做一些初始化,实现函数自执行 mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些 ...

  9. 关于API,前后端分离

    之前再开放新型web项目和app时,遇到了和前后端交互的问题.总所周知的是,web前后端交接时,最重要的交互方式的接口的制定. 而关于接口的规定,衍生出了一大堆问题,第一是关于空值的制定,是不输出呢? ...

  10. 一张图讲解对象锁和关键字synchronized修饰方法

    每个对象在出生的时候就有一把钥匙(监视器),那么被synchronized 修饰的方法相当于给方法加了一个锁,这个方法就可以进行同步,在多线程的时候,不会出现线程安全问题. 下面通过一张图片进行讲解: ...