随着近些年微服务的盛行,使得服务逐渐模块化,功能化,单个服务仅仅实现某个特定的功能或者模块,因此服务间的调用变得常见且频繁,所以高性能且快速响应的服务调用成了必须去面对的问题,传统的http请求能面对跨语言的问题,但是性能远远无法达到高并发的要求,因此更偏向底层的RPC框架越来越受到青睐,像阿里的Dubbo,谷歌的gRPC,facebook的Thrift等等!本节将学习Thrift这个跨语言的Thrift RPC框架!
Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目,主要用于各个服务之间的RPC通信,支持跨语言,常用的语言比如C++, Java, Python, PHP, Ruby, Erlang, Perl, C#, JavaScript, Node.js等都支持。(博主也是一名多语言爱好者,因此对Thrift也比较感兴趣,所以在后面,我也会写java,python 的这两种语言的RPC调用。)
Thrift是一个典型的CS(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,没错,这种语言就是IDL(Interface Description Language)。
IDL 是一种用于定义接口和数据结构的语言,用于描述 Thrift 的服务接口和数据类型。在 Thrift 中使用的是 Thrift 自定义的 IDL 语言,它具有类似于其他接口描述语言的特性。
因此我们需要写IDL,然后使用Thrift编译器将IDL转换为对应的语言,我们开发者只需要实现具体的业务逻辑,无需关注底层逻辑!
在学习thrift之前,我们需要先简单的学习一下IDL的语法,很简单,和其他语言结构差不多。
类型 | 解释 |
---|---|
bool | 布尔值 |
byte | 8位有符号整数 |
i16 | 16位有符号整数 |
i32 | 32位有符号整数 |
i64 | 64位有符号整数 |
double | 64位浮点数 |
string | UTF-8编码的字符串 |
binary | 二进制串 |
先看例子:
struct Person { // 定义 Person 结构体
1: required string name; // 姓名,必选字段
2: required i32 age; // 年龄,必选字段
3: optional string sex; // 性别,可选字段
}
如上面所示,它类似C语言的结构体,对应java中的Bean,其中required
修饰的他的值初始化是必传项。
有三种可用的容器类型:
例如:
struct Test {
1: map<string, User> usermap,
2: set<i32> intset,
3: list<double> doublelist
}
服务的定义方法在语义上等同于面向对象语言中的接口。
service PersonService { // 定义 PersonService 服务接口
Person getByName(1: string name); // 根据姓名获取 Person 信息
bool save(1: Person person); // 保存 Person 信息
}
枚举的定义形式和Java的Enum定义差不多,例如:
enum Sex {
MALE,
FEMALE
}
thrift支持自定义exception,规则和struct一样,如下:
exception RequestException {
1: i32 code;
2: string reason;
}
thrift的命名空间相当于Java中的package的意思,主要目的是组织代码。thrift使用关键字namespace定义命名空间,例如:
namespace java com.aniu.service
namespace py example
namespace 后跟的是你要转化的语言以及生成的文件所在的包!
写完IDL文件后,我们需要将其转换成对应语言!因此,我们需要先安装Thrift编译器!这里博主用的Windows,macos和Linux自行下载!
网址:https://dlcdn.apache.org/thrift/0.19.0/thrift-0.19.0.exe
下载安装完成后,配置完环境变量,如下图,可查看版本!后续在其他语言例如java中引入maven包时,需要对应版本!
这里编写一个简单的入门案例,实现rpc远程过程调用!
定义thrift 文件 person.thrift
namespace java com.aniu.service
struct Person { // 定义 Person 结构体
1: required string name; // 姓名,必选字段
2: required i32 age; // 年龄,必选字段
3: optional string sex; // 性别,可选字段
}
service PersonService { // 定义 PersonService 服务接口
Person getByName(1: string name); // 根据姓名获取 Person 信息
bool save(1: Person person); // 保存 Person 信息
}
使用
thrift --gen java person.thrift
命令,即可在当前目录下生成gen-java目录,里面即是生成的java代码!
引入对应版本的Maven包
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.19.0</version>
</dependency>
目录结构如下:
如上图,Person和PersonService即为thrift编译器将IDL转换后生成的java代码。
PersonService里面有两个接口,我们需要实现接口,在实现类里面写业务逻辑。
package com.aniu.service.impl;
import com.aniu.service.Person;
import com.aniu.service.PersonService;
import org.apache.thrift.TException;
public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getByName(String name) throws TException {
return new Person(name,18);
}
@Override
public boolean save(Person person) throws TException {
return false;
}
}
package com.aniu.server;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import com.aniu.service.PersonService;
import com.aniu.service.impl.PersonServiceImpl;
public class Server {
public static void main(String[] args) {
try{
// 创建一个新的 Thrift 服务端套接字,监听在端口 9000 上
TServerSocket socket = new TServerSocket(9000);
// 创建一个 PersonService 的 Processor。Processor 是 Thrift 中用于处理请求的接口,它需要一个实现了 PersonService 接口的对象作为参数。
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
// 创建一个二进制协议工厂对象。Thrift 支持多种协议,如 TBinaryProtocol、TCompactProtocol、TJSONProtocol 等,这里选择的是二进制协议。
TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory();
// 创建一个 TSimpleServer 的参数对象 args1,并将之前创建的套接字、Processor 和协议工厂设置为其属性。
TServer.Args args1 = new TSimpleServer.Args(socket);
args1.processor(processor);
args1.protocolFactory(factory);
// 使用之前设置好的参数创建 TSimpleServer 对象
TSimpleServer tSimpleServer = new TSimpleServer(args1);
// 开始执行 TSimpleServer,开始监听并处理客户端的请求。
tSimpleServer.serve();
}catch (Exception e){
System.out.println(e);
}
}
}
import com.aniu.service.PersonService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
public class Client {
public static void main(String[] args) {
try{
// 创建一个 Thrift 的套接字对象,连接到在本地主机(localhost)的9000端口上运行的 Thrift 服务。
TSocket socket = new TSocket("localhost", 9000);
// 创建一个使用二进制协议的实例,该协议用于在客户端和服务器之间传输数据。
TBinaryProtocol protocol = new TBinaryProtocol(socket);
// 创建一个Thrift 客户端实例,它使用前面创建的二进制协议实例进行通信。
PersonService.Client client = new PersonService.Client(protocol);
// 打开与服务器端的连接,通过客户端对象(client)进行远程过程调用(RPC)或其他通信操作
socket.open();
// RPC 调用
Person person = client.getByName("aniu");
System.out.println(person);
}catch (Exception e){
System.out.println(e);
}
}
}
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.19.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>1.3.5</version>
<scope>compile</scope>
</dependency>
</dependencies>
先启动服务端,再启动客户端,即可实现RPC调用!
本节实现了一个简单的案例,但对于thrift的服务端如何创建的,客户端如何调用没有具体讲解,基本都在注释里,下一篇博文来讲解原理以及实现跨语言RPC调用!