作者:fredalxin
地址:https://fredal.xin/kryo-quickstart
Kryo是一個高性能的序列化/反序列化工具,由于其變長存盤特性并使用了位元組碼生成機制,擁有較高的運行速度和較小的體積,在某些場景中成為了除Json、Protobuf之外的選擇,
依賴
首先我們引入maven的相關依賴:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
需要注意的是,由于kryo使用了較高版本的asm,可能會與業務現有依賴的asm產生沖突,這是一個比較常見的問題,只需將依賴改成:
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo-shaded</artifactId>
<version>4.0.2</version>
</dependency>
記錄型別資訊
這算是kryo的一個特點,可以把物件資訊直接寫到序列化資料里,反序列化的時候可以精確地找到原始類資訊,不會出錯,這意味著在寫readxxx方法時,無需傳入Class或Type類資訊,
相應的,kryo提供兩種讀寫方式,記錄型別資訊的writeClassAndObject/readClassAndObject方法,以及傳統的writeObject/readObject方法,
執行緒安全
kryo的物件本身不是執行緒安全的,所以我們有兩種選擇來保障執行緒安全,
使用Threadlocal來保障執行緒安全:
private static final ThreadLocal<Kryo> kryoLocal = new ThreadLocal<Kryo>() {
protected Kryo initialValue() {
Kryo kryo = new Kryo();
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(
new StdInstantiatorStrategy()));
return kryo;
};
};
或者使用kryo提供的pool:
public KryoPool newKryoPool() {
return new KryoPool.Builder(() -> {
final Kryo kryo = new Kryo();
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(
new StdInstantiatorStrategy()));
return kryo;
}).softReferences().build();
}
實體化器
在上面注意到kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); 這句話顯示指定了實體化器,
在一些依賴了kryo的開源軟體中,可能由于實體化器指定的問題而拋出空指標例外,例如hive的某些版本中,默認指定了StdInstantiatorStrategy,
public static ThreadLocal<Kryo> runtimeSerializationKryo = new ThreadLocal<Kryo>() {
@Override
protected synchronized Kryo initialValue() {
Kryo kryo = new Kryo();
kryo.setClassLoader(Thread.currentThread().getContextClassLoader());
kryo.register(java.sql.Date.class, new SqlDateSerializer());
kryo.register(java.sql.Timestamp.class, new TimestampSerializer());
kryo.register(Path.class, new PathSerializer());
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
......
return kryo;
};
};
而StdInstantiatorStrategy在是依據JVM version資訊及JVM vendor資訊創建物件的,可以不呼叫物件的任何構造方法創建物件,
那么例如碰到ArrayList這樣的物件時候,就會出問題,觀察一下ArrayList的原始碼:
public ArrayList() {
this.elementData = https://www.cnblogs.com/javastack/archive/2021/06/28/DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
既然沒有呼叫構造器,那么這里elementData會是NULL,那么在呼叫類似ensureCapacity方法時,就會拋出一個例外,
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length
&& !(elementData =https://www.cnblogs.com/javastack/archive/2021/06/28/= DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;
grow(minCapacity);
}
}
解決方案很簡單,就如框架中代碼寫的一樣,顯示指定實體化器,首先使用默認無參構造策略DefaultInstantiatorStrategy,若創建物件失敗再采用StdInstantiatorStrategy,
類注冊
當kryo寫一個物件的實體的時候,默認需要將類的完全限定名稱寫入,將類名一同寫入序列化資料中是比較低效的,所以kryo支持通過類注冊進行優化,
kryo.register(SomeClassA.class);
kryo.register(SomeClassB.class);
kryo.register(SomeClassC.class);
注冊會給每一個class一個int型別的Id相關聯,這顯然比類名稱高效,但同時要求反序列化的時候的Id必須與序列化程序中一致,這意味著注冊的順序非常重要,
但是由于現實原因,同樣的代碼,同樣的Class在不同的機器上注冊編號任然不能保證一致,所以多機器部署時候反序列化可能會出現問題,
所以kryo默認會禁止類注冊,當然如果想要打開這個屬性,可以通過kryo.setRegistrationRequired(true);打開,
回圈參考
這是對回圈參考的支持,可以有效防止堆疊記憶體溢位,kryo默認會打開這個屬性,當你確定不會有回圈參考發生的時候,可以通過kryo.setReferences(false);關倍訓圈參考檢測,從而提高一些性能,
可變長存盤
kryo對int和long型別都采用了可變長存盤的機制,以int為例,一般需要4個位元組去存盤,而對kryo來說,可以通過1-5個變長位元組去存盤,從而避免高位都是0的浪費,
最多需要5個位元組存盤是因為,在變長存盤int程序中,一個位元組的8位用來存盤有效數字的只有7位,最高位用于標記是否還需讀取下一個位元組,1表示需要,0表示不需要,
在對string的存盤中也有變長存盤的應用,string序列化的整體結構為length+內容,那么length也會使用變長int寫入字符的長度,
配合快取使用的場景
在實際開發中,class增刪欄位是很常見的事情,但對于kryo來說,確是不支持的,而如果恰好需要使用快取,那么這個問題會被放得更大,
例如一個物件使用kryo序列化后,資料放入了快取中,而這時候如果這個物件增刪了一個屬性,那么快取中反序列化的時候就會報錯,所以頻繁使用快取的場景,可以盡量避免kryo,
不過現在的Kryo提供了兼容性的支持,使用CompatibleFieldSerializer.class,在kryo.writeClassAndObject時候寫入的資訊如下:
class name|field length|field1 name|field2 name|field1 value| filed2 value
而在讀入kryo.readClassAndObject時,會先讀入field names,然后匹配當前反序列化類的field和順序再構造結果,
當然如果在做好快取隔離的情況下,這一切都不用在意,
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.終于靠開源專案弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式發布,全新顛覆性版本!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288616.html
標籤:其他
