中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行gson替換fastjson引發的線上問題分析

發布時間:2021-12-14 09:57:07 來源:億速云 閱讀:178 作者:柒染 欄目:大數據

如何進行gson替換fastjson引發的線上問題分析,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

前言

Json 序列化框架存在的安全漏洞一直以來都是程序員們掛在嘴邊調侃的一個話題,尤其是這兩年 fastjson 由于被針對性研究,更是頻頻地的報出漏洞,出個漏洞不要緊,可安全團隊總是用郵件催著線上應用要進行依賴升級,這可就要命了,我相信很多小伙伴也是不勝其苦,考慮了使用其他序列化框架替換 fastjson。這不,最近我們就有一個項目將 fastjson 替換為了 gson,引發了一個線上的問題。分享下這次的經歷,以免大家踩到同樣的坑,在此警示大家,規范千萬條,安全第一條,升級不規范,線上兩行淚。

 

問題描述

線上一個非常簡單的邏輯,將對象序列化成 fastjson,再使用 HTTP 請求將字符串發送出去。原本工作的好好的,在將 fastjson 替換為 gson 之后,竟然引發了線上的 OOM。經過內存 dump 分析,發現竟然發送了一個 400 M+ 的報文,由于 HTTP 工具沒有做發送大小的校驗,強行進行了傳輸,直接導致了線上服務整體不可用。

 

問題分析

為什么同樣是 JSON 序列化,fastjson 沒出過問題,而換成 gson 之后立馬就暴露了呢?通過分析內存 dump 的數據,發現很多字段的值都是重復的,再結合我們業務數據的特點,一下子定位到了問題 -- gson 序列化重復對象存在嚴重的缺陷。

直接用一個簡單的例子,來說明當時的問題。模擬線上的數據特性,使用 List<Foo> 添加進同一個引用對象

Foo foo = new Foo();
Bar bar = new Bar();
List<Foo> foos = new ArrayList<>();
for(int i=0;i<3;i++){
    foos.add(foo);
}
bar.setFoos(foos);

Gson gson = new Gson();
String gsonStr = gson.toJson(bar);
System.out.println(gsonStr);

String fastjsonStr = JSON.toJSONString(bar);
System.out.println(fastjsonStr);
 

觀察打印結果:

gson:

{"foos":[{"a":"aaaaa"},{"a":"aaaaa"},{"a":"aaaaa"}]}
 

fastjson:

{"foos":[{"a":"aaaaa"},{"$ref":"$.foos[0]"},{"$ref":"$.foos[0]"}]}
 

可以發現 gson 處理重復對象,是對每個對象都進行了序列化,而 fastjson 處理重復對象,是將除第一個對象外的其他對象使用引用符號 $ref 進行了標記。

當單個重復對象的數量非常多,以及單個對象的提交較大時,兩種不同的序列化策略會導致一個質變,我們不妨來針對特殊的場景進行下對比。

 

壓縮比測試

  • 序列化對象:包含大量的屬性。以模擬線上的業務數據。

  • 重復次數:200。即 List 中包含 200 個同一引用的對象,以模擬線上復雜的對象結構,擴大差異性。

  • 序列化方式:gson、fastjson、Java、Hessian2。額外引入了 Java 和 Hessian2 的對照組,方便我們了解各個序列化框架在這個特殊場景下的表現。

  • 主要觀察各個序列化方式壓縮后的字節大小,因為這關系到網絡傳輸時的大小;次要觀察反序列后 List 中還是不是同一個對象

public class Main {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Foo foo = new Foo();
        Bar bar = new Bar();
        List<Foo> foos = new ArrayList<>();
        for(int i=0;i<200;i++){
            foos.add(foo);
        }
        bar.setFoos(foos);
        // gson
        Gson gson = new Gson();
        String gsonStr = gson.toJson(bar);
        System.out.println(gsonStr.length());
        Bar gsonBar = gson.fromJson(fastjsonStr, Bar.class);
        System.out.println(gsonBar.getFoos().get(0) == gsonBar.getFoos().get(1));  
        // fastjson
        String fastjsonStr = JSON.toJSONString(bar);
        System.out.println(fastjsonStr.length());
        Bar fastjsonBar = JSON.parseObject(fastjsonStr, Bar.class);
        System.out.println(fastjsonBar.getFoos().get(0) == fastjsonBar.getFoos().get(1));
        // java
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(bar);
        oos.close();
        System.out.println(byteArrayOutputStream.toByteArray().length);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        Bar javaBar = (Bar) ois.readObject();
        ois.close();
        System.out.println(javaBar.getFoos().get(0) == javaBar.getFoos().get(1));
        // hessian2
        ByteArrayOutputStream hessian2Baos = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(hessian2Baos);
        hessian2Output.writeObject(bar);
        hessian2Output.close();
        System.out.println(hessian2Baos.toByteArray().length);
        ByteArrayInputStream hessian2Bais = new ByteArrayInputStream(hessian2Baos.toByteArray());
        Hessian2Input hessian2Input = new Hessian2Input(hessian2Bais);
        Bar hessian2Bar = (Bar) hessian2Input.readObject();
        hessian2Input.close();
        System.out.println(hessian2Bar.getFoos().get(0) == hessian2Bar.getFoos().get(1));
    }

}
 

輸出結果:

gson:
62810
false

fastjson:
4503
true

Java:
1540
true

Hessian2:
686
true
 

結論分析:由于單個對象序列化后的體積較大,采用引用表示的方式可以很好的縮小體積,可以發現 gson 并沒有采取這種序列化優化策略,導致體積膨脹。甚至一貫不被看好的 Java 序列化都比其優秀的多,而 Hessian2 更是夸張,直接比 gson 優化了 2個數量級。并且反序列化后,gson 并不能將原本是同一引用的對象還原回去,而其他的序列化框架均可以實現這一點。

 

吞吐量測試

除了關注序列化之后數據量的大小,各個序列化的吞吐量也是我們關心的一個點。使用基準測試可以精準地測試出各個序列化方式的吞吐量。

@BenchmarkMode({Mode.Throughput})
@State(Scope.Benchmark)
public class MicroBenchmark {

    private Bar bar;

    @Setup
    public void prepare() {
        Foo foo = new Foo();
        Bar bar = new Bar();
        List<Foo> foos = new ArrayList<>();
        for(int i=0;i<200;i++){
            foos.add(foo);
        }
        bar.setFoos(foos);
    }

    Gson gson = new Gson();

    @Benchmark
    public void gson(){
        String gsonStr = gson.toJson(bar);
        gson.fromJson(gsonStr, Bar.class);
    }

    @Benchmark
    public void fastjson(){
        String fastjsonStr = JSON.toJSONString(bar);
        JSON.parseObject(fastjsonStr, Bar.class);
    }

    @Benchmark
    public void java() throws Exception {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(bar);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        Bar javaBar = (Bar) ois.readObject();
        ois.close();
    }

    @Benchmark
    public void hessian2() throws Exception {
        ByteArrayOutputStream hessian2Baos = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(hessian2Baos);
        hessian2Output.writeObject(bar);
        hessian2Output.close();


        ByteArrayInputStream hessian2Bais = new ByteArrayInputStream(hessian2Baos.toByteArray());
        Hessian2Input hessian2Input = new Hessian2Input(hessian2Bais);
        Bar hessian2Bar = (Bar) hessian2Input.readObject();
        hessian2Input.close();
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
            .include(MicroBenchmark.class.getSimpleName())
            .build();

        new Runner(opt).run();
    }

}
 

吞吐量報告:

Benchmark                 Mode  Cnt        Score         Error  Units
MicroBenchmark.fastjson  thrpt   25  6724809.416 ± 1542197.448  ops/s
MicroBenchmark.gson      thrpt   25  1508825.440 ±  194148.657  ops/s
MicroBenchmark.hessian2  thrpt   25   758643.567 ±  239754.709  ops/s
MicroBenchmark.java      thrpt   25   734624.615 ±   66892.728  ops/s
 

是不是有點出乎意料,fastjson 竟然獨領風騷,文本類序列化的吞吐量相比二進制序列化的吞吐量要高出一個數量級,分別是每秒百萬級和每秒十萬級的吞吐量。

 

整體測試結論

  • fastjson 序列化過后帶有 $ 的引用標記也能夠被 gson 正確的反序列化,但筆者并沒有找到讓 gson 序列化時轉換成引用的配置
  • fastjson、hessian、java 均支持循環引用的解析;gson 不支持
  • fastjson 可以設置 DisableCircularReferenceDetect,關閉循環引用和重復引用的檢測
  • gson 反序列化之前的同一個引用的對象,在經歷了序列化再反序列化回來之后,不會被認為是同一個對象,可能會導致內存對象數量的膨脹;而 fastjson、java、hessian2 等序列化方式由于記錄的是引用標記,不存在該問題
  • 以筆者的測試 case 為例,hessian2 具有非常強大的序列化壓縮比,適合大報文序列化后供網絡傳輸的場景使用
  • 以筆者的測試 case 為例,fastjson 具有非常高的吞吐量,對得起它的 fast,適合需要高吞吐的場景使用
  • 序列化還需要考慮到是否支持循環引用,是否支持循環對象優化,是否支持枚舉類型、集合、數組、子類、多態、內部類、泛型等綜合場景,以及是否支持可視化等比較的場景,增刪字段后的兼容性等等特性。綜合來看,筆者比較推薦 hessian2 和 fastjson 兩種序列化方式
    

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

电白县| 郎溪县| 宁乡县| 千阳县| 丘北县| 本溪| 沧州市| 达尔| 峡江县| 陈巴尔虎旗| 罗平县| 太仓市| 沧州市| 许昌市| 景洪市| 昌图县| 黔江区| 买车| 闽侯县| 饶河县| 安宁市| 宁南县| 长沙县| 黔西| 闽侯县| 宁津县| 扎囊县| 中阳县| 三门峡市| 城步| 安西县| 海口市| 永城市| 七台河市| 凌源市| 彩票| 定安县| 和政县| 韶关市| 商丘市| 娄烦县|