一、RPC 简介
- RPC(Remote Procedure Call,即:远程过程调用):调用远程计算机上的服务
- RPC 需要解决如下几个问题:
- 解决通讯问题:建立服务器与客户端的 TCP 或 HTTP 连接,所有服务器与客户端的数据交互都是在这个连接里进行
TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,在数据传输方面,越底层越快
即 TCP 一般比 HTTP 快,比如:早期 Web Service 基于 HTTP 协议的 RPC 具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC - 解决服务发现问题:A 服务如何告诉 B 服务它提供了什么样的能力,一般都会有注册中心等做服务发现,目前流行的都是使用 zookeeper 做注册中心进行服务发现
- 解决传输问题:服务调用需要将底层数据通过 TCP 或 HTTP 进行传输,则如何高效的序列化以及反序列化就是需要解决的问题
市面上有很多优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能
- 解决通讯问题:建立服务器与客户端的 TCP 或 HTTP 连接,所有服务器与客户端的数据交互都是在这个连接里进行
二、Thrift 架构
如图:
-
黄色部分是用户实现的业务逻辑
-
褐色部分是根据 Thrift 定义的服务接口描述文件生成的客户端和服务器端代码框架
Processor 类主要是开发 Thrift 服务器程序时使用,该类内部定义了一个map,保存了所有函数名到函数对象的映射,一旦Thrift接到一个函数调用请求,就从该map中根据函数名字找到该函数的函数对象,然后执行
-
红色部分是根据 Thrift 文件生成代码实现数据的读写操作
-
TProtocol 传输协议:用来描述 what is transmitted
Thrift 可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本和二进制传输协议
- 为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议
例如:实现了TProtocol接口的TBinaryProtocol类,对于readDouble()函数就是按照二进制的方式读取出一个Double类型的数据。同理,写入时也会按照对应的协议进行写入操作
目前Thrift支持的协议有这些种:
- TBinaryProtocol:二进制编码格式进行数据传输
- TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输
- TJSONProtocol:使用 JSON 的数据编码协议进行数据传输
- TSimpleJSONProtocol:只提供 JSON 只写的协议,适用于通过脚本语言解析
-
TTransport 传输层:用来描述how to transmitted
传输层实际上可以理解为对 I/O 层操作的一个封装,更直观的理解为它封装了一个socket,不同的实现类有不同的封装方式,常见的一般为阻塞式,同步非阻塞式和异步非阻塞,目前支持的有如下几种:
- TFramedTransport:使用非阻塞方式,按块的大小进行传输
- TSocket:使用阻塞式 IO 进行传输
- TNonblockingTransport:使用非阻塞方式,用于构建异步客户端
七层网络模型:
从时序图知:
- server 端本身有使用线程池,这样是为了能同时提供多个连接对外服务
- Thrift 的 server 端还提供了异步非阻塞 I/O 的server场景,不需要占用链接,等响应之后,找到之前的链接在反馈给客户端,这样一来能大大的提高并发
三、Thrift 时序图
1、server 端时序图
2、client 端时序图
四、Thrift 优势
1、序列化后的数据大小
2、序列化的耗时及 CPU 占用
五、Thrift 数据类型
Thrift 脚本可定义的数据类型包括以下几种类型:
- 基本类型:
bool
:布尔值,true 或 false,对应 Java 的 booleanbyte
:8 位有符号整数,对应 Java 的 bytei16
:16 位有符号整数,对应 Java 的 shorti32
:32 位有符号整数,对应 Java 的 inti64
:64 位有符号整数,对应 Java 的 longdouble
:64 位浮点数,对应 Java 的 doublestring
:未知编码文本或二进制字符串,对应 Java 的 String
- 结构体类型
struct
:定义公共的对象,类似于 C 语言中的结构体定义,在 Java 中是一个 JavaBean - 容器类型:
list
:对应 Java 的 ArrayListset
:对应 Java 的 HashSetmap
:对应 Java 的 HashMap
- 异常类型
exception
:对应 Java 的 Exception - 服务类型
service
:对应服务的类
六、案例
1、同步案例
Hello.thrift
namespace java service.demo
service Hello{
string helloString(1:string para)
i32 helloInt(1:i32 para)
bool helloBoolean(1:bool para)
void helloVoid()
string helloNull()
}
创建 HelloServiceImpl.java
文件并实现 Hello.java 文件中的 Hello.Iface 接口:
package service.demo;
import org.apache.thrift.TException;
public class HelloServiceImpl implements Hello.Iface {
@Override
public boolean helloBoolean(boolean para) throws TException {
return para;
}
@Override
public int helloInt(int para) throws TException {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return para;
}
@Override
public String helloNull() throws TException {
return null;
}
@Override
public String helloString(String para) throws TException {
return para;
}
@Override
public void helloVoid() throws TException {
System.out.println("Hello World");
}
}
创建服务器端实现代码,将 HelloServiceImpl 作为具体的处理器传递给 Thrift 服务器:
package service.server;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
import service.demo.HelloServiceImpl;
public class HelloServiceServer {
//启动 Thrift 服务器
public static void main(String[] args) {
try {
// 设置服务端口为 7911
TServerSocket serverTransport = new TServerSocket(7911);
// 设置协议工厂为 TBinaryProtocol.Factory
Factory proFactory = new TBinaryProtocol.Factory();
// 关联处理器与 Hello 服务的实现
TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TThreadPoolServer(processor, serverTransport,
proFactory);
System.out.println("Start server on port 7911...");
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
创建客户端实现代码,调用 Hello.client 访问服务端的逻辑实现:
package service.client;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
public class HelloServiceClient {
//调用 Hello 服务
public static void main(String[] args) {
try {
// 设置调用的服务地址为本地,端口为 7911
TTransport transport = new TSocket("localhost", 7911);
transport.open();
// 设置传输协议为 TBinaryProtocol
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
// 调用服务的 helloVoid 方法
client.helloVoid();
transport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}
2、异步案例
Thrift 提供非阻塞的调用方式,可构建异步客户端:Thrift 提供了新的类 TAsyncClientManager 用于管理客户端的请求,在一个线程上追踪请求和响应,同时通过接口 AsyncClient 传递标准的参数和 callback 对象,服务调用完成后,callback 提供了处理调用结果和异常的方法
CallBack 的实现:MethodCallback.java
package service.callback;
import org.apache.thrift.async.AsyncMethodCallback;
public class MethodCallback implements AsyncMethodCallback {
Object response = null;
public Object getResult() {
// 返回结果值
return this.response;
}
// 处理服务返回的结果值
@Override
public void onComplete(Object response) {
this.response = response;
}
// 处理调用服务过程中出现的异常
@Override
public void onError(Throwable throwable) {
}
}
创建非阻塞服务器端实现代码,将 HelloServiceImpl 作为具体的处理器传递给异步 Thrift 服务器:
package service.server;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TTransportException;
import service.demo.Hello;
import service.demo.HelloServiceImpl;
public class HelloServiceAsyncServer {
//启动 Thrift 异步服务器
public static void main(String[] args) {
TNonblockingServerTransport serverTransport;
try {
serverTransport = new TNonblockingServerSocket(10005);
Hello.Processor processor = new Hello.Processor(new HelloServiceImpl());
TServer server = new TNonblockingServer(processor, serverTransport);
System.out.println("Start server on port 10005 ...");
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
创建异步客户端实现代码,调用 Hello.AsyncClient 访问服务端的逻辑实现,将 MethodCallback 对象作为参数传入调用方法中:
package service.client;
import java.io.IOException;
import org.apache.thrift.async.AsyncMethodCallback;
import org.apache.thrift.async.TAsyncClientManager;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.TNonblockingSocket;
import org.apache.thrift.transport.TNonblockingTransport;
import service.callback.MethodCallback;
import service.demo.Hello;
public class HelloServiceAsyncClient {
//调用 Hello 服务
public static void main(String[] args) throws Exception {
try {
TAsyncClientManager clientManager = new TAsyncClientManager();
TNonblockingTransport transport = new TNonblockingSocket("localhost", 10005);
TProtocolFactory protocol = new TBinaryProtocol.Factory();
Hello.AsyncClient asyncClient = new Hello.AsyncClient(protocol, clientManager, transport);
System.out.println("Client calls .....");
MethodCallback callBack = new MethodCallback();
asyncClient.helloString("Hello World", callBack);
Object res = callBack.getResult();
//等待服务调用后的返回结果
while (res == null) {
res = callBack.getResult(); //异步客户端实现同步效果代码段
}
System.out.println(((Hello.AsyncClient.helloString_call) res).getResult());
} catch (IOException e) {
e.printStackTrace();
}
}
}