rpc框架之 avro 学习 2 - 高效的序列化

同一类框架,后出现的总会吸收之前框架的优点,然后加以改进,avro在序列化方面相对thrift就是一个很好的例子。借用Apache Avro 与 Thrift 比较 一文中的几张图来说明一下,avro在序列化方面的改进:

1、无需强制生成目标语言代码

avro提供了二种使用方式,一种称之为Sepcific方式,这跟thrift基本一致,都是写定义IDL文件,然后用编译器(或插件)生成目标class,另一种方式是Generic,这种方式下,不用生成目标代码,而是采用动态加载定义文件的方式,将 FieldName - FieldValue,以Map<K,V>的方式存储。

2、scheme/tag信息不重复写入二进制数据,存储及传输更高效

上图是thrift的存储格式,每块数据前都有一个tag用于标识数据域的类型及编号(这部分tag信息可以理解为数据域的meta信息),如果传输一个List集合,集合中的每条记录,这部分meta信息实际是重复存储的,多少有些浪费。

这是avro的改进,avro抛弃了对Filed编号的做法,而是直接在class的头部,把所有schema元数据信息包含在内(见下面的java代码),这样,client与server二端其实都已经知道数据的schema(架构模式)信息,仅仅在client与server通讯初始化,首次传输即可,以后无需再传递这部分信息,提升了网络传输效率。类似刚才的List集合这种情况,这部分信息也需要重复存储到2进制数据中,反序列化时,也不需再关注schema的信息,存储空间更小。

package yjmyzz.avro.study.dto;

@SuppressWarnings("all")
@org.apache.avro.specific.AvroGenerated
public class QueryParameter extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
    public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"QueryParameter\",\"namespace\":\"yjmyzz.avro.study.dto\",\"fields\":[{\"name\":\"ageStart\",\"type\":\"int\"},{\"name\":\"ageEnd\",\"type\":\"int\"}]}");

    public static org.apache.avro.Schema getClassSchema() {
        return SCHEMA$;
    }
  
    //...
}

这是avro生成的java代码,从源代码可以印证Schema确实已经包含在java代码内。

关于avro的序列化,可以用下面的代码测试一下:

package yjmyzz.avro.test;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.*;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;
import org.junit.Assert;
import org.junit.Test;
import yjmyzz.avro.study.dto.QueryParameter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class SerializeTest {

    @Test
    public void test() throws IOException {

        QueryParameter queryParameter = getQueryParameter();

        //****** 1 Specific 方式-序列化*******//

        ByteArrayOutputStream out1 = new ByteArrayOutputStream();
        DatumWriter<QueryParameter> writer1 = new SpecificDatumWriter<QueryParameter>(QueryParameter.class);
        BinaryEncoder encoder1 = EncoderFactory.get().binaryEncoder(out1, null);
        writer1.write(queryParameter, encoder1);
        encoder1.flush();
        out1.close();
        byte[] byte1 = out1.toByteArray();
        System.out.println("Avro Specific二进制序列后的byte数组长度:" + byte1.length);

        //反序列化
        DatumReader<QueryParameter> reader1 = new SpecificDatumReader<QueryParameter>(QueryParameter.class);
        Decoder decoder1 = DecoderFactory.get().binaryDecoder(out1.toByteArray(), null);
        QueryParameter result1 = reader1.read(null, decoder1);
        Assert.assertEquals(queryParameter.getAgeStart(), result1.getAgeStart());
        Assert.assertEquals(queryParameter.getAgeEnd(), result1.getAgeEnd());

        //**我是万恶的分割线***//

        //****** 2 Genericy 方式-序列化*******//
        Schema.Parser parser = new Schema.Parser();
        //Schema schema = parser.parse(new File("/Users/jimmy/Work/Code/avro/avro-contract/src/main/avro/QueryParameter.avsc"));
        Schema schema = parser.parse(getClass().getResourceAsStream("/QueryParameter.avsc"));

        //根据schema创建一个record示例(跟反射的思想有点类似)
        GenericRecord datum = new GenericData.Record(schema);
        datum.put("ageStart", 1);
        datum.put("ageEnd", 5);

        //序列化
        ByteArrayOutputStream out2 = new ByteArrayOutputStream();
        DatumWriter<GenericRecord> writer2 = new GenericDatumWriter<GenericRecord>(schema);
        Encoder encoder2 = EncoderFactory.get().binaryEncoder(out2, null);
        writer2.write(datum, encoder2);
        encoder2.flush();
        out2.close();
        byte[] byte2 = out2.toByteArray();
        System.out.println("Avro Generic二进制序列后的byte数组长度:" + byte2.length);

        //反序列化
        DatumReader<GenericRecord> reader2 = new GenericDatumReader<GenericRecord>(schema);
        Decoder decoder2 = DecoderFactory.get().binaryDecoder(out2.toByteArray(), null);
        GenericRecord result2 = reader2.read(null, decoder2);
        Assert.assertEquals(datum.get("ageStart"), result2.get("ageStart"));
        Assert.assertEquals(datum.get("ageEnd"), result2.get("ageEnd"));
    }

    private QueryParameter getQueryParameter() {
        QueryParameter query = new QueryParameter();
        query.setAgeStart(1);
        query.setAgeEnd(5);
        return query;
    }
}

Avro Specific二进制序列后的byte数组长度:2 Avro Generic二进制序列后的byte数组长度:2

前一篇thrift中的序列化结果相比,存储占用的空间比thrift的TCompactProtocol还要小,确实在序列化方面avro做得更好。

但是,凡事总有二面性,虽然avro在序列化方面做了不少改进,但是其RPC的实现并没有做出太多的创新,默认提供的HttpServer、NettyServer都是直接用的其它开源产品实现,不象Thrift自己提供了全新的实现,所以在RPC的性能方面,avro仍有很多可以优化的空间,默认情况下,从我自己测试的情况下,avro是不敌thrift的。但具体能优化到什么程度,就看使用的人在网络通讯、网络协议方面的功底了,有朋友说avro使用c#语言开发Server与Client端,对源代码优化后,可达到每秒20~30万的处理数。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏为数不多的Android技巧

一种绕过Android P对非SDK接口限制的简单方法

众所周知,Android P 引入了针对非 SDK 接口(俗称为隐藏API)的使用限制。这是继 Android N上针对 NDK 中私有库的链接限制之后的又一次...

6243
来自专栏web编程技术分享

【手把手】JavaWeb 入门级项目实战 -- 文章发布系统 (第六节)

43512
来自专栏java学习

Java每日一题_关于变量的使用

答:在【类里面】声明的变量叫全局变量,全局变量也叫成员变量,全局变量,在全局作用域下申明的变量没有被手动赋值的却在程序中被使用的情况下,将被赋予初值:

903
来自专栏点滴积累

shapeless官方指南翻译写在前面

目录 前言 Shapeless简介 The Type Astronaut's Guide to Shapeless简介 总结 一、前言        在我的20...

3787
来自专栏Jimoer

Java设计模式学习记录-模板方法模式

模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

1864
来自专栏程序员互动联盟

【新技术分享】C++17 最新进展

C++标准委员会最近在夏威夷的科纳召开了一次会议,大家可能关心最新的进展,但是按照以往的情况,某些文件需要很久才会公开。会议进行的时候,大家都在忙着修订自己的文...

3456
来自专栏芋道源码1024

我的编码习惯 —— API 接口定义

工作中,少不了要定义各种接口,系统集成要定义接口,前后台掉调用也要定义接口。接口定义一定程度上能反应程序员的编程功底。列举一下工作中我发现大家容易出现的问题:

1404
来自专栏Crossin的编程教室

每一个程序员要遵守的一些优秀编程风格

无论你是业余的还是专业的程序员,正确的编程风格,不但有助于提升软件产品的功能,还可以明显减少软件灾难的产生。今天,我们就来探讨一下有助于我们获取更佳编程风格的一...

3478
来自专栏极客猴

Python 中各种时间类型的转换

我们编码过程中经常需要获取当前时间。当然, 这也离不开对时间类型进行转换运算。本文主要讲解 Python 各种时间类型之间的转换。

942
来自专栏韩伟的专栏

C#语言和JAVA、C++的对比学习

很早以前,就听说著名的BorlandDelphi开发者,去微软设计了一门伟大的语言C#。但是由于一直都在Linux上做开发,所以无缘拜会。直到最近几年,借手游大...

3394

扫码关注云+社区

领取腾讯云代金券