高并发Avro(心得)

  1. 概述:AVRO是Apache提供的一套用于进行序列化RPC的机制
  2. 序列化
    1. 概述:
      1. 数据序列化就是将对象或者数据结构转化成特定的格式,使其可在网络中传输,或者可存储在内存或者文件中
      2. 反序列化则是相反的操作,将对象从序列化数据中还原出来
      3. 数据序列化的重点在于数据的交换和传输
    2. 衡量标准:
      1. 序列化之后的数据大小。因为序列化的数据要通过网络进行传输或者是存储在内存或者文件中,所以数据量越小,则存储或者传输所用的时间就越少
      2. 序列化以及反序列化的耗时及占用的CPU
      3. 是否能够跨语言或者平台。因为现在的企业开发中,一个项目往往会使用到不同的语言来进行架构和实现。那么在异构的网络系统中,网络双方可能使用的是不同的语言或者是不同的操作系统,例如一端使用的是Java而另一端使用的C++;或者一端使用的是Windows系统而另一端使用的是Linux系统,那么这个时候就要求序列化的数据能够在不同的语言以及不同的平台之间进行解析传输
    3. Java原生序列化/反序列化机制的问题
      1. Java的原生序列化不能做到对象结构的服用,就导致序列化多个对象的时候数据量较大
      2. Java的原生序列化在使用的时候,是按照Java指定的格式将对象进行解析,解析为字节码格式,那么此时其他的语言在接收到这个对象的时候,是无法解析或者解析较为困难。即Java的原生序列化机制是没有做到跨语言或者跨平台传递使用
    4. 序列化框架AVRO
      1. 概述
        1. Avro是一种远程过程调用和数据序列化框架,是在Apache的Hadoop项目之内开发的
        2. 它使用JSON来定义数据类型和通讯协议,使用压缩二进制格式来序列化数据
        3. 它主要用于Hadoop,它可以为持久化数据提供一种序列化格式,并为Hadoop节点间及从客户端程序到Hadoop服务的通讯提供一种电报格式
        4. 通过avro,每次进行序列化,根据模式(schema)文件来序列化,可以提高性能
      2. 特点如下:
        1. 丰富的数据结构类型,8种基本数据类型以及6种复杂类型
        2. 快速可压缩的二进制形式
        3. 提供容器文件用于持久化数据
        4. 远程过程调用RPC框架
        5. 简单的动态语言结合功能,Avro 和动态语言结合后,读写数据文件和使用 RPC协议都不需要生成代码,而代码生成作为一种可选的优化只值得在静态类型语言中实现。而代码生成作为一种可选的优化只值得在静态类型语言中实现
      3. 格式:
        1. 简单类型

          Avro类型

          说明

          null

          没有值

          boolean

          一个二级制布尔值

          int

          32位有符号整数

          long

          64位有符号整数

          float

          32位单精度浮点数

          double

          64位双精度浮点数

          bytes

          8位无符号字节序列

          string

          字符序列

        2. 复杂格式
          1. Avro定义了六种复杂数据类型,每一种复杂数据类型都具有独特的属性,下表就每一种复杂数据类型进行说明:
          2. 类型

            属性

            说明

            record

             

            class

             

            name

            a JSON string providing the name of the record (required).

             

            namespace

            a JSON string that qualifies the name(optional).

             

            doc

            a JSON string providing documentation to the user of this schema (optional).

             

            aliases

            a JSON array of strings, providing alternate names for this record (optional).

             

            fields

            a JSON array, listing fields (required).

             

                name

            a JSON string.

             

                type

            a schema/a string of defined record.

             

                default

            a default value for field when lack.

             

                order

            ordering of this field.

            Enums

             

            enum

             

            name

            a JSON string providing the name of the enum (required).

             

            namespace

            a JSON string that qualifies the name.

             

            doc

            a JSON string providing documentation to the user of this schema (optional).

             

            aliases

            a JSON array of strings, providing alternate names for this enum (optional)

             

            symbols

            a JSON array, listing symbols, as JSON strings (required). All symbols in an enum must be unique.

            Arrays

             

            array

             

            items

            he schema of the array’s items.

            Maps

             

            map

             

            values

            the schema of the map’s values.

            Fixed

             

            fixed

             

            name

            a string naming this fixed (required).

             

            namespace

            a string that qualifies the name.

             

            aliases

            a JSON array of strings, providing alternate names for this enum (optional).

             

            size

            an integer, specifying the number of bytes per value (required).

            Unions

             

            a JSON arrays

          3. 每一种复杂数据类型都含有各自的一些属性,其中部分属性是必需的,部分是可选的
          4. 这里需要说明Record类型中field属性的默认值,当Record Schema实例数据中某个field属性没有提供实例数据时,则由默认值提供,具体值见下表。Union的field默认值由Union定义中的第一个Schema决定。
          5. Avro类型

            json类型

            举例

            null

            null

            null

            boolean

            boolean

            true

            int,long

            integer

            1

            float,double

            number

            1.1

            bytes

            string

            "\u00FF"

            string

            string

            "foo"

            record

            object

            {"a": 1}

            enum

            string

            "FOO"

            array

            array

            [1]

            map

            object

            {"a": 1}

            fixed

            string

            "\u00ff"

        3. pom文件
          
              4.0.0
              cn.tedu.zk
              zebra-avro
              0.0.1-SNAPSHOT
              jar
           
              zebra-avro
              http://maven.apache.org
           
              
                  UTF-8
                  2.3.2
                  1.7.5
              
              
                  
                      junit
                      junit
                      4.10
                      test
                  
                  
                      org.slf4j
                      slf4j-simple
                      1.6.4
                      compile
                  
                  
                      org.apache.avro
                      avro
                      1.7.5
                  
                  
                      org.apache.avro
                      avro-ipc
                      1.7.5
                  
              
              
                  
                      
                          org.apache.maven.plugins
                          maven-compiler-plugin
                          ${compiler-plugin.version}
                      
                      
                          org.apache.avro
                          avro-maven-plugin
                          1.7.5
                          
                              
                                  schemas
                                  generate-sources
                                  
                                      schema
                                      protocol
                                      idl-protocol
                                  
                                  
                                      
                                      ${project.basedir}/src/main/avro/
                                      
                                      ${project.basedir}/src/main/java/
                                  
                              
           
                          
                      
                  
              
          
          

           

        4. API-序列化
          1. 一、步骤

          2. 创建maven工程,导入pom依赖
          3. 在指定的目录下编辑avsc文件,例如
          4. {

                "namespace":"avro.domain"// 包名

                "type":"record",

                "name":"User"// 类名

                "fields":

                [

                    {"name":"username","type":"string"}, // 属性名以及属性类型

                    {"name":"age","type":"int"}

                ]

            }

          5. 选中pom.xml文件,右键,选择Run as,点击Maven generate-resources
          6. 序列化示例
          7. @Test

            public void write() throws Exception{

                  

                User u1 = new User("Ken Tompson",194375);

                User u2 = new User("丹尼斯·里奇",194170);

                  

                DatumWriter dw = new SpecificDatumWriter<>(User.class);

                DataFileWriter dfw = new DataFileWriter<>(dw);

                  

                // 创建底层的文件输出通道 

                // schema - 序列化类的模式

                // path - 文件路径

                dfw.create(u1.getSchema(),new File("1.txt"));

                // 把对象数据写到文件中

                dfw.append(u1);

                dfw.append(u2);

                  

                dfw.close();

                  

            }

          8. 反序列化示例
          9. @Test

            public void read() throws Exception{

                DatumReader dr = new SpecificDatumReader<>(User.class);

                DataFileReader dfr = new DataFileReader<>(new File("1.txt"),dr);

                  

                //--通过迭代器,迭代出对象数据

                while(dfr.hasNext()){

                    System.out.println(dfr.next());

                }

                  

            }

  3. RPC
    1. 概述:
      1. RPC 的全称是 Remote Procedure Call(远程过程调用)是一种进程间通信方式
      2. 它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。 即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同
    2. 特点:
      1. 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易
      2. 高效:过程调用看起来十分简单而且高效
      3. 通用:在单机计算中过程往往是不同算法部分间最重要的通信机制。 通俗一点说,就是一般程序员对于本地的过程调用很熟悉,那么我们在通过网络做远程通信时,通过RPC 把远程调用做得和本地调用完全类似,那么就更容易被接受,使用起来也就毫无障碍。
    3. RPC框架
      1. 用户(User)
      2. 用户存根(User-Stub)
      3. RPC通信包(称为RPCRuntime)
      4. 服务器存根(Server-Stub)
      5. 服务器(Server)
    4. RPC过程:
      1. 服务消费方(User)调用以本地调用方式调用服务
      2. User-stub(存根)接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体,并交给RPCRuntime模块
      3. RPCRuntime找到服务地址,并将消息发送到服务端
      4. 服务端的RPCRuntime收到消息后,传给Server-stub
      5. Server-stub根据解码结果调用本地的服务
      6. 本地服务执行并将结果返回给Server-stub
      7. server stub将返回结果打包成消息并发送至消费方
      8. client stub接收到消息,并进行解码
      9. 服务消费方得到最终结果
    5. RPC调用细节:
      1. 接口方式的调用:RPC设计的目的在于可以让调用者可以像以本地调用方式一样调用远程服务,具体实现的方式是调用接口的方式来调用。在java中,底层通过Java动态代理方式生成接口的代理类,代理类中封装了与远程服务通信的细节:
        1. 客户端的请求消息:
          1. 接口名称
          2. 方法名
          3. 参数类型以及相应的参数值
          4. requestID,标识唯一请求id
        2. 服务器端的返回消息:
          1. 返回值
          2. requestID
      2. 序列化
      3. 通信:一般RPC通信都是基于NIO进行通信
    6. RPC框架特点
      1. 基于RPC的进程通信方式
      2. 有自定义的一套序列化和反序列的机制
      3. 客户端通过代理机制调用远程方法
      4. 服务端通过回调机制执行方法及返回结果
    7. Protobuffer
      1. 概述
        1. Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件
        2. Google Protobuf用于 RPC 系统和持续数据存储系统
        3. Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化,很适合做数据存储或 RPC 数据交换格式
        4. 可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式
        5. 目前提供了 C++、Java、Python 三种语言的 API
      2. 特点
        1. 平台无关、语言无关
        2. 二进制、数据自描述
        3. 提供了完整详细的操作API
        4. 高性能,比xml要快20-100倍
        5. 尺寸小,比xml要小3-10倍,高可扩展性、
        6. 数据自描述、前后兼容
      3. 数据类型
        1. 基本类型

          proto类型

          Java类型

          说明

          double

          double

          双精度浮点型

          float

          float

          单精度浮点型

          int32

          int

          使用可变长编码方式,编码负数的时候不够高效。如果编码负数,建议使用sint32

          int64

          long

          使用可变长编码方式。编码负数的时候不够高效。如果包含负数,建议使用sint64

          uint32

           

          对应于无符号整数int

          uint64

           

          对应于无符号整数long

          sint32

          int

          使用可变长编码方式。编码通常比int32高效

          sint64

          long

          使用可变长编码方式。编码通常比int64高效

          fixed32

          int

          固定4个字节。如果数值比228大的话,用此方式比较高效

          fixed64

          long

          固定8个字节。如果数值比256大的话,用此方式比较高效

          sfixed32

          int

          固定4个字节

          sfixed64

          long

          固定8个字节

          bool

          boolean

          布尔值

          string

          String

          字符串

          bytes

          ByteString

          字节序列

        2. 复杂类型

          Proto类型

          Java类型

          message

          class

          enum

          enum

          map

          Map

          service

          RPC interface

      4. API-序列化
        1. 写一个.proto文件。格式如下:
        2. package cn.tedu;

          message Person {

               

              // 定义int类型的属性id

              // required表示该属性为必须属性

              required int32 id = 1;

               

              // 定义String类型的属性name 

              required string name = 2;

               

              // 定义int类型的属性age

              // optional表示可选属性

              optional int32 age = 3;

               

           

          }

        3. 利用protoc.exe编译该文件产生对应的Java文件:protoc --java_out . Person.proto
        4. 在Eclipse中新建工程,并导入对应jar包
        5. 将产生Java文件放到对应的src目录下
        6. 序列化/反序列化操作
        7. public class ProtoBuffDemo {

           

              public static void main(String[] args) throws FileNotFoundException, IOException {

           

                  // 创建对应的Person对象

                  // 注意:该创建方式是建造者模式

                  Person p = Person.newBuilder().setId(1).setName("Amy").setAge(15).build();

                  System.out.println(p);

           

                  // 序列化/反序列化对象

                  // 方式一:转化为字节数组

                  // byte[] data = p.toByteArray();

                  // Person px = Person.parseFrom(data);

                  // System.out.println(px);

           

                  // 方式二:利用流写出或者读取

                  p.writeTo(new FileOutputStream("person.data"));

                  Person px = Person.parseFrom(new FileInputStream("person.data"));

                  System.out.println(px);

              }

           

          }

      5. API-RPC

        1. 写一个.proto文件。格式如下:
        2. package cn.tedu.rpc;

          option java_generic_services=true// 开启rpc

          // 定义请求参数

          message Req {

              required double num1 = 1;

              required double num2 = 2;

              required string mname = 3;

          };

           

          // 定义返回结果

          message Resp {

              required double result = 1;

          };

           

          // 定义要执行的方法

          service CalcService {

              rpc add(Req) returns (Resp);

          };

        3. 利用protoc.exe产生Java文件:protoc --java_out . Calc.proto
        4. 在Eclipse中新建工程,并导入对应jar包
        5. 将产生Java文件放到对应的src目录下
        6. 实现客户端
        7. import java.io.IOException;

          import java.io.OutputStream;

          import java.net.InetSocketAddress;

          import java.net.Socket;

           

          import com.google.protobuf.Descriptors.MethodDescriptor;

          import com.google.protobuf.Message;

          import com.google.protobuf.RpcCallback;

          import com.google.protobuf.RpcChannel;

          import com.google.protobuf.RpcController;

           

          import cn.tedu.rpc.Calc.CalcService;

          import cn.tedu.rpc.Calc.Req;

          import cn.tedu.rpc.Calc.Resp;

           

          public class Client {

           

              public static void main(String[] args) {

           

                  // 获取要调用的远程对象的本地存根

                  // 该存根和远程的方法一致

                  // 调用存根的任何方法,在底层都会调用传入的RPChannel中的callMethod方法

                  // 所以需要自己来实现callMethod方法来实现其中的真正远程调用

                  CalcService stub = CalcService.newStub(new RpcChannel() {

           

                      @Override

                      public void callMethod(MethodDescriptor md, RpcController controller, Message req, Message resp,

                              RpcCallback cb) {

           

                          try {

                              Socket s = new Socket();

                              s.connect(new InetSocketAddress("127.0.0.1", 8080));

                              // 获取指向远程服务器的输出流

                              OutputStream out = s.getOutputStream();

                              // 序列化请求对象

                              byte bs[] = req.toByteArray();

                              // 进行发送

                              out.write(bs);

                              s.shutdownOutput();

                              // 获取返回结果

                              Resp rx = Resp.parseFrom(s.getInputStream());

                              s.shutdownInput();

           

                              // 调用回调对象

                              cb.run(rx);

                               

                              s.close();

                          } catch (IOException e) {

                              e.printStackTrace();

                          }

                      }

                  });

           

                  // 准备请求对象

                  Req req = Req.newBuilder().setNum1(3).setNum2(5).setMname("add").build();

           

                  // 调用方法

                  stub.add(null, req, new RpcCallback() {

           

                      @Override

                      public void run(Resp resp) {

                          System.out.println("获取到远程调用的结果:" + resp.getResult());

           

                      }

                  });

           

              }

           

          }

        8. 实现服务器端
        9.     }

               

          }

          import java.net.ServerSocket;

          import java.net.Socket;

           

          import com.google.protobuf.BlockingService;

          import com.google.protobuf.Descriptors.MethodDescriptor;

          import com.google.protobuf.RpcController;

          import com.google.protobuf.ServiceException;

           

          import cn.tedu.rpc.Calc.CalcService;

           

          import cn.tedu.rpc.Calc.Req;

          import cn.tedu.rpc.Calc.Resp;

          import cn.tedu.rpc.Calc.CalcService.BlockingInterface;

           

          public class Server {

           

              public static void main(String[] args) throws Exception {

           

                  // 创建服务器端对象

                  ServerSocket ss = new ServerSocket(8080);

                  Socket s = ss.accept();

                   

           

                  // 获取请求参数

                  Req req = Req.parseFrom(s.getInputStream());

                  s.shutdownInput();

           

                  // 创建真正的远程调用对象,用于处理客户端发送来的调用请求

                  CalcServiceImpl calc = new CalcServiceImpl();

                   

                  // 包装calc对象,可以便捷的实现利用方法名来调用真正的方法

                  BlockingService service = CalcService.newReflectiveBlockingService(calc);

                  // 获取方法名以及对应的实际方法

                  MethodDescriptor descriptor = service.getDescriptorForType().findMethodByName(req.getMname());

                  // 执行方法

                  Resp resp = (Resp) service.callBlockingMethod(descriptor, null, req);

                  // 写出数据

                  resp.writeTo(s.getOutputStream());

                  s.shutdownOutput();

                  // 关流

                  ss.close();

           

              }

           

          }

           

          class CalcServiceImpl implements BlockingInterface {

           

              @Override

              public Resp add(RpcController controller, Req request) throws ServiceException {

                   

                  // 计算结果

                  Resp resp = Resp.newBuilder().setResult(request.getNum1() + request.getNum2()).build();

                   

                  return resp;

           

你可能感兴趣的:(高并发Avro(心得))