目录
RPC框架简介
简介
各种序列化协议优缺点
gRPC调用模式
gRPC跟ProtocolBuffers的关系
ProtocolBuffers协议
gRPC桩代码生成
gRPC线程模型
gRPC分层
gRPC开发经验
官网及快速开始
常见状态码
适用场景
适用
不适用
手写简易RPC框架
Dubbo学习笔记
一文详解Netty架构及优势
HTTP、HTTPS的优劣及其与RPC和WebSocket的对比
RPC框架简介
简介
一个RPC框架的基本构成主要如下图:
常见的RPC框架有以下几种:
1、apache dubbo
提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维
文档 | Apache Dubbo
2、Mtransport
美团致力于将Mtransport定位成一组高性能、高可用的企业级RPC通信框架,现将已在公司广泛使用,成熟稳定的Mtransport进行开源,开源后总名称为OCTO-RPC,其中第一批包括Dorado(Java)、Whale(C++)两个语言版本。支持服务注册、服务发现、异步通信、负载均衡等功能。
美团分布式服务通信框架及服务治理系统OCTO - 程序员大本营
GitHub - Meituan-Dianping/octo-rpc: OCTO-RPC 是支持Java和C++的企业级通信框架,在RPC服务之上扩展了丰富的服务治理功能,为美团各业务线提供高效、统一的通信服务。
3、motan
motan是新浪微博开源的RPC框架,除了常规的点对点调用外,motan还提供服务治理功能,包括服务节点的自动发现、摘除、高可用和负载均衡等。Motan具有良好的扩展性,主要模块都提供了多种不同的实现,例如支持多种注册中心,支持多种rpc协议等
https://www.cnblogs.com/lovebean/p/10622166.html
https://github.com/weibocom/motan
4、thrift
Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用框架来使用,是由Facebook为“大规模跨语言服务开发”而开发的。提供了全套RPC解决方案,包括序列化机制、传输层、并发处理框架等。
5、gRPC
gRPC是一款高性能开源通用的RPC框架,同时面向服务端跟移动端,基于HTTP/2协议设计。目前提供 C、Java 和 Go 语言版本。
dubbo | Mtransport | motan | thrift | gRPC | |
开发语言 | java | java/c++ | java | 多语言 | 多语言 |
服务治理 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
序列化框架 | 多框架 | thrift | 多框架 | thfit | protobuf |
服务注册发现 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
管理中心 | 支持 | 支持 | 支持 | 不支持 | 不支持 |
各种序列化协议优缺点
Response对象↔二进制
1、JDK自带序列化 outputstream/inputstream
-
缺点:
-
无法跨语言
-
序列化编码后的字节数组太大
-
序列化耗时严重
-
2、fastJSON:文本型序列化框架,String和对象的转换,应用广泛。如果 RPC 框架选用 JSON 序列化,服务提供者与服务调用者之间传输的数据量要相对较小,否则将严重影响性能。
3、Hessian:支持跨语言,序列化后字节数适中,API 易用。是国内主流 rpc 框架:dubbo,motan 的默认序列化协议。
4、Google 的Protobuf:提起 Protostuff 不得不说到 Protobuf。Protobuf 可能更出名一些,因为其是 google 的亲儿子,grpc 框架便是使用 protobuf 作为序列化协议,虽然 protobuf 与语言无关平台无关,但需要使用特定的语法编写 .prpto 文件,然后静态编译,这带了一些复杂性。而 protostuff 实际是对 protobuf 的扩展,protostuff-runtime 模块继承了 protobuf 性能,且不需要预编译文件,但与此同时,也失去了跨语言的特性。所以 protostuff 的定位是一个 JAVA 序列化框架,其性能略优于 Hessian。tip :protostuff 反序列化时需用户自己初始化序列化后的对象,其只负责将该对象进行赋值。
5、Message pack
6、JBoss Marshalling(序列化包)
7、Kryo:专为 JAVA 定制的序列化协议,序列化后字节数少,利于网络传输。但不支持跨语言(或支持的代价比较大)。dubbox 扩展中支持了 kryo 序列化协议。
......
gRPC调用模式
gRPC支持定义四种类型的调用:
-
一元RPC服务(unary RPCs): 客户端向服务端发送一次请求,服务端对请求作出一次响应,如同一次普通的函数调用
rpc SayHello(HelloRequest) returns (HelloResponse);
-
服务端流式RPC服务:这种模式是客户端发起一次请求,服务端返回一段连续的数据流。典型例子是oneService中读取hive数据的方法定义,源源不断的读取hive表数据。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
-
客户端流式RPC服务: 与服务端数据流模式相反,这次是客户端源源不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
-
双向流式RPC服务: 服务端和客户端使用各自的读写流发送消息序列。两个读写流独立工作,因此客户端和服务端可以以任意顺序进行读写, 例如服务端可以等到客户端发送完所有的消息才开始向流中写响应消息,也可以每读取一条消息就写一条响应消息,或者其他的顺序组合。但是每个流中的消息顺序是有保证的。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
gRPC跟ProtocolBuffers的关系
protobuf全名是ProtocolBuffers,是谷歌推出的二进制序列化协议,提供IDL文件来定义各种类型的数据。目前整体协议版本是proto3,protobuf提供了从proto文件编译生成各个语言文件的功能。与此同时protobuf提供了丰富的插件机制,用户可以扩展生成的对应语言的文件,俗称桩代码生成。
ProtocolBuffers协议
syntax = "proto2";
package tutorial;
option java_multiple_files = true;
option java_package = "com.example.tutorial.protos";
option java_outer_classname = "AddressBookProtos";
message Person {
optional string name = 1;
optional int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
optional string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
----------------------------------------------------------------------
syntax = "proto3";
package com.kuaishou.infra.grpc.test;
option java_package = "com.kuaishou.demo";
option java_outer_classname = "Demo";
option java_multiple_files = true;
message TestRequest {
string value = 1;
}
message TestResponse {
string value = 1;
}
service TestService {
rpc Test (TestRequest) returns (TestResponse);
rpc TestDeadline (TestRequest) returns (TestResponse);
rpc TestCost (TestRequest) returns (TestResponse);
rpc TestEmpty (TestRequest) returns (TestResponse);
}
service TestStreamService {
rpc Test (TestRequest) returns (stream TestResponse);
rpc TestFailed (TestRequest) returns (stream TestResponse);
rpc TestSlow (TestRequest) returns (stream TestResponse);
rpc TestUpStream(stream TestRequest) returns (TestResponse);
rpc TestUpStreamSlow(stream TestRequest) returns (TestResponse);
rpc TestDuplex(stream TestRequest) returns (stream TestResponse);
rpc TestOutboundFlowControl (TestRequest) returns (stream TestResponse);
}
-
proto文件路径:
-
proto文件本身所在路径:假设是kuaishou/demo;使用场景是当其他proto文件需要引入当前proto文件时依赖这个路径,形如 import "kuaishou/demo/demo.proto"。这样就可以使用文件内部定义的message了
-
proto文件内部package指定路径:com.kuaishou.infra.grpc.test;这个路径非常的核心,任何迁移或者修改proto文件的操作都不应该修改这个package路径,因为它决定了文件内部service的full path name。一旦发生变化就会造成在实际gRPC调用时找不到服务方法而抛异常(Unimplemented)
-
option参数指定java输出路径:com.kuaishou.demo;这个路径也比较核心,定义好之后也不要轻易修改,因为它决定了proto中message类的生成路径,如果修改会造成源码不兼容编译阻塞
-
-
proto文件输出样式
-
proto文件的名称,例如infra_demo.proto;在没指定2的前提下,会默认按照驼峰生成OuterClass,对于本例为InfraDemo.java;有意思的是当proto文件中的message跟默认生成java类名重名的时候,会在生成类名后面加上OuterClass,对于本例为InfraDemoOuterClass
-
option java_outer_classname = "Demo"如果指定此选项,则输出文件名称为指定名称Demo
-
option java_multiple_files = true:指定此选项proto文件中的message会生成独立的java文件,此选项定义好后也不要轻易修改,否则也会造成编译阻塞,推荐开启这个选项
-
gRPC桩代码生成
gRPC通过Protocol Buffers提供的Plugin机制来实现原生桩代码生成,Protobuf的插件化桩代码生成机制主体思想如下:
-
FileDescriptor 作为Plugin的输入参数,代表一个proto文件的描述
-
ServiceDescriptor代表一个service的描述符
-
MethodDescriptor代表一个service中方法的描述符,包括上面描述的四种MethodType
-
Printer作为输出写文件到对应路径
过程可以拆解为如下几个部分:
-
ServiceDescriptor、MethodDescriptor单例变量,出于性能考虑
-
ClientStub,主要包括三种:
-
XXXBlockingStub:官方已经弃用
-
XXXFutureStub:返回ListeningFuture,也是我们使用最多的方式
-
XXXStub:异步方式,业务通过StreamObserver回调来感知数据流转
-
XXXImplBase:服务实现基类
gRPC桩代码生成的扩展方式有两种:
-
在原生桩代码生成之上做修改,需要修改c++文件重新编译构建
-
直接利用Protocol Buffers提供的扩展机制自己生成全新的文件
gRPC线程模型
grpc基于http2.0传输协议进行数据传输,使用NIO线程模型实现服务远程调用:
-
client侧线程模型
-
Caller线程:业务当前线程
-
Worker线程(grpc-default-executor):请求处理与响应回调线程
-
IO线程(grpc-nio-worker-ELG):使用Netty的EventLoopGroup(ELG)
-
-
server侧线程模型
-
Worker线程(grpc-default-executor):Server启动时候指定
-
IO线程(grpc-nio-worker-ELG):使用Netty的ELG
-
gRPC分层
在gRPC中,客户端应用可以通过桩代码直接调用运行在另一台机器上的服务端应用的方法,就像客户端调用自己的本地方法一样。这一特性使得我们可以轻易构建分布式的应用和服务。
grpc中使用protobuf定义桩代码,通过protoc-gen-grpc-java插件生成java代码,同时protobuf支持自定义代码生成器。
-
JAVA业界主流实现方式
-
salesforce简介:https://en.wikipedia.org/wiki/Salesforce.com
-
salesforce提供工具包支持以JAVA方式来扩展protoc:GitHub - salesforce/grpc-java-contrib: Useful extensions for the grpc-java library
-
参考实践:Watchers · salesforce/reactive-grpc · GitHub
-
https://itnext.io/customizing-grpc-generated-code-5909a2551ca1
-
gRPC开发经验
官网及快速开始
gRPC
Quick start | Java | gRPC
常见状态码
grpc/doc/statuscodes.md at master · grpc/grpc · GitHub
-
OK:代表请求成功执行
-
CANCELLED:请求被取消,通常是超时 or Deadline Exceed导致
-
UNKNOWN:通常是Server抛异常导致,一般Server业务执行抛异常非StatusException or RuntimeStatusException,所以gRPC内部会用UNKNOWN状态码进行处理
-
DEADLINE_EXCEEDED:客户端请求配置了withDeadlineAfter CallOption,双端都生效,服务端超时会取消请求,Client侧表现为收到这个状态码的RuntimeStatusException,也是我们最常见的异常
-
PERMISSION_DENIED:顾名思义,代表没有访问权限,可以对应HTTP的403,常见如IP白名单
-
UNAUTHENTICATED:gRPC特意用这个状态码代表没有授权的访问,通常是开启tls的服务
-
RESOURCE_EXAUSTED:代表服务端资源不足,如带宽,磁盘,连接数等等;业务也可以使用它来描述业务资源不足的情况
-
UNIMPLEMENTED:gRPC内部异常,请求的FullPath方法未实现,如果没有FallbackRegistry会抛这个状态码异常,通常可能是proto文件有不兼容改动
-
UNAVAILABLE:代表当前服务不可用,一般是暂时性问题,代表客户端可以通过重试来解决的情况
-
INTERNAL:gRPC内部严重错误,通常是有严重bug才会出现,业务不应该使用此状态码
适用场景
适用
-
对于给其他Team提供接口时,应该优先使用gRPC服务
-
对于消耗特定资源的(例如GPU/CPU/大内存资源),有特殊依赖的(比如运行环境需要依赖外部的ffmpeg等binary等),集中实现效率比较高的(比如某个业务集中到几台机器,可以使用大内存缓存等)
-
一些经常变化的业务逻辑,依赖很广泛,又需要实现可以收敛且能随时更新
-
其他需要做〔服务化〕的业务
不适用
-
不关心返回值的one-way调用或者异步请求,应该优先使用kafka
-
一些极高QPS,逻辑稳定,没有远程访问的本地实现
-
使用方为公司外部的服务(这类大概率需要暴露成http服务)