精通并发与 Netty (一)常用的 rpc 框架

Google Protobuf 使用方式分析

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

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

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

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

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

先看如何单独使用 Google ProtoBuf

新建 .proto 结构描述文件

syntax = "proto2"; package com.paul.protobuf; //加快解析速度 option optimize_for = SPEED; option java_package = "com.paul.protobuf"; option java_outer_classname = "DataInfo"; message Student{ reuqired string name = 1; option int32 = 2; option string address = 3; }

使用对应的编译文件生成对应的 Java 类

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

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

精通并发与 Netty (一)常用的 rpc 框架

测试序列化方法

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

在来看 Netty 对 Google ProtoBuf 的支持

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

@Override protected void initChannel(SocketChannel ch) throws Exception{ ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ProtobufVarint32FrameDecoder()); //解码器 pipeline.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance())); pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); //编码器 pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new MyServerHandler()); }

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

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

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

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

比如我们有两个 JavaBean

message MyMessage{ enum DataType{ PersonType = 1; DogType = 2; CatType = 3; } required Datatype data_type = 1; //oneof 在同一时刻只有一个字段会被设置,字段之间会共享内存,后面设置会自动清空前面的。 oneof dataBody{ Person person = 2; Dog dog = 3; Cat cat = 4; } } message Person{ option string name = 1; option int32 age = 2; option string address = 3; } message Dog{ option string name = 1; option int32 age = 2; } message Cat{ option string name = 1; option int32 city = 2; }

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

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

我们自己的 handler 的改动:

@Overrode public void channelActive(ChannelHandlerContext ctx) throws Exception{ MyDataInfo.MyMessage myMessage = MyDataInfo.MyMessage.newBuilder(). setDataType(DataType.PersonType.PersonType). setPerson(MyDataInfo.Person.newBuilder(). setName("张三").setAge(20). setAddress("111").build()). build(); ctx.channel().writeAndFlush(myMessage); }

服务端 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 的模式。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfsxy.html