常见的序列化协议以及高性能Kryo实战

序列化框架的选择- hession2、kyro、protostuff 当涉及到序列化框架时,Hessian 2、Kryo 和 Protostuff 是三个常见的选择。它们在性能、跨语言支持、序列化大小和使用灵活性等方面存在一些差异。hession2、kyro、protostuff都是第三方开源的序列化/反序列化框架,要了解其各自的特性,我们首先需要知道序列化/反序列化是什么:序列化和反序列化是将对象转换为字节流(序列化)以及将字节流转换回对象(反序列化)的过程。序列化序列化是指将对象转换为字节流的过程,以便将其存储在文件、数据库或通过网络进行传输。 阅读详情

1. 序列化与反序列化

如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。

  • 序列化: 将数据结构或对象转换成二进制字节流的过程
  • 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程

2. 常见序列化协议有哪些?

JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。

像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。

2.1 JDK 自带的序列化方式

JDK 自带的序列化,只需实现 java.io.Serializable接口即可

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToStringpublic 
class RpcRequest implements Serializable {
	private static final long serialVersionUID = 1905122041950251207L;
	private String requestId;
	private String interfaceName;
	private String methodName;
	private Object[] parameters;
	private Class<?>[] paramTypes;
	private RpcMessageTypeEnum rpcMessageTypeEnum;
}
serialVersionUID 有什么作用?

序列化号 serialVersionUID 属于版本控制的作用。反序列化时,会检查 serialVersionUID 是否和当前类的 serialVersionUID 一致。如果 serialVersionUID 不一致则会抛出 InvalidClassException 异常。强烈推荐每个序列化类都手动指定其 serialVersionUID,如果不手动指定,那么编译器会动态生成默认的 serialVersionUID。

serialVersionUID 不是被 static 变量修饰了吗?

为什么还会被“序列化”?static 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 static 变量是属于类的而不是对象。你反序列之后,static 变量的值就像是默认赋予给了对象一样,看着就像是 static 变量被序列化,实际只是假象罢了。

如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,可以使用 transient 关键字修饰。

transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

关于 transient 还有几点注意:

  • transient 只能修饰变量,不能修饰类和方法。
  • transient 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 int 类型,那么反序列后结果就是 0。
  • static 变量因为不属于任何对象(Object),所以无论有没有 transient 关键字修饰,均不会被序列化。
为什么不推荐使用 JDK 自带的序列化?

我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:

  • 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
  • 性能差 :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
  • 存在安全问题 :序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。

相关阅读:应用安全:JAVA反序列化漏洞之殇 - Cryin 、Java反序列化安全漏洞怎么回事? - Monica。

2.2 Kryo

Kryo是一个快速高效的Java 二进制对象图序列化框架。该项目的目标是高速、小尺寸和易于使用的 API。该项目在需要持久化对象的任何时候都很有用,无论是文件、数据库还是通过网络。‍

另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。

Kryo的优点
  • 高性能:Kryo 的设计目标是提供极快的序列化和反序列化速度。相比于一些传统的序列化框架,如 Java 的默认序列化机制,Kryo 在性能方面通常表现出更好的效果。它采用了高度优化的序列化算法和数据结构,以提高序列化和反序列化的速度。
  • 紧凑的序列化格式:Kryo 生成的序列化字节流通常非常紧凑,这意味着它所占用的存储空间相对较小。较小的序列化大小可以减少网络传输的带宽占用,并降低存储成本。
  • 支持多种数据类型:Kryo 能够序列化和反序列化各种数据类型,包括原始类型(如整数、浮点数)、集合(如列表、映射)、自定义对象等。它还提供了对复杂对象图的支持,能够处理对象之间的引用关系。
  • 注册机制:Kryo 支持注册机制,可以显式注册类以提高序列化和反序列化的性能。通过注册类,Kryo 可以在序列化过程中使用类的编号而不是类的全限定名,从而减少序列化字节流的大小。
  • 跨平台兼容性:Kryo 是一个纯 Java 实现的序列化框架,因此在不同的 Java 平台上具有良好的兼容性。它可以与许多主流的 Java 框架和库无缝集成,如 Apache Spark、Hadoop、Akka 等。

首先我们先引入依赖(这里我们是通过maven的方式):

<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>kryo</artifactId>
   <version>5.5.0</version>
</dependency>

官网里也写了其他引用方式,感兴趣的可以直接到github下面看

测试代码示例:

public class HelloKryo {

    static public void main(String[] args) throws Exception {

        //1,使用kryo,我们要先创建一个kryo的对象,这里和普通序列化有些不同
        Kryo kryo = new Kryo();
        //2,将我们需要序列化的实体类注册进去。
        //当 Kryo 去编写一个对象的实例时,首先它可能需要编写一些标识对象类的东西。默认情况下,Kryo将读取或写入的所有类都必须事先注册。注册提供 int 类 ID、用于类的序列化程序以及用于创建类实例的对象实例化程序
        kryo.register(KryoBean.class);
        //3,创建一个上面注册过的实体类对象。
        KryoBean object = new KryoBean();
        //4,给对象赋值。
        object.value = "Hello Kryo!";
        //5,创建输出流。
        Output output = new Output(new FileOutputStream("file.text"));
        //6,将数据写到指定文件,这里使用的kryo自带的方法,对象在写入文件的时候会进行序列化
        /*序列化程序通常不应直接使用其他序列化程序,而应使用 Kryo 读写方法。这允许 Kryo 编排序列化并处理引用和 null 对象等功能。有时,序列化程序知道要对嵌套对象使用哪个序列化程序。
        在这种情况下,它应该使用 Kryo 接受序列化程序的读写方法。*/
        kryo.writeObject(output, object);
        //7,关闭输出流。
        output.close();
        //5,创建输入流。
        Input input = new Input(new FileInputStream("file.text"));
        //6,通过kryo对象读取文件中的对象,并进行反序列化。
        KryoBean object2 = kryo.readObject(input, KryoBean.class);
        //7,关闭输入流。
        input.close();
		System.out.println(object2.value);
    }
    static public class KryoBean {
        String value;

    }

}

结果显示:
在这里插入图片描述
在这里插入图片描述

🆗那我们简单上手之后,就可以开发属于我们自己的序列化工具

序列化工具的接口

/**
 * 序列化工具(程序调用该接口来实现obj<->byte[]之间的序列化/反序列化)
 * @author eguid
 *
 */
public interface Serializer{
	
	/**
	 * 序列化
	 * @param t
	 * @param bytes
	 */
	public void serialize(Object t,byte[] bytes);
	
	/**
	 * 序列化
	 * @param obj
	 * @param bytes
	 * @param offset
	 * @param count
	 */
	public void serialize(Object obj, byte[] bytes, int offset, int count);
	
	/**
	 * 反序列化
	 * @param bytes -字节数组
	 * @return T<T>
	 */
	public <T>T deserialize(byte[] bytes);
	
 
	/**
	 * 反序列化
	 * @param bytes
	 * @param offset
	 * @param count
	 * @return
	 */
	public <T>T deserialize(byte[] bytes, int offset, int count);
 
}

序列化工具的实现类

/**
 * 基于kyro的序列化/反序列化工具
 * 
 * @author eguid
 *
 */
public class kryoSerializer implements Serializer {
 
	// 由于kryo不是线程安全的,所以每个线程都使用独立的kryo
	final ThreadLocal<Kryo> kryoLocal = new ThreadLocal<Kryo>() {
		@Override
		protected Kryo initialValue() {
			Kryo kryo = new Kryo();
			kryo.register(ct, new BeanSerializer<>(kryo, ct));
			return kryo;
		}
	};
	final ThreadLocal<Output> outputLocal = new ThreadLocal<Output>();
	final ThreadLocal<Input> inputLocal = new ThreadLocal<Input>();
	private Class<?> ct = null;
 
	public kryoSerializer(Class<?> ct) {
		this.ct = ct;
	}
 
	public Class<?> getCt() {
		return ct;
	}
 
	public void setCt(Class<?> ct) {
		this.ct = ct;
	}
 
	@Override
	public void serialize(Object obj, byte[] bytes) {
		Kryo kryo = getKryo();
		Output output = getOutput(bytes);
		kryo.writeObjectOrNull(output, obj, obj.getClass());
		output.flush();
	}
 
	@Override
	public void serialize(Object obj, byte[] bytes, int offset, int count) {
		Kryo kryo = getKryo();
		Output output = getOutput(bytes, offset, count);
		kryo.writeObjectOrNull(output, obj, obj.getClass());
		output.flush();
	}
 
	/**
	 * 获取kryo
	 * 
	 * @param t
	 * @return
	 */
	private Kryo getKryo() {
		return kryoLocal.get();
	}
 
	/**
	 * 获取Output并设置初始数组
	 * 
	 * @param bytes
	 * @return
	 */
	private Output getOutput(byte[] bytes) {
		Output output = null;
		if ((output = outputLocal.get()) == null) {
			output = new Output();
			outputLocal.set(output);
		}
		if (bytes != null) {
			output.setBuffer(bytes);
		}
		return output;
	}
 
	/**
	 * 获取Output
	 * 
	 * @param bytes
	 * @return
	 */
	private Output getOutput(byte[] bytes, int offset, int count) {
		Output output = null;
		if ((output = outputLocal.get()) == null) {
			output = new Output();
			outputLocal.set(output);
		}
		if (bytes != null) {
			output.writeBytes(bytes, offset, count);
		}
		return output;
	}
 
	/**
	 * 获取Input
	 * 
	 * @param bytes
	 * @param offset
	 * @param count
	 * @return
	 */
	private Input getInput(byte[] bytes, int offset, int count) {
		Input input = null;
		if ((input = inputLocal.get()) == null) {
			input = new Input();
			inputLocal.set(input);
		}
		if (bytes != null) {
			input.setBuffer(bytes, offset, count);
		}
		return input;
	}
 
	@SuppressWarnings("unchecked")
	@Override
	public <T> T deserialize(byte[] bytes, int offset, int count) {
		Kryo kryo = getKryo();
		Input input = getInput(bytes, offset, count);
		return (T) kryo.readObjectOrNull(input, ct);
	}
 
	@Override
	public <T> T deserialize(byte[] bytes) {
		return deserialize(bytes, 0, bytes.length);
	}

测试类:

@Test
    void test2(){
        Serializer ser = new kryoSerializer(Msg.class);
        for (int i = 0; i < 10; i++) {

            Msg msg = new Msg();
            msg.setVersion_flag(new byte[] { 1, 2, 3 });
            msg.setCrc_code((short) 1);
            msg.setMsg_body(new byte[] { 123, 123, 123, 43, 42, 1, 12, 45, 57, 98 });
            byte[] bytes = new byte[300];
            long start = System.nanoTime();
            ser.serialize(msg, bytes);
            System.err.println("序列化耗时:" + (System.nanoTime() - start));
            System.out.println(msg);
            System.out.println(Arrays.toString(bytes));

            Msg newmsg = null;
            start = System.nanoTime();
            newmsg = ser.deserialize(bytes);
            System.err.println("反序列化耗时:" + (System.nanoTime() - start));
            System.out.println(newmsg);
        }
    }
    @Setter
    public static class Msg{
        private byte[] version_flag;

        private short crc_code;

        private byte[] msg_body;

    }

我们查看结果:
在这里插入图片描述
可以看到这里速度也是非常快

为什么使用纳秒,而不用毫秒?

与java原生的序列化反序列化要耗时几毫秒不同,kryo序列化和反序列化太快了,单个对象的序列化反序列化速度都在0.0x毫秒左右(1 毫秒=1000000 纳秒)

所以我们可以看出kryo序列化的性能是非常快的,也可以拿原生的序列化速度做对比,这里就不再赘述了。

接下来我们顺便简单介绍一下其他的序列化协议

2.3 Protobuf

Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。

Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言

一个简单的 proto 文件如下:

在这里插入图片描述

Github 地址:https://github.com/protocolbuffers/protobuf

2.4 Hessian

Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的

在这里插入图片描述
值得一提的是,Dubbo2.x 默认启用的序列化方式是 Hessian2

hession官网 :http://hessian.caucho.com/doc/hessian-overview.xtp

招银网络二面:什么是序列化常见序列化协议有哪些? 今天分享一道读者面试招银网络科技遇到的面试真题。 下面是正文。 序列化和反序列化相关概念 什么是序列化?什么是反序列化? 如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络上传输 Java 对象,这些场景都需要用到序列化。 简单来说: 序列化:将数据结构或对象转换成二进制字节流的过程 反序列化:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程 对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(C 阅读详情

相关推荐

Kryo 高性能序列化和反序列化

一、pom.xml <dependency> <groupId>com.esotericsoftware</groupId> <artifactId>kryo</artifactId> <version>4.0.0</version> </dependency> 二、封装工具类 package com.cxs.web.system.kryo; import com.cxs.common

a251628111的博客 1027

几种序列化协议的介绍

它基于 ECMAScript (w3c制定的js规范)的一个子集, JSON采用与编程语言无关的文本格式,但是也使用了类C语言(包括C, C++, C#, JavaJavaScript, Perl, Python等)的习惯,简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。(deserialization)则是将从网络、磁盘等读取的字节数组还原成原始对象,以便后续业务的进行,一般也将反序列化称为解码(Decode),主要用于网络传输对象的解码,以便完成远程调用。

木牛的博客 1603

Java高性能序列化工具Kryo序列化

Java高性能序列化工具Kryo序列化

weixin_71682097的博客 1292

Jackson - 简述几种常见序列化协议

文章目录1. 序列化的定义2. HTTP协议3. 常用的编码格式(序列化协议)1. xml2. Json3. Protobuf4. Java内置的编码和序列化机制4. 理解JSON序列化 1. 序列化的定义 序列化是将对象的状态信息转换为可存储或可传输的格式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。 序列化:将对象的状态信息转换为可存储或传输的格式的数据的过程。 反序列化:将可存储或传输的格式的数据转换为对象的状态信息的

你今天真好看呀 2099

网络通信应用层协议-序列化与反序列化

在进行网络通信时,应用层协议是需要程序员手动定制的,序列化与反序列化是对协议进行字符串形式的转换。应用层协议表示发送端和接收端共同遵守的规则,依照这个规则,通信的双方才能够分析出传输数据的真正含义,只有理解了数据的真正含义,才能对数据进行处理,因此应用层协议在网络通信中非常重要。传输层协议为TPC\UDP,网络层协议为ipv4\ipv6,这些协议都是设置好的,而应用层协议需要程序员手动实现,传输应用层协议需要进行序列化与反序列化

安权_code的博客 1037

常见序列化协议有哪些?

Thrift:由 Facebook 开发的一种跨语言的服务框架,也包含了一种二进制序列化协议。Thrift 使用 IDL(Interface Description Language)来定义数据结构和服务接口,并生成相应的代码进行序列化和反序列化操作。Protocol Buffers 使用简洁的接口描述语言来定义数据结构,并生成相应的代码进行序列化和反序列化操作。它具有高效的编码和解码速度,以及较小的数据体积。以上是常见序列化协议,每种协议都有自己的特点和适用场景。

2301_78627004的博客 406

一起写个Dubbo——4. Kryo序列化

上一节我们实现了一个通用的序列化框架,使得序列化方式具有了较高的扩展性,并且实现了一个基于 JSON 的序列化器。这一节我们就来实现一个基于 Kryo序列化器。

Ziyang的博客 8631

Java 高性能序列化框架 Kryo 详解:从入门到实战

KryoJava 高性能序列化的首选框架,核心优势是“快+小”,适配高并发、低延迟、低带宽的场景(如 RPC、缓存、游戏);但需注意其“Java 独占”“类结构兼容”“线程安全”等问题,生产环境建议结合对象池、强制类注册等优化手段使用。若需跨语言交互,可搭配 Protobuf 作为补充。如果需要针对特定场景(如 Spark 集成、游戏服务器优化)的 Kryo 实战代码,可告诉我具体需求!

weixin_46515691的博客 836

Kryo序列化框架:高性能Java对象序列化原理与实战指南

在分布式系统和高性能计算领域,数据序列化是将内存对象转换为可传输字节流的关键技术,其性能直接影响系统吞吐量和延迟。序列化的核心原理在于高效处理对象元数据与字节编码,以优化网络传输和存储效率。Kryo作为Java生态中的高性能序列化框架,通过基于注册的机制和避免反射等设计,实现了远超原生序列化的速度与极小的字节体积,其技术价值在于为大数据框架(如Spark、Flink)和缓存中间件提供了底层通信保障。在应用场景上,它尤其适合微服务间高频调用、Redis缓存对象以及需要极致性能的内部通信。本文聚焦Kryo的核心

weixin_34072458的博客 434

Kryo序列化实战指南

Kryo是一个高性能、高效的Java二进制序列化与反序列化库,由EsotericSoftware开发并维护。它设计用于快速序列化对象到字节数组或从字节数组反序列化回对象,特别适合需要频繁序列化操作的场景,如网络通信、持久化存储等。Kryo的一个显著特点是其速度优势和较小的序列化结果,同时支持自定义序列化器,提供强大的灵活性。 ## 2. 项目快速启动 ### 添加依赖 如果你是Maven用户,...

gitblog_00152的博客 750

【Spark】Spark的Kryo序列化

1.美图 2.Spark序列化概述 在Spark的架构中,在网络中传递的或者缓存在内存、硬盘中的对象需要进行序列化操作,序列化的作用主要是利用时间换空间: 分发给Executor上的Task 需要缓存的RDD(前提是使用序列化方式缓存) 广播变量 Shuffle过程中的数据缓存 使用receiver方式接收的流数据缓存 算子函数中使用的外部变量 ​ 上面的六种数据,通过Java序列化(默认的序...

九师兄 3270

Redis使用kryo来进行序列化遇到的问题

1.通过spring-data-redis集成redis,使用kryo的方式来序列化value值,kryo是一款高性能序列化反序列话工具,具体要引入的jar包可以参看 https://mvnrepository.com/artifact/com.esotericsoftware/kryo 需要导入的jar包有 <dependency> <groupId>c...

qq_28600087的博客 2891

Java进阶营】Java技术指南「序列化机制」采用protostuff和kryo高性能序列化框架实现RestTemplate的序列化组件

序列化 序列化可以简单理解为对象–>字节的过程,同理,反序列化则是相反的过程。为什么需要序列化?因为网络传输只认字节。所以互信的过程依赖于序列化。 网络传输的性能等诸多因素,通常会支持多种序列化方式以供使用者插拔使用,一些常用的序列化方案hessian,kryo,Protostuff、FST等,其中最快、效果最好的要数Kryo和Protostuff RedisConfiguration的配置 创建Redis连接工厂对象(RedisConnectionFactory) 创建RestT

m0_60721514的博客 1724

java基础学习:序列化之 - kryo

Kryo是一个快速且高效的Java序列化框架,它主要用于将Java对象转换为字节流以便存储或传输,同时能够将字节流反序列化为原始Java对象。Kryo相比Java自带的序列化机制具有更高的性能和更小的序列化体积。

玉汝于成 1759

Kryo二进制序列化框架实战指南

Kryo二进制序列化框架实战指南 项目介绍 Kryo是一个面向Java高性能、高效二进制对象图序列化框架。它设计的目标是高速度、小尺寸以及简洁易用的API。Kryo适用于任何需要持久化对象的情况,比如文件存储、数据库交互或网络传输。此外,Kryo还提供了深度克隆和浅克隆的能力,支持直接对象到对象的复制,无需经过序列化到字节再到反序列化的中间步骤。该项目原托管于Google Code,后迁移至Gi...

gitblog_00365的博客 1296

10 Dubbo + Kryo 实现高速序列化

dubbo序列化

qq_24907431的博客 710

序列化 - Kryo序列化详解

序列化是指将数据结构或对象转换为可存储或传输的格式,以便在稍后的时间点重新构建或恢复原始数据结构或对象的过程。在计算机科学和编程中,序列化通常用于将内存中的数据转换为可以在磁盘上保存或通过网络传输的形式,以便在需要时进行持久化存储或在不同系统之间进行数据交换。序列化的主要目的是在不同的环境中传递数据,无论是在不同的程序之间、不同的计算机之间,甚至在不同的编程语言之间。序列化的结果通常是一串字节流或文本,这取决于所使用的序列化格式。

~ 1236
上一篇: 服务发现の秘密——Java SPI以及dubbo SPI源码解析
下一篇: 反卷神器!idea强制休息防沉迷小插件
愤怒的小肥
博客等级 码龄5年 109粉丝 21原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值