现如今对于任何一个大型的服务,都不太可能是一个单体的服务。而是由诸多的子服务构成,具体的业务逻辑通过子服务之间的相互调用来完成。这种相互的调用称之为远程调用,也就是通常所说的 RPC。
什么是 RPC
RPC(Remote Procedure Call)是指远程调用,这个和本地调用有很大的区别。下面在 main
方法中调用 helloWorld
就是本地调用。
public class Main {
public static void final main(String[] args) {
helloWorld(); // 本地调用
}
public static void helloWorld() {
System.out.println("Hello World");
}
}
现在假设 helloWorld
这个方法不在本地,没法直接调用,那么就需要通过远程调用来访问这个方法,在进行远程调用的过程中,就产生了客户端-服务端(Server-Client)模式,服务的提供方就是服务端,服务的调用方就是客户端,客户端和服务端并不是绝对不变的,要看具体的服务的调用流程来认定服务端和客户端。
远程调用的方式有很多,比如 Java 中的 RMI(Remote Method Invocation) 就是典型的 RPC, 还有 Http 也可以认为是一种 RPC。
Thrift 是 FaceBook 实现的一种支持多语言的 RPC 框架,对于主流的编程语言 Java
,C++
, Python
等都有很好的支持。 Thrift 会通过自身的 IDL(Interface Description Language)
语言来对接口进行定义,然后 Thrift 通过定义好的 IDL 文件生成相应的接口脚手架,然后只需要分别实现接口脚手架的 Server 端和 Client 端。并将 Server 端进行部署,Client 就可以访问 Server端的服务。
Thrift 特性简介
下图是 Thrift 官方给出的架构图。Thrift 支持的语言多达 28 种,对于各类操作系统也都提供了支持,Thrift 将自身划为4层(不要和网络的四层协议混淆),而且对于各个层已经做好干净的分层和实现。对于不同的业务场景就可以选择不同的协议进行组合。
Thrift 整体上可以分成以下四层:
- 传输层提供了面向网络 IO 的抽象,在传输层可以使用多种传输协议,TCP、Http 提供了支持
- 协议层中定义了内存中数据到传输格式的映射机制
- 处理层封装了从输入流读取数据和从输出流写数据的能力
- 服务器层将上述的组件合并到一起并按照下列的流程工作:
- 创建一个 Transport 对象
- 在 Transport 基础上创建输入\输出 Protocol 对象
- 在输入\输出 Protocol 基础上创建 Processor 对象
- 监听到来的连接并移交给 Processor 处理
Thrift 的性能
Thrift 的性能在现有的 RPC 框架中属于前列,这里有详细的 Benchmark,如果特别关注性能,那么 Thrift 是一个很好的选择。
Thrift 的网络模型
而且 Thrift 提供了多种网络模型,支持阻塞服务模型和非阻塞服务模型,所有的网络处理模型都继承自 TServer。
阻塞服务模型:
- TSimpleServer
- 最简单的阻塞 IO 模型
- 一次只能接收和处理一个请求
- 实际开发基本不会用到
- TThreadPoolServer
- 采用阻塞 Socket 的方式工作
- 主线程负责监听是否有新的 Socket 到达
- 具体的业务处理交给线程池来处理
非阻塞服务模型:
- TNonblockingServer
- 单线程模式,但是引入了 NIO 的机制,通过 Channel/Selector 机制来处理事件
- THsHaServer
- THsHaServer 继承自 TNonblockingServer
- 引入了线程池提高了任务的并发处理能力
- TThreadedSelectorServer
- 这个模型是对 THsHaServer 模型的一种补充
Thrift的序列化机制
数据在网络的传输过程中,序列化和反序列化是一个很重要的过程,对于不同的系统,对序列化有着不同的要求,Thrift 提供了多种序列化的方式来满足不同的要求。传输协议总体上可以分成文本和二进制的两种协议。
Thrift 有如下的传输协议:
- TBinaryProtocol:二进制编码格式进行数据传输
- TCompactProtocol:高效率、密集的二进制编码格式进行数据传输
- TJSONProtocol:使用 JSON 文本的数据编码协议进行数据传输
- TSimpleJSONProtocol:只提供 JSON 只写的协议,适用于通过脚本语言进行解析
Thrift demo 搭建
Thrift 的安装在官方文档已经介绍的很清楚了。
Thrift 使用 IDL 来定义(Client-Server)之间的接口。假如需要定义一个 hello 的接口,那么就可以定义一个 hello.thrift
文件,文件的内容如下:
service HelloWorldService {
string hello(1: string name)
}
上面定义了一个名为 HelloWorldService
的服务,在这个服务器中有一个叫 hello
的接口,这个接口接受一个 string
类型的参数。
service
和 string
都是 IDL 的关键字,IDL 中的关键字包括:
- 基本数据类型
- 容器类型
- 结构体
- 枚举
这些都是 IDL 的基本数据结构,也还有命名空间、异常、常量等关键字。完整的 IDL文档看这里
上面的 IDL 代码定义好了一个 Thrift 接口的规则,Thrift 就可以根据这个规则生成相应的接口文件。通过这些接口文件,就可以方便的实现相应的业务逻辑,相当于 Thrift 通过 IDL 文件搭出了服务脚手架。
在生成的脚手架文件中,有两个 interface 非常重要:
- Iface: 这个 interface 由服务器来实现,向客户端提供服务的逻辑在这个接口内实现
- Client: 这个 interface 由客户端来实现,访问服务端的逻辑在这个接口内实现
AsyncIface
和 AsyncClient
是这两个 interface 的异步版本。
下面使用 SimpleServer
来实现这个服务:
Iface 实现:
public class HelloWorldServiceImpl implements HelloWorldService.Iface{
@Override
public String say(String username) throws TException {
return "Hello " + username;
}
}
/**
*创建一个服务器,向外暴露服务
*/
public class HelloWorldServer {
public static void main(String[] args) throws TTransportException {
TServerSocket serverSocket = new TServerSocket(9090);
HelloWorldService.Processor processor = new HelloWorldService.Processor(new HelloWorldServiceImpl());
TSimpleServer.Args tArgs = new TSimpleServer.Args(serverSocket);
tArgs.processor(processor);
TServer server = new TSimpleServer(tArgs);
server.serve();
}
}
Client实现
/**
* 客户端,连接服务器,并且发送消息
*/
public class HelloWorldClient {
public static void main(String[] args) throws TTransportException, TException {
TTransport transport = new TSocket("127.0.0.1", 9090, 3000);
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
try {
transport.open();
String result = client.say("Ray");
System.out.println(result);
} finally {
if (null != transport) {
transport.close();
}
}
}
}
一个简单的 Thrift 的服务也就实现了。
(完)
关注微信公众号,聊点其他的