如何進行g(shù)son替換fastjson引發(fā)的線上問題分析

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

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比祥符網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式祥符網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋祥符地區(qū)。費用合理售后完善,十多年實體公司更值得信賴。

前言

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

 

問題描述

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

 

問題分析

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

直接用一個簡單的例子,來說明當(dāng)時的問題。模擬線上的數(shù)據(jù)特性,使用 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);
 

觀察打印結(jié)果:

gson:

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

fastjson:

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

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

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

 

壓縮比測試

  • 序列化對象:包含大量的屬性。以模擬線上的業(yè)務(wù)數(shù)據(jù)。

  • 重復(fù)次數(shù):200。即 List 中包含 200 個同一引用的對象,以模擬線上復(fù)雜的對象結(jié)構(gòu),擴大差異性。

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

  • 主要觀察各個序列化方式壓縮后的字節(jié)大小,因為這關(guān)系到網(wǎng)絡(luò)傳輸時的大??;次要觀察反序列后 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));
    }

}
 

輸出結(jié)果:

gson:
62810
false

fastjson:
4503
true

Java:
1540
true

Hessian2:
686
true
 

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

 

吞吐量測試

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

@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 竟然獨領(lǐng)風(fēng)騷,文本類序列化的吞吐量相比二進制序列化的吞吐量要高出一個數(shù)量級,分別是每秒百萬級和每秒十萬級的吞吐量。

 

整體測試結(jié)論

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

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對創(chuàng)新互聯(lián)的支持。

分享標(biāo)題:如何進行g(shù)son替換fastjson引發(fā)的線上問題分析
網(wǎng)址分享:http://muchs.cn/article6/pjpoog.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、Google、云服務(wù)器、標(biāo)簽優(yōu)化、搜索引擎優(yōu)化做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)