手写RPC框架(十二)

本文深入探讨了protobuf、protostuff及kryo等序列化技术的特点与应用,并提供了详细的实现步骤与注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

v2.3

更新:使用protobuf和kryo替换netty原生的类序列化

  • protobuf

    protobuf(Google Protocol Buffers)是Google提供一个具有高效的协议数据交换格式工具库(类似Json),但相比于Json,Protobuf有更高的转化效率,时间效率和空间效率都是JSON的3-5倍。

    写这个要会写protobuf脚本 经过protobuf编译后的类,可以直接进行属性的获取和设置,没问题的 就不用写get set方法了

    • 创建相应的protoc文件,将所需转换的相关信息写入

      syntax = "proto3";//版本
      option java_outer_classname = "PersonPOJO";//生成的外部类名同时也是文件名
      //protobuf 使用message管理真正的数据
      message Person{
        string name = 1; //对应的参数类型 名称 属性序号
      }
      //千万记得指令中间的空格别少写或者多写
      //编写完成后使用指令  protoc.exe --java_out=. Person.proto   要到自己对应下载好的protoc.exe文件下
      
    • 去到相应的protoc文件夹下,将我们写好的文件复制到/bin目录下,通过 protoc.exe --java_out=. Person.proto 完成文件的编译 将编译后的文件保存至我们工程相应的位置 (编译后的文件,对应的代码就不粘了,太长了)

      //经过编译后的代码,如果要创建实例的话,采用下面的方法
      PersonPOJO.Person person = PersonPOJO.Person.newBuilder().setName("炸油条").build();
      
    • 进行对编解码器的设置 解码必须要传入实例

      case "protoc":  //添加protobuf的编解码器   如果是protobuf的编解码器的话 那可能还需要一点其他操作
                      if (returnType!=String.class&&parameterType!=String.class)
                      {
                          pipeline.addLast(new ProtobufEncoder());
                          //对什么实例解码
                          pipeline.addLast(new ProtobufDecoder(PersonPOJO.Person.getDefaultInstance()));
                      }
                      else if (returnType!=String.class&&parameterType==String.class)
                      {
                          //如果是客户端的话那么传出的是服务端传入的  所以这边编码那边就是解码
                          if (isConsumer)
                          {
                              //根据传入传出进行对应的编码
                              pipeline.addLast(new StringEncoder());
                              pipeline.addLast(new ProtobufDecoder(PersonPOJO.Person.getDefaultInstance()));;
                          }
                          else
                          {
                              pipeline.addLast(new ProtobufEncoder());
                              pipeline.addLast(new StringDecoder());
                          }
                      }
                      else if (returnType==String.class&&parameterType!=String.class)
                      {
                          //客户端 会对参数进行编码,服务端是解码
                          if (isConsumer)
                          {
                              pipeline.addLast(new ProtobufEncoder());
                              pipeline.addLast(new StringDecoder());
                          }
                          else
                          {
                              pipeline.addLast(new StringEncoder());
                              //这个就是获取对应的实例 必须要这样传
                              pipeline.addLast(new ProtobufDecoder(PersonPOJO.Person.getDefaultInstance()));;
                          }
                      }
                      else
                      {
                          //因为传入参数和传出都是字符串类型 所以就传入字符串编解码器
                          pipeline.addLast(new StringEncoder());
                          pipeline.addLast(new StringDecoder());
                      }
                      return;
      
    • 引入依赖

      <dependency>
          <groupId>com.google.protobuf</groupId>
          <artifactId>protobuf-java</artifactId>
          <version>3.6.1</version>
      </dependency>
      
  • protobuf的优化:protostuff是一个基于protobuf实现的序列化方法 速度和效率相较于protobuf大大提升搞懂
    它较于protobuf最明显的好处是,在几乎不损耗性能的情况下做到了 不用我们写.proto文件来实现序列化,注意使用后就不能再传递之前用过的proto文件了

    • protostuff实现序列化和反序列化的类

      package serialization.protostuff;
      
      import io.netty.buffer.ByteBuf;
      import io.netty.buffer.Unpooled;
      import io.protostuff.LinkedBuffer;
      import io.protostuff.ProtostuffIOUtil;
      import io.protostuff.Schema;
      import io.protostuff.runtime.RuntimeSchema;
      import serialization.Serializer;
      
      //进行序列化和反序列化
      public class ProtostuffUtils implements Serializer {
      
          /**
           * Avoid re applying buffer space every time serialization
           */
          private static final LinkedBuffer BUFFER = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
      
          @Override
          public byte[] serialize(Object obj) {
              Class<?> clazz = obj.getClass();
              Schema schema = RuntimeSchema.getSchema(clazz);
              byte[] bytes;
              try {
                  bytes = ProtostuffIOUtil.toByteArray(obj, schema, BUFFER);
              } finally {
                  BUFFER.clear();
              }
              return bytes;
          }
      
          @Override
          public <T> T deserialize(byte[] bytes, Class<T> clazz) {
              Schema<T> schema = RuntimeSchema.getSchema(clazz);
              T obj = schema.newMessage();
              ProtostuffIOUtil.mergeFrom(bytes, obj, schema);
              return obj;
          }
      }
      
      
    • 在处理器处理前后根据是否用的protostuff序列化 还有传入的是否是非string类参数 进行 相应的处理

      if (Serialization.class.getAnnotation(CodecSelector.class).Codec()
                      .equals("protostuff")
                      &&msg.getClass()==byte[].class)
              {
                  ProtostuffUtils protostuffUtils = new ProtostuffUtils();
                  //反序列化的模板 是根据我传进来的参数进行改变的
                  msg = protostuffUtils.deserialize((byte[]) msg,param.getClass());
              }
              response = msg;
              notify();
      
    • 引入依赖

      <dependency>
          <groupId>io.protostuff</groupId>
          <artifactId>protostuff-runtime</artifactId>
          <version>1.3.8</version>
      </dependency>
      <dependency>
          <groupId>io.protostuff</groupId>
          <artifactId>protostuff-core</artifactId>
          <version>1.3.8</version>
      </dependency>
      
  • kryo

    注意:不支持跨语言,只支持Java Object的序列化

    • Kryo工具类

      package serialization.kryo;
      
      import com.esotericsoftware.kryo.Kryo;
      import com.esotericsoftware.kryo.io.Input;
      import com.esotericsoftware.kryo.io.Output;
      import entity.Person;
      import serialization.Serializer;
      
      import java.io.ByteArrayInputStream;
      import java.io.ByteArrayOutputStream;
      
      
      //通过KryoUtils实现序列化
      public class KryoUtils implements Serializer {
      
      
          /**
           * Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
           */
          private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
              Kryo kryo = new Kryo();
              // kryo.register(Person.class); // 注册了能提高性能 减少了空间的 浪费 /但如果分布式系统中注册 不同的顺序可能导致错误  
              // kryo.register(PersonPOJO.Person.class); //不能使用这个
              kryo.setRegistrationRequired(false); //显式的关闭了注册的行为
              return kryo;
          });
      
          @Override
          public byte[] serialize(Object obj) {
              //将其序列化  然后写入到输出流中
              //byte数组输出流
              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
              //需要的参数有Output是它自带的 要创建一个
              Output output = new Output(byteArrayOutputStream);
              Kryo kryo = kryoThreadLocal.get();
              kryo.writeObject(output,obj);
              kryoThreadLocal.remove();
              return output.toBytes();
          }
      
          @Override
          public <T> T deserialize(byte[] bytes, Class<T> clazz) {
              //根据传入的bytes就知道里面是什么了  然后读取 转换
              Kryo kryo = kryoThreadLocal.get();
              //需要的参数有Input是它自带的
              ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
              Input input = new Input(byteArrayInputStream);
              Object obj = kryo.readObject(input, clazz);
              kryoThreadLocal.remove();
              return (T) obj;
          }
      }
      

      在处理器的实现逻辑和protostuff一致,就不粘贴了,去看下原理,然后集成下,更加轻便的使用,否则看起来太繁琐了

    • 引入对应依赖

      <!--引入kryo依赖-->
      <dependency>
          <groupId>com.esotericsoftware</groupId>
          <artifactId>kryo</artifactId>
          <version>5.3.0</version>
      </dependency>
      
  • 简化处理器判断流程

    • private boolean isProtostuff;
         private boolean isKryo;
      
    //当成功建立 就赋值上下文对象
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        context = ctx;
        System.out.println("U•ェ•*U 成功连接");
        String codecType = Serialization.class.getAnnotation(CodecSelector.class).Codec();
        if (codecType.equals("protostuff"))isProtostuff = true;
        else if (codecType.equals("kryo"))isKryo = true;
    }
​```
  
​```
  • BUG

    • 做好相应protoc序列化后,服务端成功注册上后,客户端启动后出现问题

      • 做好相应protoc序列化后,服务端成功注册上后,客户端启动后出现问题

      • 解决方法 应该就是要在编解码器处传入对应要传输的数据

    • 因为重载了多个方法,所以在通过反射实现方法的时候,选取哪个方法出现了问题。
      在这里插入图片描述

      • 解决
      Class<?> calledClass = Class.forName("provider.api."+methodName + "ServiceImpl");
              Method[] methods = calledClass.getMethods();
              Method method = methods[0];
              //因为我进行重写了 内部会有多个实现方法  所以就按照对应的传入参数 来判断是哪个方法
      		//因为methods[i].getParameterTypes()返回的就是class集合 我不需要再去获取对应class的类了 ,我刚刚就是搞错了 又获取了一遍类 导致出错
              for (int i = 0; i < methods.length; i++) {
                  if (methods[i].getParameterTypes()[0]==msg.getClass())
                  {
                      method = methods[i];
                      break;
                  }
              }
              Object instance = calledClass.newInstance();
              Object response = method.invoke(instance, msg);
              //获得对应信息并进行回传
              ctx.writeAndFlush(response);
      
    • 通过protostuff转换成的byte[] 无法传输,因为writeandflush需要进行编解码对byte[]

      • 解决进行大改:当传入的不是String.class实例的时候 才进行序列化,不管是序列化还是反序列化,都是根据他是不是byte[]实例才进行,在添加编解码器的时候,就需要注意了
      • 同时遇到一个问题,当我用到protostuff之后传入的类就不需要进行proto编译了,加入编译的类反而还报错
    • kryo消费者接收回信时,出现问题 com.esotericsoftware.kryo.io.KryoBufferUnderflowException: Buffer underflow.

      • 解决:莫名其妙就好了…
    • 注意 kryo不支持包不含无参构造器的反序列化

  • 待解决问题,不能只针对一个类别可以序列化 ,要实现多个类别序列化

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值