李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
01.Netty优化之扩展序列化算法
Leefs
2022-06-21 PM
1507℃
0条
[TOC] ### 一、概念 序列化最终的目的是为了对象可以**跨平台存储**和**进行网络传输**,而一般进行跨平台存储和网络传输的方式就是 **IO**,而 IO 支持的数据格式就是**字节数组**。 将对象转成字节数组的时候需要制定一种规则,这种规则就是序列化机制。 + **序列化和反序列化概述** + **序列化**:就是把对象转化为可传输的字节序列过程; + **反序列化**:就是把字节序列还原为对象的过程。 ### 二、常见序列化方式 序列化只是定义了拆解对象的具体规则,那这种规则肯定也是多种多样的,比如现在常见的序列化方式有:`JDK 原生`、`JSON`、`ProtoBuf`、`Hessian`、`Kryo`等。 + ##### JDK原生 作为一个成熟的编程语言,JDK自带了序列化方法。只需要类实现了`Serializable`接口,就可以通过`ObjectOutputStream`类将对象变成byte[]字节数组。 JDK 序列化会把对象类的描述信息和所有的属性以及继承的元数据都序列化为字节流,所以会导致生成的字节流相对比较大。 另外,这种序列化方式是 JDK 自带的,因此不支持跨语言。 **JDK 原生的序列化方式生成的字节流比较大,也不支持跨语言,因此在实际项目和框架中用的都比较少。** + ##### ProtoBuf 谷歌推出的,是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于通信协议、数据存储等。序列化后体积小,一般用于对传输性能有较高要求的系统。 + ##### Hessian Hessian 是一个轻量级的二进制 web service 协议,主要用于传输二进制数据。 在传输数据前 Hessian 支持将对象序列化成二进制流,相对于 JDK 原生序列化,Hessian序列化之后体积更小,性能更优。 + ##### Kryo Kryo 是一个 Java 序列化框架,号称 Java 最快的序列化框架。Kryo 在序列化速度上很有优势,底层依赖于字节码生成机制。 由于只能限定在 JVM 语言上,所以 Kryo 不支持跨语言使用。 + ##### JSON 上面讲的几种序列化方式都是直接将对象变成二进制,也就是byte[]字节数组,这些方式都可以叫二进制方式。 JSON 序列化方式生成的是一串有规则的字符串,在可读性上要优于上面几种方式,但是在体积上就没什么优势了。 另外 JSON 是有规则的字符串,不跟任何编程语言绑定,天然上就具备了跨平台。 JSON 序列化常见的框架有:`fastJSON`、`Jackson`、`Gson` 等。 **JSON 可读性强,支持跨平台,体积稍微逊色。** ### 三、序列化技术的选型 选型最重要的就是要考虑这三个方面:**协议是否支持跨平台**、**序列化的速度**、**序列化生成的体积**。 **(1)协议是否支持跨平台** 如果一个大的系统有好多种语言进行混合开发,那么就肯定不适合用有语言局限性的序列化协议,比如 JDK 原生、Kryo 这些只能用在 Java 语言范围下,你用 JDK 原生方式进行序列化,用其他语言是无法反序列化的。 **(2)序列化的速度** 如果序列化的频率非常高,那么选择序列化速度快的协议会为你的系统性能提升不少。 **(3)序列化生成的体积** 如果频繁的在网络中传输的数据那就需要数据越小越好,小的数据传输快,也不占带宽,也能整体提升系统的性能,因此序列化生成的体积就很关键了。 ### 四、Netty扩展序列化算法 序列化,反序列化主要用在消息正文的转换上 * 序列化时,需要将 Java 对象变为要传输的数据(可以是 byte[],或 json 等,最终都需要变成 byte[]) * 反序列化时,需要将传入的正文数据还原成 Java 对象,便于处理 #### 代码实现 + 为了支持更多序列化算法,抽象一个 Serializer 接口 ```java import com.google.gson.*; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; /** * @author lilinchao * @date 2022/6/20 * @description 用于扩展序列化、反序列化算法 **/ public interface Serializer { /** * 反序列化方法 * @param clazz 反序列化的Class类型 * @param bytes 传输过来的字节数组 * @param
* @return 返回指定T类型的结果对象 */
T deserialize(Class
clazz,byte[] bytes); /** * 序列化方法 * @param object 序列化的对象 * @param
* @return 序列化对象采用指定的算法来转换得到的字节数组 */
byte[] serialize(T object); //序列化算法枚举类,目前包含JDK序列化与JSON序列化 enum Algorithm implements Serializer{ Java { @Override public
T deserialize(Class
clazz, byte[] bytes) { try { ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); return (T) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("反序列化失败", e); } } @Override public
byte[] serialize(T object) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(object); return bos.toByteArray(); } catch (IOException e) { throw new RuntimeException("序列化失败", e); } } }, Json{ @Override public
T deserialize(Class
clazz, byte[] bytes) { Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new Serializer.ClassCodec()).create(); String json = new String(bytes, StandardCharsets.UTF_8); return gson.fromJson(json, clazz); } @Override public
byte[] serialize(T object) { Gson gson = new GsonBuilder().registerTypeAdapter(Class.class, new Serializer.ClassCodec()).create(); String json = gson.toJson(object); return json.getBytes(StandardCharsets.UTF_8); } } } class ClassCodec implements JsonSerializer
>, JsonDeserializer
> { @Override public Class> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { try { String str = json.getAsString(); return Class.forName(str); } catch (ClassNotFoundException e) { throw new JsonParseException(e); } } @Override // String.class public JsonElement serialize(Class> src, Type typeOfSrc, JsonSerializationContext context) { // class -> json return new JsonPrimitive(src.getName()); } } } ``` + 增加配置类和配置文件 ```java import cn.itcast.protocol.Serializer; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * @author lilinchao * @date 2022/6/20 * @description 配置类 **/ public abstract class Config { static Properties properties; static { try (InputStream in = Config.class.getResourceAsStream("/application.properties")) { properties = new Properties(); properties.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } public static int getServerPort() { String value = properties.getProperty("server.port"); if(value == null) { return 8080; } else { return Integer.parseInt(value); } } /** * 获取配置文件中的序列化方式 * @return 序列化算法实现枚举类 */ public static Serializer.Algorithm getSerializerAlgorithm() { String value = properties.getProperty("serializer.algorithm"); //默认使用JDK序列化算法 if(value == null) { return Serializer.Algorithm.Java; } else { //从枚举类中根据序列化算法名字来取到枚举类算法实例 return Serializer.Algorithm.valueOf(value); } } } ``` + `application.properties`配置文件 ```properties #serializer.algorithm=Java serializer.algorithm=Json ``` + 修改编解码器 ```java import cn.itcast.config.Config; import cn.itcast.message.Message; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageCodec; import lombok.extern.slf4j.Slf4j; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.List; @Slf4j @ChannelHandler.Sharable /** * 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的 */ public class MessageCodecSharable extends MessageToMessageCodec
{ @Override protected void encode(ChannelHandlerContext ctx, Message msg, List
outList) throws Exception { ByteBuf out = ctx.alloc().buffer(); // 1. 4 字节的魔数 out.writeBytes(new byte[]{1, 2, 3, 4}); // 2. 1 字节的版本, out.writeByte(1); // 3. 1 字节的序列化方式 jdk 0 , json 1 out.writeByte(Config.getSerializerAlgorithm().ordinal()); // 4. 1 字节的指令类型 out.writeByte(msg.getMessageType()); // 5. 4 个字节 out.writeInt(msg.getSequenceId()); // 无意义,对齐填充 out.writeByte(0xff); // 6. 获取内容的字节数组 (原生JDK序列化) /*ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(msg); byte[] bytes = bos.toByteArray();*/ //根据序列化算法序号来从枚举类中取出指定的序列化实现算法 byte[] bytes = Config.getSerializerAlgorithm().serialize(msg); // 7. 长度 out.writeInt(bytes.length); // 8. 写入内容 out.writeBytes(bytes); outList.add(out); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List
out) throws Exception { int magicNum = in.readInt(); byte version = in.readByte(); byte serializerAlgorithm = in.readByte(); byte messageType = in.readByte(); int sequenceId = in.readInt(); in.readByte(); int length = in.readInt(); byte[] bytes = new byte[length]; in.readBytes(bytes, 0, length); //找到反序列化算法 Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerAlgorithm]; //确定具体消息类型 //根据serializerType来从枚举类中取到序列化算法进行反序列化 Class extends Message> messageClass = Message.getMessageClass(messageType); Message deserialize = algorithm.deserialize(messageClass, bytes); //原生JDK反序列化 // ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); // Message message = (Message) ois.readObject(); // log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length); // log.debug("{}", message); out.add(deserialize); } } ``` + 其中确定具体消息类型,可以根据 `消息类型字节` 获取到对应的 `消息 class` ```java import lombok.Data; import java.io.Serializable; import java.util.HashMap; import java.util.Map; @Data public abstract class Message implements Serializable { /** * 根据消息类型字节,获得对应的消息 class * @param messageType 消息类型字节 * @return 消息 class */ public static Class extends Message> getMessageClass(int messageType) { return messageClasses.get(messageType); } private int sequenceId; private int messageType; public abstract int getMessageType(); public static final int LoginRequestMessage = 0; public static final int LoginResponseMessage = 1; public static final int ChatRequestMessage = 2; public static final int ChatResponseMessage = 3; public static final int GroupCreateRequestMessage = 4; public static final int GroupCreateResponseMessage = 5; public static final int GroupJoinRequestMessage = 6; public static final int GroupJoinResponseMessage = 7; public static final int GroupQuitRequestMessage = 8; public static final int GroupQuitResponseMessage = 9; public static final int GroupChatRequestMessage = 10; public static final int GroupChatResponseMessage = 11; public static final int GroupMembersRequestMessage = 12; public static final int GroupMembersResponseMessage = 13; private static final Map
> messageClasses = new HashMap<>(); static { messageClasses.put(LoginRequestMessage, LoginRequestMessage.class); messageClasses.put(LoginResponseMessage, LoginResponseMessage.class); messageClasses.put(ChatRequestMessage, ChatRequestMessage.class); messageClasses.put(ChatResponseMessage, ChatResponseMessage.class); messageClasses.put(GroupCreateRequestMessage, GroupCreateRequestMessage.class); messageClasses.put(GroupCreateResponseMessage, GroupCreateResponseMessage.class); messageClasses.put(GroupJoinRequestMessage, GroupJoinRequestMessage.class); messageClasses.put(GroupJoinResponseMessage, GroupJoinResponseMessage.class); messageClasses.put(GroupQuitRequestMessage, GroupQuitRequestMessage.class); messageClasses.put(GroupQuitResponseMessage, GroupQuitResponseMessage.class); messageClasses.put(GroupChatRequestMessage, GroupChatRequestMessage.class); messageClasses.put(GroupChatResponseMessage, GroupChatResponseMessage.class); messageClasses.put(GroupMembersRequestMessage, GroupMembersRequestMessage.class); messageClasses.put(GroupMembersResponseMessage, GroupMembersResponseMessage.class); } } ``` + 测试代码 ```java import cn.itcast.config.Config; import cn.itcast.message.LoginRequestMessage; import cn.itcast.message.Message; import cn.itcast.protocol.MessageCodecSharable; import cn.itcast.protocol.Serializer; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.logging.LoggingHandler; /** * @author lilinchao * @date 2022/6/20 * @description 序列化测试 **/ public class TestSerializer { public static void main(String[] args) { MessageCodecSharable CODEC = new MessageCodecSharable(); LoggingHandler LOGGING = new LoggingHandler(); EmbeddedChannel channel = new EmbeddedChannel(LOGGING,CODEC,LOGGING); LoginRequestMessage message = new LoginRequestMessage("zhangsan", "123"); //出站消息(编码)操作 // channel.writeOutbound(message); //入站消息(解码)操作 ByteBuf buf = messageToByteBuf(message); channel.writeInbound(buf); } public static ByteBuf messageToByteBuf(Message msg) { int algorithm = Config.getSerializerAlgorithm().ordinal(); ByteBuf out = ByteBufAllocator.DEFAULT.buffer(); out.writeBytes(new byte[]{1, 2, 3, 4}); out.writeByte(1); out.writeByte(algorithm); out.writeByte(msg.getMessageType()); out.writeInt(msg.getSequenceId()); out.writeByte(0xff); byte[] bytes = Serializer.Algorithm.values()[algorithm].serialize(msg); out.writeInt(bytes.length); out.writeBytes(bytes); return out; } } ``` *附参考文章链接* *https://mp.weixin.qq.com/s/nzFBPuUGSSIGZaBbE-FkTg*
标签:
Netty
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2179.html
上一篇
07.Netty进阶之聊天室案例
下一篇
02.Netty优化之参数调优
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
Azkaban
Spark
Golang
MyBatisX
pytorch
正则表达式
NIO
Git
Shiro
Elasticsearch
SpringCloud
ajax
MyBatis-Plus
容器深入研究
RSA加解密
VUE
ClickHouse
Quartz
SpringBoot
Flink
数据结构
Linux
Eclipse
Beego
前端
Spark SQL
微服务
Elastisearch
机器学习
DataX
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭