Google Protobuf 使用方式分析

对于 RPC 协议来说,最重要的就是对象的发送与接收,这就要用到序列化与反序列化,也称为编码和解码,序列化与反序列化和网络传输一般都在对应的 RPC 框架中完成。

序列化与反序列化的流程如下:

JavaBean-> stub(client) <->skeleton(server)->JavaBean,简单点说就是编码和解码。

相比于 RMI 远程方法调用,很多 RPC 远程过程调用的跨语言的,这就需要序列化于反序列化协议也支持跨语言。Google Procobuf 就是这样一种跨语言的序列化于反序列化协议,效率非常高(怎么做到比其他协议效率高那?比其他协议压缩生成的对象小)。

Netty 对于 ProtoBuf 提供了很好的支持。

先看如何单独使用 Google ProtoBuf

  1. 新建 .proto 结构描述文件

    1. syntax = "proto2";
    2. package com.paul.protobuf;
    3. //加快解析速度
    4. option optimize_for = SPEED
    5. option java_package = "com.paul.protobuf";
    6. option java_outer_classname = "DataInfo";
    7. message Student{
    8. reuqired string name = 1;
    9. option int32 = 2;
    10. option string address = 3;
    11. }
  2. 使用对应的编译文件生成对应的 Java 类

    Proton —java_out src/main/java src/protobuf/Student.proto

  3. 这时在我们代码的 src/main/java 文件夹下生成了一个新的 pkg com.paul.protobuf,里面生成了 DataInfo 类。对象会有对应的 builder 方法让我们来构建。

  4. 测试序列化方法

    1. // 构建对象->字节->对象
    2. public class ProtoBufTest{
    3. public static void main(String[] args) throws Exception{
    4. DataInfo.Student student = DataInfo.Student.newBuilder().setName("张三").setAge(20).setAddress("abc").build();
    5. byte[] student2ByteArray = student.toByteArray();
    6. DataInfo.Student student2 = DataInfo.Student.parseFrom(student2ByteArray);
    7. System.out.println(studdent2);
    8. }
    9. }

在来看 Netty 对 Google ProtoBuf 的支持

还是只给出不一样的部分(服务单和客户端的这部分是一样的):

  1. @Override
  2. protected void initChannel(SocketChannel ch) throws Exception{
  3. ChannelPipeline pipeline = ch.pipeline();
  4. pipeline.addLast(new ProtobufVarint32FrameDecoder());
  5. //解码器
  6. pipeline.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));
  7. pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
  8. //编码器
  9. pipeline.addLast(new ProtobufEncoder());
  10. pipeline.addLast(new MyServerHandler());
  11. }

测试方法就是在客户端组装一个 DataInfo.Student 然后发送给服务端,这里就不演示了。

大家可能会发现上面的代码存在一个问题,就是上面的程序只能对 DataInfo.Student 进行编解码,如果传递消息的类型有多种怎么办那?

解决方案一:定义义协议,需要自己实现解码器,通过前两位来标识具体的 JavaBean 类型。

解决方案二:定义一个最外层的类,通过枚举的方式来确定传递的 JavaBean 类型。

比如我们有两个 JavaBean

  1. message MyMessage{
  2. enum DataType{
  3. PersonType = 1;
  4. DogType = 2;
  5. CatType = 3;
  6. }
  7. required Datatype data_type = 1;
  8. //oneof 在同一时刻只有一个字段会被设置,字段之间会共享内存,后面设置会自动清空前面的。
  9. oneof dataBody{
  10. Person person = 2;
  11. Dog dog = 3;
  12. Cat cat = 4;
  13. }
  14. }
  15. message Person{
  16. option string name = 1;
  17. option int32 age = 2;
  18. option string address = 3;
  19. }
  20. message Dog{
  21. option string name = 1;
  22. option int32 age = 2;
  23. }
  24. message Cat{
  25. option string name = 1;
  26. option int32 city = 2;
  27. }

Pipeline 的改动(客户端和服务端):

  1. pipeline.addLast(new ProtobufDecoder(DataInfo.MyMessage.getDefaultInstance()));

我们自己的 handler 的改动:

  1. @Overrode
  2. public void channelActive(ChannelHandlerContext ctx) throws Exception{
  3. MyDataInfo.MyMessage myMessage = MyDataInfo.MyMessage.newBuilder().
  4. setDataType(DataType.PersonType.PersonType).
  5. setPerson(MyDataInfo.Person.newBuilder().
  6. setName("张三").setAge(20).
  7. setAddress("111").build()).
  8. build();
  9. ctx.channel().writeAndFlush(myMessage);
  10. }

服务端 handler 根据 enum 的类型分别进行解析。

在实际的应用环境中,我们客户端和服务端大概率是两个分开的应用程序,此时我们使用 Google ProtoBuf 时 .proto 文件和对应的 class 文件是不是需要在两边都保存一份,如果有修改会非常麻烦。下面我们介绍一种最佳实践。

最佳实践是使用 git 作为版本控制系统为前提的:

不那么好的方案:git submodule,就相当于 maven 的子模块,客户端和服务端都依赖这个模块。

比较好的方案:git subtree,将公共的代码合并到 server 和 client 端,相当于向 server 和 client 提交代码。

Apache Thrift 使用方式与文件编写方式分析

Apache Thrift 和 Google ProtoBuf 整体非常相似,适用于可伸缩的跨语言的服务开发。Thrift 相当于 Netty + Google ProtoBuf,是一个高性能 RPC 框架。Thrift 底层是 socket + RPC 的模式。

Thrift 是一个典型的 CS 结构,客户端和服务端可以使用不同的语言开发,既然客户端和服务端能使用不同的语言开发,那么一定有一种中间语言来关联服务端和客户端,这就是 IDL(Interface Description Language)。

Thrift 如何实现多语言之间的通信?

数据传输使用 socket (多种语言均支持),数据再以特定的格式(String 等)发送,接收方语言进行解析。

如何使用?

定义 thrift 的文件,由 thrift 文件(IDL) 生成双方语言的接口,model,在生成的 model 以及接口中会有解码编码的代码。

Thrift 中的服务

Thrift 定义服务相当于 Java 中创建 Interface 一样,创建的 service 经过代码生成命令之后就会生成客户端和服务端的框架代码,定义形式如下:

  1. service HelloWorldService{
  2. //service 中定义的函数,相当于 java interface 中定义的方法
  3. string doAction(1:string name, 2:i32 age);
  4. }

.thrift 文件的定义

  1. // java 中的包名
  2. namespace jave thrift.generate
  3. // 定义别名
  4. typedef i16 short
  5. typedef i32 int
  6. typedef i64 long
  7. typedef bool boolean
  8. typedef string String
  9. struct Person{
  10. 1: optional String username,
  11. 2: optional int age,
  12. 3: optional boolean married
  13. }
  14. exception DataException{
  15. 1: optional String message,
  16. 2: optional String callStack,
  17. 3: optional String date
  18. }
  19. service PersonService{
  20. Person getPersonByName(1: required String username) throws (1: DataException dataException),
  21. void savePerson(1:requried Person person) throws (1:DataException dataException)
  22. }

编译 thrift 文件

  1. thrift --gen java src/thrift/data.thrift

生成的文件

Person.java 里面包含了编解码的方法,PersonService 里面包含了 getPersonByName 和 savePerson 的方法。

测试方法:

服务端服务的具体实现方法

  1. public class PersonServiceImpl implements PersonService.Iface{
  2. @Override
  3. public Person getPersonByName(String username) throws DataException,TException{
  4. Person p = new Person();
  5. p.setUserName("paul");
  6. p.setAge(25);
  7. p.setMarried(true);
  8. return p;
  9. }
  10. @Override
  11. public void savePerson(Person person) throws DataException,TException{
  12. System.out.println(person.getUserName());
  13. }
  14. }

Thrift 的服务端:

  1. public class ThriftServer{
  2. public static void main(String[] args){
  3. //非阻塞的 socket server
  4. TNonblockingServerSocket socket = new TNonblockingServerSocket(8899);
  5. // 高可用的 server
  6. THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
  7. PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
  8. arg.protocolFactory(new TCompactPrococol.Factory());
  9. arg.transportFactory(new TFramedTransport.Facotry());
  10. arg.processorFactory(new TProcessorFactory(processor));
  11. TServer server = new THsHaServer(arg);
  12. System.out.println("Thrift Server Started");
  13. //死循环
  14. server.serve();
  15. }
  16. }

Thrift 的客户端:

  1. public class ThriftClient{
  2. publiuc static void main(String[] args){
  3. TTransport transport = new TFramedTransport(new TSocket
  4. ("localhost",8899),600);
  5. TProcotol procotol = new TComapctProcotol(transport);
  6. PersonService.Client client = new PersonService.Client(procotol);
  7. try{
  8. //打开 socket
  9. transport.open();
  10. //好像调用本地方法一样
  11. Person person = client.getPersonByName("paul");
  12. System.out.println(person.getAge());
  13. }catch(Exception ex){
  14. throw ex;
  15. }finally{
  16. transport.close();
  17. }
  18. }
  19. }

Thrift 的架构:

Thrift 的传输格式,协议:

TBinaryProtocol-二进制格式

TCompactProtocol-压缩格式

TJSONProtocol-JSON 格式

TSimpleJSONProtocol-提供 JSON 只写协议,生成的文件很容易通过脚本语言解析。很少使用,缺少元数据信息,接收方不能读取出来。

TDebugProtocol-使用易懂的可读的文本格式,以便于 debug。

Thrift 数据传输方式,transport:

TSocket-阻塞式 socket。

TFramedTransport-以 frame 为单位进行传输,非阻塞式服务中使用。

TFileTransport-以文件形式进行传输。

TMemoryTransport-将内存用于 I/O,Java 实现时内部实际使用了简单的 ByteArrayOutputStream。

支持的服务模型,server:

TSimpleServer-简单的单线程服务模型,常用于测试。

TThreadPoolServer-多线程服务模型,标准的阻塞式 IO。

TNonboockingServer-多线程服务模型,使用非阻塞式 IO(需要使用 TFramedTransport 数据传输方式)。

THsHaServer-THsHa 引入了线程池去处理,其模型把读写任务放到线程池处理。Half-sync/Half-async 的处理模式,Half-sync 是在处理 IO 事件上,Half-async 用于 handler 对 rpc 的同步处理。

精通并发与 Netty (二)常用的 rpc 框架的更多相关文章

  1. 精通并发与 Netty (一)如何使用

    精通并发与 Netty Netty 是一个异步的,事件驱动的网络通信框架,用于高性能的基于协议的客户端和服务端的开发. 异步指的是会立即返回,并不知道到底发送过去没有,成功没有,一般都会使用监听器来监 ...

  2. 《精通并发与Netty》学习笔记(01 - netty介绍及环境搭建)

    一.Netty介绍     Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序.     ...

  3. 什么是RPC,RPC好处,常用的RPC框架

    RPC简介 RPC(Remote Procedure Call Protocol)远程过程调用协议.一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用 ...

  4. 企业常用的RPC框架比较

    RPC框架比较     语言 协议 服务治理 社区 机构 Hessian 多语言 hessian(二进制) – 不活跃 Caucho Thrift 多语言 thrift – 活跃 Apache Fin ...

  5. 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)

    一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行)   LineBasedFrameDecoder是回 ...

  6. 《精通并发与Netty》学习笔记(11 - 详解NIO (二) 分散/聚集 Scatter/Gather、Selector)

    一.分散/聚集 Scatter/Gather scatter/gather指的在多个缓冲区上实现一个简单的I/O操作,比如从通道中读取数据到多个缓冲区,或从多个缓冲区中写入数据到通道:scatter( ...

  7. 《精通并发与Netty》学习笔记(07 - 基于Thrift实现Java与Python的RPC调用)

    上节我们介绍了基于Thrift实现java与java的RPC调用,本节我们基于Thrift实现Java与Python的RPC调用 首先,修改data.thirft文件,将命名空间由java改为py n ...

  8. 《精通并发与Netty》学习笔记(06 - Apache Thrift使用简介)

    一.概述 Apache Thrift 是 Facebook 实现的一种高效的.支持多种编程语言的远程服务调用的框架.Thrift是由Facebook开发的,并在2008年捐给了Apache基金会,成为 ...

  9. 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)

    一.粘包/拆包概念 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认 ...

随机推荐

  1. Linux crontab 语法和具体的例子

    基本格式 : * * * * * command 分 时 日 月 周 命令 第1列表示分钟1-59 每分钟用*或者 */1表示 第2列表示小时1-23(0表示0点) 第3列表示日期1-31 第4列表示 ...

  2. 让你的sublime text写C代码 (sublime text 2 配置构建C开发环境)

    原则 1. 首先你要配置能够编译C++/C环境 2. window中配置该执行环境的环境变量,能够全局使用 3. sublime Text创建新的构建机制.并设置用改全局编译环境 具体过程 能够编译C ...

  3. 【Linux知识】server性能测试--UnixBench

    链接地址: http://blog.csdn.net/jason_asia/article/details/38309079 1.1.   server性能测试UnixBench 分别DELL R72 ...

  4. 关于hibernate组件配置

    建立关系数据模型的一个重要原则是在不会导致数据冗余的前提下,尽可能减少数据库表的数目及表之间的外键参照关系.以员工信息为例,员工信息中有员工的家庭地址信息,如果把地址信息单独放在一张表中,然后建立员工 ...

  5. WPF学习笔记:(二)数据绑定模式与INotifyPropertyChanged接口

    数据绑定模式共有四种:OneTime.OneWay.OneWayToSource和TwoWay,默认是TwoWay.一般来说,完成数据绑定要有三个要点:目标属性是依赖属性.绑定设置和实现了INotif ...

  6. WCF 大文件传输配置

    <bindings> <webHttpBinding> <!--这个是接收大数据加的,设置WCF服务器端数据接收上限参数,此处单位字节,故2147483647字节==2G ...

  7. 通通玩blend美工(3)——可爱的云

    原文:通通玩blend美工(3)--可爱的云 好久没有写这个系列的博客了,这里给个电梯吧,照顾新来的同学~~ 通通玩blend美工(1)——荧光Button 通通玩blend美工(2)——时钟 目前我 ...

  8. laravel在wamp中输入地址后总是无法访问

    在wamp中的apache中conf的httpd.conf #LoadModule rewrite_module modules/mod_rewrite.so 改为 LoadModule rewrit ...

  9. iOS UITableView动态隐藏或显示Item

    通过改变要隐藏的item的高度实现隐藏和显示item 1.创建UITableView #import "ViewController.h" @interface ViewContr ...

  10. fileapi.h里的API函数(包括LockFileEx和FindFirstChangeNotification函数)

    /** * This file is part of the mingw-w64 runtime package. * No warranty is given; refer to the file ...