《從零開始搭建遊戲伺服器》 序列化工具(最優版Protostuff)

河樂不為發表於2017-06-10

前言:

之前使用protobuf工具來解析表格資料和定製網路協議,但是為了網路安全和壓縮資料大小,有時候需要對資料進行序列化,這就需要設計一個序列化工具類來完成序列化和反序列化的操作。

框架的對比:

Java中幾個常用的序列化框架對比,包括:kryoHessianProtostuffProtostuff-Runtimejava.io

框架 優點 缺點
kryo 速度快,序列化後體積小 跨語言支援比較複雜
Hessian 預設支援跨語言 速度比較慢
java.io JDK自帶功能,使用方便,可序列化所有類 速度慢,佔空間大
Protostuff 速度快,基於protobuf 需要靜態編譯
Protostuff-Runtime 無需靜態編譯,但序列化之前需要預先傳入Schema 不支援無預設建構函式的類,反序列化時需要使用者自己初始化序列化後的物件,而此工具只負責對初始化後的物件進行賦值

1.詳細分析:

  • protobuf 的一個缺點是需要資料結構的預編譯過程,首先要編寫 .proto 格式的配置檔案,再通過 protobuf 提供的工具生成各種語言響應的程式碼。由於java具有反射和動態程式碼生成的能力,這個預編譯過程不是必須的,可以在程式碼執行時來實現。有個 protostuff外掛 已經實現了這個功能。

  • protostuff 基於Google protobuf,但是提供了更多的功能和更簡易的用法。其中,protostuff-runtime 實現了無需預編譯對java bean進行protobuf序列化/反序列化的能力。protostuff-runtime的侷限是序列化前需預先傳入schema,反序列化不負責物件的建立只負責複製,因而必須提供預設建構函式。此外,protostuff 還可以按照protobuf的配置序列化成json/yaml/xml等格式。

2.坑點解決:

沒經過修改過的 protostuff,效能方法還是有些缺陷,這裡我在一篇關於 輕量級分散式 RPC 框架 部落格中找到了據說是當前效能最優的 優化版Protostuff 的使用案例。
在原生的 Protostuff 中通過反射例項化java類的時候,是通過使用 Class.newInstance() 方法來實現的,而前提就是這個類java類必須提供預設建構函式。
假如希望在java類不提供預設建構函式的時候也能實現反射例項化,可以選擇使用 objenesis 來例項化java類,使用方式可以參考 objenesis官網

3.框架選擇:

綜合上述的分析,最終我還是選擇了 Protostuff 框架來完成Protobuf資料的序列化,關於不支援無預設建構函式類序列化的缺陷接下來通過使用 objenesis 也會得到解決。

自定義序列化工具類

這裡我們建立此工具類,取名為 SerializationUtil,使用Protostuff來序列化和反序列化Protobuf資料:

1.庫引入:

首先要在pom.xml裡新增com.dyuproject.protostuffobjenesis的jar包:

 <dependency>  
     <groupId>com.dyuproject.protostuff</groupId>  
     <artifactId>protostuff-core</artifactId>  
     <version>1.0.8</version>  
 </dependency>  

 <dependency>  
     <groupId>com.dyuproject.protostuff</groupId>  
     <artifactId>protostuff-runtime</artifactId>  
     <version>1.0.8</version>  
 </dependency>  
 <!-- Objenesis -->
 <dependency>
     <groupId>org.objenesis</groupId>
     <artifactId>objenesis</artifactId>
     <version>2.1</version>
 </dependency>

2.工具類編寫:

主要包含兩個核心的函式:序列化函式 Serializer 和反序列化函式 Deserializer

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;

import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;

public class SerializationUtil {

    private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>();

    private static Objenesis objenesis = new ObjenesisStd(true);

    private SerializationUtil() {
    }

    @SuppressWarnings("unchecked")
    private static <T> Schema<T> getSchema(Class<T> cls) {
        Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
        if (schema == null) {
            schema = RuntimeSchema.createFrom(cls);
            if (schema != null) {
                cachedSchema.put(cls, schema);
            }
        }
        return schema;
    }

    @SuppressWarnings("unchecked")
    public static <T> byte[] serialize(T obj) {
        Class<T> cls = (Class<T>) obj.getClass();
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        try {
            Schema<T> schema = getSchema(cls);
            return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        } finally {
            buffer.clear();
        }
    }

    public static <T> T deserialize(byte[] data, Class<T> cls) {
        try {
            T message = (T) objenesis.newInstance(cls);
            Schema<T> schema = getSchema(cls);
            ProtostuffIOUtil.mergeFrom(data, message, schema);
            return message;
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
}

上面是引入 objenesis 之後的版本,優點就是支援沒有提供預設建構函式的java類的例項化,可以看到這裡通過 Class<T> cls = (Class<T>) obj.getClass(); 來例項化java類的。ConcurrentHashMap是適用於高併發的Map資料結構。

3.工具類呼叫:

隨便定義一個Protobuf的協議類,假設為User,下面就是具體的序列化工具使用操作:

  • 序列化:

    byte[] data = SerializationUtil.serialize(user,User.class);  
  • 反序列化:

    User user2 = SerializationUtil.deserialize(data,User.class); 

參考資料:

相關文章