1. 程式人生 > >Netty protobuf 整合 -- 使protobuf可同時處理多種型別

Netty protobuf 整合 -- 使protobuf可同時處理多種型別

簡要介紹

在頻寬固定的情境下,壓縮訊息大小可以提升網路傳輸效率。另外,如果訊息需要經過多個元件,那麼收益更為可觀。

訊息序列化一般不會採用jdk自帶的Serializable,更多的會採用thrift或者protobuf來做編解碼。

thrift

protobuf

Netty 整合protobuf困境

在做序列化的時候,往往遇上一種困境,在pipeline新增編解碼的時候,只能新增一種資料型別編解碼。

如下所示

pipeline.addLast(new ProtobufDecoder(LoginAsk.getDefaultInstance));
pipeline.addLast(new ProtobufEncoder());

上述的程式碼只能接收LoginAsk,無法處理其他型別的請求。

有一種較為直接的方式,開啟多個埠,每一個埠處理特定的業務,不同訊息之間用netty代理轉發。當業務需要經常和其他型別互動時,由於多了多次轉發,程式碼會變得複雜而不可控。

解決思路

ProtobufDecoder只接受一種例項化方式,傳遞特定的class,所以只能從這個型別突破。很容易想到,將這個類作為轉發器,即,能通過這個類的特定欄位定位到特定的class,並且包含body(結合class反序列化成instance)。

由此可得,訊息協議可以被設計成head,body,tail三部分。head可包含訊息型別msgType,訊息長度msgLen,業務訊息全限定名clazzName; body為業務資料序列化後的byte陣列。tail可選,可加入CRC校驗等功能。

現在可將問題轉換成,獲取到特定的className和byte陣列後,如何反序列化成instance。經過多次嘗試,需要經過以下步驟,通過反射修改構造器,並獲取到例項,然後通過例項反射呼叫方法獲取到parser。

程式碼如下:


// 通過classname獲取例項
public static Object getInstance(Class<?> clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
        boolean accessible = declaredConstructor.isAccessible();
        declaredConstructor.setAccessible(true);
        Object obj = declaredConstructor.newInstance();
        declaredConstructor.setAccessible(accessible);
        return obj;
}

   private static String PARSER_METHOD_NAME = "getParserForType";

// 反射呼叫獲取parser
    public static Parser reflectGetParser(Object instance) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<?> clazz = instance.getClass();
        Method[] declaredMethods = clazz.getDeclaredMethods();
        Method targetMethod = Stream.of(declaredMethods)
                .filter(method -> method.getName().equals(PARSER_METHOD_NAME))
                .findAny().get();
        Object invoke = targetMethod.invoke(instance);
        return (Parser) invoke;
    }

    public static Parser reflectGetParser(Class<?> clazz) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        Object instance = ReflectionHelper.getInstance(clazz);
        return reflectGetParser(instance);
    }

通過以上的步驟,修改了原有的訊息定義模式。將業務訊息加了一層包裝,在傳送訊息的時候,將業務訊息包裝成一個MsgWrapper序列化後傳送;在接收訊息的時候,解開頭部訊息,結合body,反序列化成特