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&¶meterType!=String.class) { pipeline.addLast(new ProtobufEncoder()); //对什么实例解码 pipeline.addLast(new ProtobufDecoder(PersonPOJO.Person.getDefaultInstance())); } else if (returnType!=String.class&¶meterType==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&¶meterType!=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不支持包不含无参构造器的反序列化
-
-
待解决问题,不能只针对一个类别可以序列化 ,要实现多个类别序列化