Netty protobuf 整合 -- 使protobuf可同时处理多种类型

浪尽此生 提交于 2020-03-02 07:42:08

简要介绍

在带宽固定的情境下,压缩消息大小可以提升网络传输效率。另外,如果消息需要经过多个组件,那么收益更为可观。

消息序列化一般不会采用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,反序列化成特定消息的示例。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!