zoukankan      html  css  js  c++  java
  • 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万的处理数。

  • 相关阅读:
    同台电脑 多Git账号同时使用
    netty对http协议解析原理解析(转载)
    Netty 线程模型与Reactor 模式
    增量/存量数据按时间维度分组
    网易技术分享:Nginx缓存引发的跨域惨案
    全面剖析Redis Cluster原理和应用
    聊聊阿里社招面试,谈谈“野生”Java程序员学习的道路
    美团点评基于 Flink 的实时数仓建设实践
    美团技术分享:大众点评App的短视频耗电量优化实战
    美团技术分享:美团深度学习系统的工程实践
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/4833702.html
Copyright © 2011-2022 走看看