0、前言
RPC框架
远程过程调用(Remote Procedure Call, RPC),是一种可以通过网络从远程计算机程序上请求服务的计算机通信协议。
RPC框架的开发步骤:
1、定义一个接口说明文件:描述对象(结构体)、对象成员、接口方法等信息;
2、通过RPC框架所提供的编译器,将接口说明文件编译成具体的语言文件;
3、在客户端和服务器端分别引入RPC编译器所生成的文件,即可像调用本地方法一样调用服务器端代码。
RPC框架工作流程:
1、服务消费者(client客户端)通过本地调用的方式调用服务
2、客户端存根(client stub)接收到调用请求后负责将方法、参数等信息序列化成能够进行网络传输的消息体
3、客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端
4、服务端存根(server stub)收到消息后进行解码(反序列化操作)
5、服务端存根(server stub)根据解码结果调用本地的服务进行相关处理
6、本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub)
7、服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方
8、客户端存根(client stub)接收到消息,并进行解码(反序列化)
9、服务消费方得到最终结果
RPC就是将中间过程进行封装,给用户更简单友好的API使用。
常见的RPC框架有:dubbo、thrift等
1、什么是Thrift ?
Thrift 是一个跨语言的远程服务调用框架,主要用于各个服务之间的跨语言远程通信。
Thrift通过接口定义语言(Interface Description Language,IDL)定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码,并由生成的代码负责RPC协议和传输层的实现,进而关联客户端和服务端。
1. 在xxx.thrift文件中根据Thrift语法编写接口和数据类型
2. 用Thrift编译器thrift_xx_xx.exe编译这个xxx.thrift文件生成指定语言的代码文件
3. 将生成的代码文件加入到工程中,调用接口。
2、Thrift架构:
- Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。
- 用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。
- 其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
由上到下:
- 服务层(Server Layer):整合上述组件,提供具体的网络线程/IO服务模型,形成最终的服务。
- 处理层(Processor Layer):处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的Handler进行处理。
- 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;比如说JSON、XML、二进制数据等。
- 传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。
3、Thrift协议
1、客户端传输层–TTransport:客户端传输层相关的类;
常用的传输层有:
- TSocket:使用阻塞式I/O进行传输,是最常见的模式
- TNonblockingTransport:使用非阻塞方式,用于构建异步客户端
- TFramedTransport:使用非阻塞方式,按块的大小进行传输,类似于Java中的NIO
2、服务端传输层–TServerTransport:服务端传输层相关的类;
3、协议层–TProtocol:序列化、反序列化相关的类;
Thrift—序列化机制
Thrift可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本(text)和二进制(binary)传输协议。为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目/产品中的实际需求。常用协议有以下几种:
- TBinaryProtocol:二进制编码格式进行数据传输
- TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输
- TJSONProtocol: 使用JSON文本的数据编码协议进行数据传输
- TSimpleJSONProtocol:只提供JSON只写的协议,适用于通过脚本语言解析
4、服务端–TServer:服务器的IO事件流模型相关的类;
Thrift—网络服务模型
- TSimpleServer:单线程服务器端,使用标准的阻塞式I/O
- TThreadPoolServer:多线程服务器端,使用标准的阻塞式I/O
- TNonblockingServer:单线程服务器端,使用非阻塞式I/O
- THsHaServer:半同步半异步服务器端,基于非阻塞式IO读写和多线程工作任务处理
- TThreadedSelectorServer:多线程选择器服务器端,对THsHaServer在异步IO模型上进行增强
5、 TProcessor:函数,接口调用相关的类;
4、Thrift的使用:
编写IDL文件注意的问题:
[1] 函数的参数要用数字依序标好,序号从1开始,形式为:“序号:参数名”;
[2] 每个函数的最后要加上“,”,最后一个函数不加;
[3] 在IDL中可以使用/……/添加注释
1、Thrift的数据类型
Thrift 脚本可定义的数据类型包括以下几种类型:
1、基本类型:
- bool: 布尔值—对应java中的boolean类型; 例如:bool aBoolVal
- byte: 8位有符号整数—对应java中的byte类型;例如:byte aByteVal
- i16: 16位有符号整数—例如:i16 aI16Val;
- i32: 32位有符号整数—对应java中的int类型;例如: I32 aIntVal
- i64: 64位有符号整数—对应java中的long类型;例如:I64 aLongVal
- double: 64位浮点数—java中的double类型;例如:double aDoubleVal
- string: UTF-8编码的字符串—注意是全部小写形式;例如:string aString
- binary: 二进制串
- void,空类型,对应java中的void类型;该类型主要用作函数的返回值,例如:void testVoid(),
2、结构体类型:
- struct: 定义的结构体对象–对应Java中的class
规范的struct定义中的每个域均会使用required或者optional关键字进行标识。如果required标识的域没有赋值,thrift将给予提示。如果optional标识的域没有赋值,该域将不会被序列化传输。如果某个optional标识域有缺省值而用户没有重新赋值,则该域的值一直为缺省值。
与service不同,结构体不支持继承,即一个结构体不能继承另一个结构体。
struct TestV1 {
1: i32 begin_in_both,
3: string old_string,
12: i32 end_in_both
}
- enum:枚举类型
enum Numberz
{
ONE = 1,
TWO,
THREE,
FIVE = 5,
SIX,
EIGHT = 8
}
3、容器类型:
- list: 有序元素列表 --例如,定义一个list对象:list aList;
- set: 无序无重复元素集合 --例如,定义set对象:set aSet;
- map: 有序的key/value集合 --例如,定义一个map对象:map<i32, i32> newmap;
4、异常类型:
- exception: 异常类型
5、服务类型:
- service: 具体对应服务的类
2、核心内部接口:
- Iface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的同步业务逻辑。
- AsyncIface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的异步业务逻辑。
- Client:客户端通过HelloWorldService.Client的实例对象,以同步的方式访问服务端提供的服务方法。
- AsyncClient:客户端通过HelloWorldService.AsyncClient的实例对象,以异步的方式访问服务端提供的服务方法。
5、安装
-
查看依赖环境
brew list 查看是否有boost、libevent、openssl。 若没有先安装依赖环境: brew install boost brew install openssl brew install libevent
-
使用brew install thrift 安装thrift — 这里是安装最新版本的thrift,安装特定版本另说。
安装后thrift –version验证安装是否成功。
6、简单使用
1、首先新建一个maven项目,然后在pom.xml中添加如下内容:
这里就是引入thrift和日志类库,这里我的thrift版本号是0.14.1,注意下。
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.14.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
</dependencies>
2、在这个文件夹下,新建一个thrift文件,并生成Java代码
新建hello.thrift文件,并写入:
namespace java com.amos.thrift.demo
service Hello{
string helloString(1:string para) ,
i32 helloInt(1:i32 para),
bool helloBoolean(1:bool para),
}
这个thrift文件定义了Hello服务的3个方法,分别对传入的string/i32/bool值进行处理。然后用Thrift编译器生成Hello.java文件,当前最新版本是0.12.0 (released on 2019-JAN-04)。命令是thrift-0.12.0 --gen java hello.thrift。
使用Thrift工具编译后生成的Hello.java文件中描述了Hello服务的接口定义(即Hello.Iface接口),以及服务调用的底层通信细节,包括客户端的调用逻辑Hello.Client类以及服务器端的调用逻辑Hello.Processor类,用来构建客户端和服务端的功能。
然后在命令行键入:
thrift -gen java hello.thrift
这样就会生成下图的gen-java文件夹:
3、新建所需代码文件
文件结构大致如下:
其中Hello.java文件直接从刚刚的文件夹中剪切过来。
1、创建一个文件HelloServiceImpl.java,实现Hello.java中的Iface接口。代码如下:
public class HelloServiceImpl implements Hello.Iface {
@Override
public boolean helloBoolean(boolean para) throws TException {
System.out.println("server helloBoolean " + para);
return !para;
}
@Override
public int helloInt(int para) throws TException {
System.out.println("server helloInt " + para);
return (para + 1);
}
@Override
public String helloString(String para) throws TException {
System.out.println("server helloString " + para);
return "server return : " + para;
}
2、然后创建Server实现HelloServiceServer.java,将HelloServiceImpl作为具体处理器传给Thrift服务器,代码如下:
public class HelloServiceServer {
private static final int SERVER_PORT = 7911;
public static void main(String[] args) {
try {
TServerSocket serverTransport = new TServerSocket(SERVER_PORT);
TThreadPoolServer.Args arg = new TThreadPoolServer.Args(serverTransport);
TProcessor processor = new Hello.Processor(new HelloServiceImpl());
TBinaryProtocol.Factory portFactory = new TBinaryProtocol.Factory(true, true);
arg.processor(processor);
arg.protocolFactory(portFactory);
TServer server = new TThreadPoolServer(arg);
System.out.println("Start server on port " + SERVER_PORT + "...");
server.serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
}
3、新建Client实现HelloServiceClient.java,代码如下:
注意:这里只在同一个Project下进行测试,因此我的server_address是127.0.0.1;
public class HelloServiceClient {
private static final String SERVER_ADDRESS = "127.0.0.1";
private static final int SERVER_PORT = 7911;
/**
* 调用Hello服务
* @param args
*/
public static void main(String[] args) {
try {
TTransport transport = new TSocket(SERVER_ADDRESS, SERVER_PORT);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
System.out.println(client.helloString("asd"));
System.out.println(client.helloInt(1234));
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
}
4、先后启动server和client