背景
上一篇已經(jīng)介紹了定制了一個(gè)建議的RPC框架:簡(jiǎn)易R(shí)PC實(shí)現(xiàn),
之前的實(shí)現(xiàn)方式是Fastjson,但是為了進(jìn)一步優(yōu)化框架的性能。
幾種常見(jiàn)的序列化方式
- java自帶的序列化,對(duì)象繼承Serializable
- xml,json,fastjson
- Avro
- Protobuf
等等
一些簡(jiǎn)單的比較
Serializable
優(yōu)點(diǎn): java原生,java工程中出鏡率高
缺點(diǎn):性能差,空間占用多,在網(wǎng)絡(luò)通信中用得很少,需要指定serialVersionUID
xml,json,fastjson
優(yōu)點(diǎn):可讀性好,fastjson在速度上有優(yōu)勢(shì),json格式在建站,特別是對(duì)于性能要求不是很高的場(chǎng)景應(yīng)用很廣泛。
缺點(diǎn):xml,json性能和空間占用上都沒(méi)有優(yōu)勢(shì),fastjson在國(guó)內(nèi)用得很多,但是文檔不全,時(shí)間類的序列化有bug,國(guó)際上口碑一般。
avro
優(yōu)點(diǎn):Avro 是 Hadoop 的一個(gè)子項(xiàng)目,Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開(kāi)銷和解析性能方面可以和Protobuf媲美,動(dòng)態(tài)語(yǔ)言友好,Avro提供的機(jī)制使動(dòng)態(tài)語(yǔ)言可以方便地處理Avro數(shù)據(jù)。avro是基于schema(模式),這和protobuf、thrift沒(méi)什么區(qū)別
protobuf
優(yōu)點(diǎn):跨語(yǔ)言,可自定義數(shù)據(jù)結(jié)構(gòu)。二進(jìn)制消息,效率高,性能高。Netty等框架集成了該協(xié)議。序列化后碼流小,性能高。提供結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式(XML JSON等)
缺點(diǎn):二進(jìn)制格式,可讀性差(抓包dump后的數(shù)據(jù)很難看懂)
適用場(chǎng)景:
- 對(duì)性能要求高的RPC調(diào)用
- 具有良好的跨防火墻的訪問(wèn)屬性
- 適合應(yīng)用層對(duì)象的持久化
選型建議
受到這個(gè)文章的啟發(fā):
https://www.cnblogs.com/wkcode/p/10431096.html

分析上圖知:
- XML序列化(Xstream)無(wú)論在性能和簡(jiǎn)潔性上比較差。
- Thrift與Protobuf相比在時(shí)空開(kāi)銷方面都有一定的劣勢(shì)。
- Protobuf和Avro在兩方面表現(xiàn)都非常優(yōu)越。
不同的場(chǎng)景適用的序列化協(xié)議:
- 對(duì)于公司間的系統(tǒng)調(diào)用,如果性能要求在100ms以上的服務(wù),基于XML的SOAP協(xié)議是一個(gè)值得考慮的方案。
- 基于Web browser的Ajax,以及Mobile app與服務(wù)端之間的通訊,JSON協(xié)議是首選。對(duì)于性能要求不太高,或者以動(dòng)態(tài)類型語(yǔ)言為主,或者傳輸數(shù)據(jù)載荷很小的的運(yùn)用場(chǎng)景,JSON也是非常不錯(cuò)的選擇。
- 對(duì)于調(diào)試環(huán)境比較惡劣的場(chǎng)景,采用JSON或XML能夠極大的提高調(diào)試效率,降低系統(tǒng)開(kāi)發(fā)成本。
- 當(dāng)對(duì)性能和簡(jiǎn)潔性有極高要求的場(chǎng)景,Protobuf,Thrift,Avro之間具有一定的競(jìng)爭(zhēng)關(guān)系。
- 對(duì)于T級(jí)別的數(shù)據(jù)的持久化應(yīng)用場(chǎng)景,Protobuf和Avro是首要選擇。如果持久化后的數(shù)據(jù)存儲(chǔ)在Hadoop子項(xiàng)目里,Avro會(huì)是更好的選擇。
- 由于Avro的設(shè)計(jì)理念偏向于動(dòng)態(tài)類型語(yǔ)言,對(duì)于動(dòng)態(tài)語(yǔ)言為主的應(yīng)用場(chǎng)景,Avro是更好的選擇。
- 對(duì)于持久層非Hadoop項(xiàng)目,以靜態(tài)類型語(yǔ)言為主的應(yīng)用場(chǎng)景,Protobuf會(huì)更符合靜態(tài)類型語(yǔ)言工程師的開(kāi)發(fā)習(xí)慣。
- 如果需要提供一個(gè)完整的RPC解決方案,Thrift是一個(gè)好的選擇。
- 如果序列化之后需要支持不同的傳輸層協(xié)議,或者需要跨防火墻訪問(wèn)的高性能場(chǎng)景,Protobuf可以優(yōu)先考慮。
空間
Protobuf實(shí)現(xiàn)
結(jié)合以上信息,項(xiàng)目對(duì)于性能要求較高,決定使用Protobuf作為序列化協(xié)議。
Objenesis的使用
Java已經(jīng)支持使用class.newinstance()的類動(dòng)態(tài)實(shí)例化,但是必須要有一個(gè)合適的構(gòu)造函數(shù)。而很多場(chǎng)景下類不能夠用這種方式去實(shí)例化,例如:
構(gòu)造函數(shù)需要參數(shù)(Constructors that require arguments)
有副作用的構(gòu)造函數(shù)(Constructors that have side effects)
會(huì)拋出異常的構(gòu)造函數(shù)(Constructors that throw exceptions)
Objenesis objenesis = new ObjenesisStd(); // or ObjenesisSerializer
MyThingy thingy1 = (MyThingy) objenesis.newInstance(MyThingy.class);
// or (a little bit more efficient if you need to create many objects)
Objenesis objenesis = new ObjenesisStd(); // or ObjenesisSerializer
ObjectInstantiator thingyInstantiator = objenesis.getInstantiatorOf(MyThingy.class);
MyThingy thingy2 = (MyThingy)thingyInstantiator.newInstance();
MyThingy thingy3 = (MyThingy)thingyInstantiator.newInstance();
MyThingy thingy4 = (MyThingy)thingyInstantiator.newInstance();
核心代碼
public static <T> String serializer(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return new String(ProtostuffIOUtil.toByteArray(obj, schema, buffer));
} catch (Exception e) {
log.error("protobuf序列化失敗");
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
public static <T> T deserializer(byte[] bytes, Class<T> clazz) {
try {
T message = (T) objenesis.newInstance(clazz);
Schema<T> schema = getSchema(clazz);
ProtostuffIOUtil.mergeFrom(bytes, message, schema);
return message;
} catch (Exception e) {
log.error("protobuf反序列化失敗");
throw new IllegalStateException(e.getMessage(), e);
}
}
實(shí)驗(yàn)結(jié)果比較
測(cè)試對(duì)象:
@Data
public class TestObject {
String a = "123";
String a1 = "a1";
String a2 = "a2";
Integer b = 10;
Double c = 2.5;
List<String> d = Arrays.asList(new String[]{"1", "2"});
Map<String, String> e = new HashMap<String, String>(){
{
put("ak", "av");
}
};
Object f = null;
}
分別用fastjson和protobuf,序列化/反序列化 1000次:
fastjson si result:{"a":"123","a1":"a1","a2":"a2","b":10,"c":2.5,"d":["1","2"],"e":{"ak":"av"}}
fastjson desi result:TestObject(a=123, a1=a1, a2=a2, b=10, c=2.5, d=[1, 2], e={ak=av}, f=null)
fastjson cost:362
fastjson size:76
protebuf si result:
?123??a1??a2
)?@2?12?2;?
?ak??av<
protebuf desi result:TestObject(a=123, a1=a1, a2=a2, b=10, c=2.5, d=[1, 2], e={ak=av}, f=null)
protebuf cost:275
protebuf size:42
可以看到,
- 字符長(zhǎng)度和耗時(shí)(cost,單位毫秒),protobuf優(yōu)于fastjson
- fastjson的可讀性較好