如何進行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ù)量級,分別是每秒百萬級和每秒十萬級的吞吐量。
看完上述內(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)