Google高性能序列化框架Protobuf认识及与Netty的结合

栏目: Java · 发布时间: 7年前

内容简介:序列化(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程举例来说,我们接触的最多的序列化数据格式有JSON和XML。JSON相对于其他序列化来说,可读性比较强且便于快速编写,因此在前后端分离的今天,一般都采用JSON进行序列化传输。而XML的格式统一,符合标准,同样具有良好的可读性,在Java中的绝大多数配置文件都采用XML。

Google Protocol Buffer ( 简称 Protobuf ) 是 Google公司研发的 一种灵活高效的可序列化的数据协议 。什么是序列化呢?

序列化(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程

举例来说,我们接触的最多的序列化数据格式有JSON和XML。JSON相对于其他序列化来说,可读性比较强且便于快速编写,因此在前后端分离的今天,一般都采用JSON进行序列化传输。而XML的格式统一,符合标准,同样具有良好的可读性,在 Java 中的绝大多数配置文件都采用XML。

但是,在上面的两种序列化格式中,XML体积庞大,并且它与JSON的性能都不及今天介绍的主角——Protobuf

1.2 安装

首先,在Github上下载 Protobuf 编译器,下载地址为: Github releases 。如果你和我一样使用的Windows系统,那么则下载 protoc-3.6.1-win32.zip 文件。解压完之后,将 Your path\protoc-3.6.1-win32\bin 添加到环境变量中。

在命令行上输入 protoc 查看是否安装成功:

Google高性能序列化框架Protobuf认识及与Netty的结合

1.3 使用

首先,我们需要编写一个 proto 文件,用来定义程序中需要处理的结构化数据(即 Message )。 proto 文件类似于Java或者 C语言 的数据定义。

如下,创建 person.proto 文件,定义一个 PersonMessage ,包含三个属性: idnameemail

syntax = "proto3";  // 执行protobuf的协议版本
option java_package = "site.pushy.protobuf";  // 指定包名
option java_outer_classname = "PersonEntity"; //生成的数据访问类的类名

message Person {
  int32 id = 1;
  string name = 2;
  string email = 3;
}
复制代码

然后,通过 protoc 来将该 proto 文件定义的结构化数据编译成为Java文件,编译命令格式为:

$ protoc -I=存放proto文件的目录 --java_out=生成的Java文件输入路径 proto文件的路径
复制代码

例如,我将 proto 文件放在了E盘的 demo 下,并将它生成的Java文件放在 E:\demo\protobuf\src\main\java 下,则命令如下:

$ protoc -I=E:\demo --java_out=E:\demo\protobuf\src\main\java E:\demo\person.proto
复制代码

运行完之后,将会生成 PersonEntity 类:

package site.pushy.protobuf;

public final class PersonEntity {
    private PersonEntity() {}
    // 代码省略
}
复制代码

生成的 PersonEntity 类,我们可以通过建造者模式创建 Person 对象:

public class CreatePerson {
    
    public static PersonEntity.Person create() {
        PersonEntity.Person person = PersonEntity.Person.newBuilder()
                .setId(1)
                .setName("Pushy")
                .setEmail("1437876073@qq.com")
                .build();
            
        System.out.println(person);
        return person;
    }
}
复制代码

打印的结果为:

id: 1
name: "Pushy"
email: "1437876073@qq.com"
复制代码

怎么样?使用是不是非常简单,下面我们来了解一下 Protobuf 的序列化。

2. 序列化

2.1 字节数组

Protobuf 最简单序列化方式是将 Person 对象转换为字节数组,例如:

// 序列化
PersonEntity.Person person = CreatePerson.create();
byte[] data = person.toByteArray();

// 反序列化
PersonEntity.Person parsePerson = PersonEntity.Person.parseFrom(data);
System.out.println(parsePerson.name);
复制代码

这种方式可以适用于很多场景, Protobuf 会根据自己的编码方式将Java对象序列化成字节数组。同时 Protobuf 也会从字节数组中重新编码,得到新的Java POJO对象。

2.2 Stream

第二种序列化方式是将 Protobuf 对象写入Stream:

// 序列化,粘包,将一个或者多个ProtoBuf写入到Stream
PersonEntity.Person person = CreatePerson.create();
ByteArrayOutputStream os = new ByteArrayOutputStream();
person.writeTo(os);

// 反序列化,拆包,从stream中读出一个或者多个Protobuf字节对象
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
PersonEntity.Person parsePerson = PersonEntity.Person.parseFrom(is);
System.out.println(parsePerson);
复制代码

这种方式比较适用于RPC调用和Socket传输,在序列化的字节数组之前,添加一个 varint32 的数字表示字节数组的长度;那么在反序列化时,可以通过先读取 varint ,然后再依次读取此长度的字节;这种方式有效的解决了socket传输时如何“拆包”“封包”的问题。在 Netty 中,适用了同样的技巧。

2.3 文件

第三种则是通过写入文件进行序列化:

// 序列化,将Protobuf对象保存为文件
PersonEntity.Person person = CreatePerson.create();
FileOutputStream fos = new FileOutputStream("pushy.dt");
person.writeTo(fos);
fos.close();

// 反序列化,从文件中读取和解析Protobuf对象
FileInputStream fis = new FileInputStream("pushy.dt");
PersonEntity.Person parsePerson = PersonEntity.Person.parseFrom(fis);
System.out.println(parsePerson);
fis.close();
复制代码

3. 结合Netty

在Netty中,对 Protobuf 做了支持,并内置了响应的编解码器实现,如下:

名称 描述
ProtobufEncoder 使用Protobuf对消息进行编码
ProtobufDecoder 使用Protobuf对消息进行解码
ProtobufVarint32FrameDecoder 根据消息中的Protobuf的 Base 128 Varints 整型长度字段值动态地分割所接受到的ByteBuf
ProtobufVarint32LengthFieldPrepender 向ByteBuf前追加一个Protobuf的 Base 128 Varints 整型的长度字段值

3.1 服务端

引导部分在此不做赘述,更多可以看demo源码。我们主要来介绍一下 ChannelPipeline 中的设置。

服务端部分,需要添加关于 Protobuf 相应的编解码器,另外,还添加 ServerHandler 来处理服务端的业务逻辑:

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        pipeline.addLast(new ProtobufEncoder());
        pipeline.addLast(new ProtobufDecoder(PersonEntity.Person.getDefaultInstance()));
        pipeline.addLast(new ServerHandler());
    }
}
复制代码

服务器端的解码器会自动将类型转换为 PersonEntity.Person

public class ServerHandler extends SimpleChannelInboundHandler<PersonEntity.Person> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, PersonEntity.Person person) throws Exception {
        System.out.println("chanelRead0 =>" + person.getName() );
    }
}
复制代码

3.2 客户端

同样,客户端也要添加 Protobuf 相应的编解码器:

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        pipeline.addLast(new ProtobufDecoder(PersonEntity.Person.getDefaultInstance()));
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());
        pipeline.addLast(new ClientHandler());
    }
}
复制代码

并使用 ClientHandler 来向服务端发送 Protobuf 的消息,用于配置了客户端的解码器,因此在使用 writeAndFlush 写入数据时可以直接传入 PersonEntity.Person 类型数据:

public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(getPerson());
    }

    private PersonEntity.Person getPerson() {
        return PersonEntity.Person.newBuilder()
                .setName("Pushy")
                .setEmail("1437876073@qq.com")
                .build();
    }

}
复制代码

测试一下,可以看到服务端确实能通过 Protobuf 序列化收到客户端发送的消息:

Google高性能序列化框架Protobuf认识及与Netty的结合

最后,代码已上传到 Github ,想要了解更多关于Protobuf的知识,可以到官网浏览文档!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Using Google App Engine

Using Google App Engine

Charles Severance / O'Reilly Media / 2009-5-23 / USD 29.99

With this book, you can build exciting, scalable web applications quickly and confidently, using Google App Engine - even if you have little or no experience in programming or web development. App Eng......一起来看看 《Using Google App Engine》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具