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万的处理数。

时间: 2024-10-16 16:52:23

rpc框架之 avro 学习 2 - 高效的序列化的相关文章

rpc框架之avro教程1-hello world

avro是hadoop的一个子项目,提供的功能与thrift.Protocol Buffer类似,都支持二进制高效序列化,也自带RPC机制,但是avro使用起来更简单,无需象thrift那样生成目标语言源代码,目前支持的语言有java.c#.php.c++等(详情见:https://cwiki.apache.org/confluence/display/AVRO/Supported+Languages),hadoop生态圈中的hive.pig已经在使用avro pom.xml 1 <?xml v

一个入门rpc框架的学习

一个入门rpc框架的学习 参考 huangyong-rpc 轻量级分布式RPC框架 该程序是一个短连接的rpc实现 简介 RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样. RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC, 它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC.会两方面会直接影响 RPC 的性能,一是传输方式,二是序列化. 众所

rpc框架dubbo学习入门及环境搭建(spring boot+Kotlin)

学习之前,确保有以下知识基础: Java网络编程 Socket传输数据 IO流 rpc简介及实现 rpc是remote procedure call的简写,意思为远程过程调用. rpc应用较多的情景是分布式开发,那什么是分布式开发呢? 原本我也是想自己解释的,奈何网上大佬解释得很清楚了,这里就不献丑了,建议阅读完下面推荐的几篇再继续往下 [转]分布式架构的演进(JavaWeb) 如何给老婆解释什么是RPC 如何实现一个简单的RPC 刚开始的时候,服务和调用都是在同一机器,这叫本地过程调用 之后,

[转]新兵训练营系列课程——平台RPC框架介绍

原文:http://weibo.com/p/1001643875439147097368 课程大纲 1.RPC简介 1.1 什么是RPC 1.2 RPC与其他远程调用方式比较 2.Motan RPC框架 2.1 RPC服务框架 2.2 Motan RPC框架中的角色 2.3 Motan RPC的调用流程 3.Motan RPC中各角色介绍 3.1 注册中心 3.2 RPC Service 3.3 RPC Client 4.Motan实战 4.1 Motan的使用方式 4.2 使用Motan提供R

Java实现简单的RPC框架

一.RPC简介 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).Hessian.Http invoker等.另外,RPC是与语言无关的. RPC示意图 如上图所示,假设Computer1在调用sayHi()方法,对于Computer1而言调用sayHi()方法就像调用本地方法一样,调用 –>返回.但从后续调用可以看出Computer1调用的是Computer2

C++的一些不错开源框架,可以学习和借鉴

larryjiang 我的文章 我的文档 我的文集 我的收藏 我的草稿 我的微博 我的投票 我的活动 我的相册 我的日历 退出登录 × 查看26条@我的微博 查看1条系统消息 查看44条@我的微博 创建文章 创建活动 创建相册 创建投票 创建文集 创建定时短信 手机KM 频道 研发月报 腾讯月刊 Q益平台 微爱益起来 职业发展 总办交流 KM周刊 乐问周刊 应用 活动 投票 相册 文集 幻灯片 祝福卡 定时短信 达人堂 实验室 期刊 工作圈 我的K吧 K吧排序 浏览K吧 | 创建K吧 KM平台组

RPC框架分析

RPC框架分析 RPC 是什么? RPC 的全称是 Remote Procedure Call 是一种进程间通信方式.它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节.即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同. RPC 起源 RPC 这个概念术语在上世纪 80 年代由 Bruce Jay Nelson 提出.这里我们追溯下当初开发 RPC 的原动机是什么?在 Nelson 的论文 "Implementing

阿里首席架构师科普RPC框架是什么

RPC概念及分类 RPC全称为Remote Procedure Call,翻译过来为"远程过程调用".目前,主流的平台中都支持各种远程调用技术,以满足分布式系统架构中不同的系统之间的远程通信和相互调用.远程调用的应用场景极其广泛,实现的方式也各式各样. 从通信协议的层面,大致可以分为: 基于HTTP协议的(例如基于文本的SOAP(XML).Rest(JSON),基于二进制Hessian(Binary)) 基于TCP协议的(通常会借助Mina.Netty等高性能网络框架) 从不同的开发语

Java 实现简单的RPC框架

0 引言 RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议.它允许像调用本地服务一样调用远程服务.它可以有不同的实现方式.如RMI(远程方法调用).Hessian.Http invoker等.另外,RPC是与语言无关的. 假设Computer1在调用sayHi()方法,对于Computer1而言调用sayHi()方法就像调用本地方法一样,调用 –>返回.但从后续调用可以看出Computer1调用的是Computer2中的sayHi()方法,RPC屏