RPC (Remote Procedure Call) is a method of calling a remote service and receiving a corresponding response. In simple terms, RPC defines a communication process, with no specific constraints on the implementation technology. The core challenges to address in RPC are serialization and network communication. For example, message content can be serialized and deserialized using gob/json/pb/thrift
, and network communication can be achieved using socket/http
. As long as the client and server agree on these aspects, they can correctly parse the messages.
Generally, an RPC framework includes code generation, serialization, and network communication. Mainstream microservices frameworks also provide capabilities for service governance, such as service discovery, load balancing, and circuit breaking.
An RPC invocation involves the following basic steps, divided into the client and server sides:
Step 2, which includes service governance processes, is often referred to as “service governance.” It typically includes, but is not limited to, service discovery, load balancing, ACL, circuit breaking, rate limiting, and other functionalities. These features are provided by other components and are not specific to the Thrift framework.
For example, the development process for an RPC service based on Thrift typically involves the following steps:
The abbreviation for IDL is Interface Definition Language. If we want to use RPC for invocation, we need to know the interface of the other party, what parameters need to be passed, and also the format of the return value. It’s like two people communicating with each other, they need to ensure that they are speaking the same language and talking about the same thing. IDL is used to solve this problem by defining the protocol for both parties, just like when we call a function in code, we need to know its signature.
For RPC frameworks, IDL serves as both an interface description language and a module for generating specific language interfaces based on the IDL file. This greatly simplifies the development work. The service provider (server) only needs to write the IDL, use code generation tools to generate code, and then implement the interface. The service caller (client) only needs to generate code based on the IDL provided by the service provider and make calls. This also involves service discovery, load balancing, and other issues, but they are not within the scope of IDL and will not be discussed in detail here.
Kitex supports two types of IDL by default: Thrift and proto3. This article provides a brief introduction to the syntax of Thrift IDL. For proto3 syntax, you can refer to the Language Guide (proto3) provided by Google.
Please note: Thrift is an RPC framework that uses IDL files with the .thrift extension. Therefore, the term “thrift” is often used to refer to IDL. Please determine the context to understand the meaning correctly.
Thrift IDL has the following primitive types:
Note: Thrift IDL does not have unsigned integer types because many programming languages do not have native support for unsigned integers.
Represents a byte array without encoding requirements. It should be used for byte array cases (e.g., transmitting serialized data in JSON/PB between Thrift RPCs) instead of the string type.
Thrift provides strongly-typed containers that map to commonly used container types in most programming languages. The following three container types are supported:
Thrift supports type definitions similar to C/C++:
typedef i32 MyInteger
typedef Tweet ReTweet
Note: typedef definitions do not end with a semicolon.
Thrift provides enumeration types:
enum TweetType {
TWEET, // 0
RETWEET = 2, // 2
DM = 0xa, // 10
REPLY // 11
}
Thrift supports C-style multi-line comments and C++/Java-style single-line comments:
/*
* This is a multi-line comment.
* Just like in C.
*/
// C++/Java style single-line comments work just as well.
Thrift namespaces are similar to C++ namespaces and Java packages. They provide a way to organize (isolate) code and avoid naming conflicts within type definitions.
Thrift provides namespace definitions for different languages:
namespace cpp com.example.project
namespace java com.example.project
namespace go com.example.project
To facilitate the management and maintenance of IDL, it is often necessary to split the Thrift IDL definitions into different files. Thrift allows files to include other Thrift files, and users can access specific definitions using the file name as a prefix.
include "tweet.thrift" ...
struct TweetSearchResult {
1: list<tweet.Tweet> tweets;
}
Thrift defines constants as follows:
const i32 INT_CONST = 1234;
const map<string,string> MAP_CONST = {
"hello": "world",
"goodnight": "moon"
}
A struct in Thrift is composed of different fields, where each field has a unique integer id, a type, a name, and an optional default value 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 can contain other structs
16: optional string language = "english" // Default value can be set
}
Note:
An exception in Thrift is similar to a struct, but it is used to integrate with the exception handling mechanism of the target programming language. All the field names defined within an exception must be unique.
In Thrift, a service definition is semantically equivalent to an interface in OOP. Code generation tools generate client and server-side interface implementations based on the service definition.
oneway itself does not guarantee reliability and has some special handling risks, so it is not recommended to use it.
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
}
Below is a simple Thrift IDL example consisting of two files: common.thrift
and 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
}
common.thrift
and defines a service.TestRequest
and returns a value of type TestResponse
.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)
}
To comply with the specifications for service invocations, Kitex imposes certain requirements on the IDL definitions:
XXXRequest
.void
. The return type should also be named using camel case, typically XXXResponse
.Apache Thrift - Thrift Type System