【問題排查】fastjson線上排坑記

前言

版本上線時發現fastjsontoString方法的返回的字符串與與以前版本的toString方法返回的字符串不相同,這致使依賴toString進行md5計算所獲得的結果不相同,更進一步致使其餘依賴該md5值的插件發現和以前的md5值不相等而重啓,致使數據存在丟失狀況。html

源碼

從項目中抽取出該模塊代碼,並進行了適當修改,但未改變整個處理邏輯,源碼以下。java

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運行上述程序,獲得結果以下。git

  • 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 ==> 87d74d87982fe1063a325c5aa97a9ef5json

格式化JSON字符串以下app

{"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源碼分析傳送門code

解決方案

由前面分析可知,直接使用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值也是徹底相同的,即驗證了方案的正確性。

總結

在遇到問題時,特別是現網問題時,須要冷靜分析,大膽猜測,當心求證,一點點找到突破口,此次的排坑過程大體如上所記錄。

相關文章
相關標籤/搜索