Netty权威指南(六)编解码技术、MessagePack编解码框架以及LengthFieldBasedFreamDecoder解码器

一、介绍

基于Java提供的对象输入/输出流ObjectInputStreamObjectOutputStream ,可以直接把Java对象作为可存储的字节数组,写入文件,也可以传输到网络上。对程序员来说,基于JDK默认的序列化机制,可以避免操作底层的字节数组,从而提高开发效率。

Java序列化的目的主要有两个:

  1. 网络传输
  2. 对象持久化

Netty的NIO网络开发,主要关注的是网络传输方面。
当进行跨进程服务调用时,需要把被传输的Java对象编码为字节数组或者ByteBuffer对象。当远程服务读取到ByteBuffer或者字节数组时,需要将其解码为发送时的Java对象,这被称为Java对象编解码技术。

Java序列化仅仅是Java编解码技术的一种,由于它的种种缺陷,衍生出了很多种编解码技术和框架,后续章节我们会结合Netty介绍几种业界主流的编解码技术和框架。

二、Java序列化的缺点

Java序列化从JDK1.1版本就已经提供,它不需要添加额外的类库,只需要实现 java.io.Serializable 并生成序列ID即可,因此,它从诞生之初就得到了广泛的应用。
但是在RPC(远程服务调用)时,很少直接使用Java序列化进行消息的编解码和传输。

2.1 无法跨语言

无法跨语言是Java序列化最致命的问题,对于跨进程的服务调用,服务提供者可能会使用C++或者其他语言开发,当我们需要和异构语言进行交互时,Java序列化就难以胜任。

由于Java序列化技术是Java语言内部的私有协议,其他语言并不支持,对于用户来说他完全是黑盒。对于Java序列化后的字节数组,别的语言无法进行反序列化,这就严重阻碍了它的应用。事实上,目前几乎所有流行的Java RPC 通信框架,都没有使用Java序列化作为编解码框架,原因就在于它无法跨语言,而这些RPC框架往往需要支持跨语言调用。

2.2 序列化后的码流太大

我们通过一个实例来看下Java序列化后的字节数组大小:
先创建一个Java对象:

package com.lsh.serializable;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.nio.ByteBuffer;

/**
 * @author :LiuShihao
 * @date :Created in 2021/4/9 12:28 下午
 * @desc :
 * UserInfo是一个普通的Java对象,实现了Serializable接口,并生成了序列化ID,可以通过JDK的Java序列化机制进行序列化和反序列化
 *
 */
//getset方法
@Data
//全参构造
@AllArgsConstructor
//无参构造
@NoArgsConstructor
public class UserInfo implements Serializable {
   
    private static final long serialVersionUID = -8018991226271912056L;

    private String userName;
    private int userID;

    /**
     *
     * 使用基于ByteBuffer的通用二进制编解码技术对UserInfo对象进行编码,编码结果仍然是byte数组,
     * 可以与传统的JDK序列化后的码流大小进行对比
     *
     * Buffer的属性:
     * 容量(capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变
     * 上界(limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数
     * 位置(position):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新
     * 标记(mark):下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新一个备忘位置。调用 mark( )来设定 mark = postion。调用 reset( )设定 position =mark。标记在设定前是未定义的(undefined)。这四个属性之间总是遵循以下关系:0 <= mark <= position <= limit <= capacity
     *
     * @return
     */
    public byte[] codeC(){
   
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        byte[] value = this.getUserName().getBytes();
        buffer.putInt(value.length);
        //put() :相对写,向position的位置写入一个byte,并将postion+1,为下次读写作准备
        buffer.put(value);
        buffer.putInt(this.userID);
        //Buffer有两种模式,写模式和读模式。在写模式下调用flip()之后,Buffer从写模式变成读模式。
        buffer.flip();
        //remaining() 返回limit和position之间相对位置差
        byte[] result = new byte[buffer.remaining()];

        buffer.get(result);

        return result;

    }


}

然后在对比两种序列化编码后的码流的大小:

package com.lsh.serializable;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author :LiuShihao
 * @date :Created in 2021/4/9 12:40 下午
 * @desc :
 */
public class TestUserInfo {
   

    /**
     * 先调用两种编码接口对UserInfo对象编码,然后分别打印两者编码后的码流的大小
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
   
        UserInfo userInfo = new UserInfo("Welcomde to Netty",100);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(bos);
        os.writeObject(userInfo);
        os.flush();
        os.close();
        byte[] b = bos.toByteArray();
        System.out.println("The jdk serializable length is :"+b.length);
        bos.close();
        System.out.println("------------------------------------------");
        System.out.println("The byte array serializable length is :"+userInfo.codeC().length);

    }
}

运行结果:
在这里插入图片描述

测试结果发现,采用JDK序列化机制编码后的二进制数组大小竟然是二进制编码的5倍。
我们评判一个编解码框架的优劣时,往往会考虑一下几个因素:

  1. 是否支持跨语言,支持的语言种类是否丰富;
  2. 编码后码流的大小;
  3. 编解码的性能;
  4. 类库是否小巧,API使用是否方便;
  5. 使用者需要手工开发的工作量和难度;

在同等情况下,编码后的字节数组越大,存储的时候越占用空间,存储的硬件成本越高,并且在网络传输时更占宽带,导致系统的吞吐量降低,Java序列化后的码流偏大,也一直被行业所诟病,导致它的应用范围受到了很大限制。

2.3 序列化性能太低

对UserInfo进行改造,新增一个方法,在创建一个性能测试版本的UserInfo测试程序:
在UserInfo类中新增下面这个方法:

    public  byte[] codeC(ByteBuffer buffer){
   
        buffer.clear();
        byte[] value = this.userName.getBytes();
        buffer.putInt(value.length);
        buffer.put(value);
        buffer.putInt(this.userID);
        buffer.flip();
        value = null;
        byte[] result = new byte[buffer.remaining()];
        buffer.get(result);
        return result;
    }

编码性能测试类:

package com.lsh.serializable;

import java.io.ByteArrayOutputStream;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liu_Shihao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值