天天看點

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

前言

版本上線時發現

fastjson

toString

方法的傳回的字元串與與之前版本的

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

    版本問題導緻。
  • 再者懷疑是由于上線時将

    JDK

    1.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.7

JDK1.8

下運作結果可知

toString

方法傳回的結果并不相同,這也就導緻

md5

計算的不相同,進一步導緻其他依賴性的問題。

更進一步

當使用

JSONObject obj = new JSONObject();

建立

JSONObject

時,跟蹤源碼可以看到其會調用

JSONObject(int, boolean)

型構造函數,并且會使用

HashMap

維護插入的鍵值對,這是關鍵所在。

HashMap

JDK1.7

JDK1.8

中底層有不同的邏輯,

JDK1.8

的桶中會維護

連結清單 + 紅黑樹

結構,該結果是對

JDK1.7

的優化,

JDK1.7

中維護

連結清單

結構,在桶中元素較多而未達到再哈希的條件時查找效率會比較低下,而

JDK1.8

當桶中元素個數達到一定數量時會将連結清單轉化為紅黑樹,這樣便能提高查詢效率,有興趣的讀者可查閱

JDK1.7

JDK1.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();

即可。

{"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"} 

           

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

值也是完全相同的,即驗證了方案的正确性。

總結

在遇到問題時,特别是現網問題時,需要冷靜分析,大膽猜想,小心求證,一點點找到突破口,這次的排坑過程大緻如上所記錄。

PS:如果您覺得閱讀本文對您有幫助,請點一下

“推薦”

按鈕,您的

,将會是我不竭的動力!

作者:

leesf 掌控之中,才會成功;掌控之外,注定失敗。

出處:http://www.cnblogs.com/leesf456/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

如果覺得本文對您有幫助,您可以請我喝杯咖啡!

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