RPC (Remote Procedure Call) ,即远程过程调用。通俗来讲,就是调用远端服务的某个方法,并获取到对应的响应。RPC 本质上定义了一种通信的流程,而具体的实现技术没有约束,核心需要解决的问题为序列化与网络通信。如可以通过 gob/json/pb/thrift
来序列化和反序列化消息内容,通过 socket/http
来进行网络通信。只要客户端与服务端在这两方面达成共识,能够做到消息正确的解析接口即可。
一般来说,RPC 框架包括了代码生成、序列化、网络通讯等,主流的微服务框架也会提供服务治理相关的能力,比如服务发现、负载均衡、熔断等等。
一次 rpc 调用包括以下基本流程,分为客户端和服务端两个部分:
其中步骤 2 中包含的流程称为「服务治理」,通常包括并不限于服务发现、负载均衡、ACL、熔断、限流等等功能。这些功能是由其他组件提供的,并不是 Thrift 框架所具有的功能。
例如基于 Thrift 的 RPC 服务开发,通常包括如下过程:
IDL 全称是 Interface Definition Language,接口定义语言。
如果我们要使用 RPC 进行调用,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。IDL 就是为了解决这样的问题,通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道 签名
一样。
对于 RPC 框架,IDL 不仅作为接口描述语言,还会根据 IDL 文件生成指定语言的接口定义模块,这样极大简化了开发工作。服务提供方(服务端)需要做的变为 编写 IDL -> 使用代码生成工具生成代码 -> 实现接口;服务调用方(客户端)只需根据服务提供方(服务端)提供的 IDL 生成代码后进行调用。这当中还有服务发现、负载均衡等问题,但不属于 IDL 范畴,故不展开介绍。
Kitex 默认支持 thrift
和 proto3
两种 IDL。本文简单介绍 Thrift IDL 语法,proto3 语法可参考:Language Guide(proto3)
注意:Thrift 是一款 RPC 框架,其使用的 IDL 以 .thrift 为后缀,故常常也使用 thrift 来表示 IDL,请根据上下文判断语意。
Thrift IDL 有以下几种基本类型:
注意:Thrift IDL 没有无符号整数类型。 因为许多编程语言中没有原生的无符号整数类型。
Thrift 提供的容器是强类型容器,映射到大多数编程语言中常用的容器类型。具体包括以下三种容器:
Thrift 支持类似 C/C++ 的类型定义
typedef i32 MyInteger
typedef Tweet ReTweet
注意:typedef 定义的末尾没有分号
Thrift 提供了枚举类型
enum TweetType {
TWEET, //
RETWEET = 2, //
DM = 0xa,
REPLY
}
Thrift 支持 c风格的多行注释 和 c++/Java 风格的单行注释
/*
* This is a multi-line comment.
* Just like in C.
*/
// C++/Java style single-line comments work just as well.
Thrift 的命名空间与 C++ 的 namespace 和 java 的 package 类似,提供了一种组织(隔离)代码的方式,也可避免类型定义内名字冲突的问题。
Thrift 提供了针对不同语言的 namespace 定义方式:
namespace cpp com.example.project
namespace java com.example.project
namespace go com.example.project
为了方便管理、维护 IDL,常常需要将 Thrift IDL 定义拆分到不同的文件。Thrift 允许文件 include 其它的 thrift 文件,用户可利用文件名作为前缀对具体定义进行访问。
include "tweet.thrift" ...
struct TweetSearchResult {
1: list<tweet.Tweet> tweets;
}
Thrift 内定义常量的方式如下:
const i32 INT_CONST = 1234;
const map<string,string> MAP_CONST = {
"hello": "world",
"goodnight": "moon"
}
Struct 由不同的 fields 构成,其中每个 field 有唯一的整型 id,类型 type,名字 name 和 一个可选择设置的默认值 default value。
struct Location {
1: required double latitude;
2: required double longitude;
}
struct Tweet {
1: required i32 userId;
2: required string userName;
3: required string text;
4: optional Location loc; // Struct的定义内可以包含其他 Struct
16: optional string language = "english" // 可设置默认值
}
注意:
Exception 与 struct 类似,但它被用来集成 目标编程语言 中的异常处理机制。Exception 内定义的所有 field 的名字都是唯一的。
Thrift 内的 service 定义在语义上和 oop 内的接口是相同的。代码生成工具会根据 service 的定义生成 client 和 service 端的接口实现。
oneway 本身不具有可靠性,且在处理上比较特殊会带来一些隐患,不建议使用
service Twitter {
// A method definition looks like C code. It has a return type, arguments,
// and optionally a list of exceptions that it may throw. Note that argument
// lists and exception list are specified using the exact same syntax as
// field lists in structs.
void ping(); // 1
bool postTweet(1:Tweet tweet); // 2
TweetSearchResult searchTweets(1:string query); // 3
// The 'oneway' modifier indicates that the client only makes a request and does not wait for any response at all. Oneway methods MUST be void.
oneway void zip() // 4
}
以下为简单的 thrift idl 示例,包含 common.thrift 和 service.thrift 两个文件。
namespace go example.common
// typedef
typedef i32 TestInteger
// Enum
enum TestEnum {
Enum1 = 1,
Enum2,
Enum3 = 10,
}
// Constant
const i32 TestIntConstant = 1234;
// Struct
struct TestStruct {
1: bool sBool
2: required bool sBoolReq
3: optional bool sBoolOpt
4: list<string> sListString
5: set<i16> sSetI16
6: map<i32,string> sMapI32String
}
namespace go example.service
include "common.thrift"
struct TestRequest {
1: string msg
2: common.TestStruct s
}
struct TestResponse {
1: string msg
2: common.TestStruct s
}
service TestService {
TestResponse tMethod(1: TestRequest req)
}
为满足服务调用的规范,Kitex 对 IDL 的定义提出了一些必须遵守的要求:
XXXRequest
XXXResponse
Apache Thrift - Thrift Type system