简介
Apache Thrift是Facebook开源的跨语言的RPC通讯框架,目前已经捐献给Apache基金会管理,因为其跨语言特性和出色的性能,在不少互联网公司获得应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增长诸如服务注册、服务发现等功能。java
RPC即Remote Procedure Call,翻译为远程过程调用。任何RPC协议的实现终极目标都是让使用者在调用远程方法的时候就像是调用本地方法同样简单,从而提升使用远程服务的效率。apache
现代互联网架构多数基于SOA思想而搭建,即面向服务化的架构。服务提供方称为Provider,服务的使用方称为Consumer,有时也把服务提供方称为Server端,使用方称为Client端,即典型的CS模型。这里的远程调用,主要指跨进程的调用,Provider和Consumer多是同一机器的不一样进程,也可能在不一样的机器,经过网络相互通讯,大部分状况下二者会部署在不一样的物理机器上,这种状况下因为网络通讯的开销就会对RPC框架的性能要求极高。编程
下面分别从服务端和客户端的视角来介绍Thrift在RPC中的应用。服务器
服务端(Server)
服务端须要发布一个服务给别人使用,首先要约定好服务的接口,包括如下几个部分:网络
- 服务的名称
- 服务使用时的参数
- 返回结果
Thrift本身规定了一套接口定义语言(IDL)来描述服务,用后缀为.thrift的文件来描述,好比咱们要提供一个打招呼的服务,传入姓名,而后返回一段友好的语句,Thrift文件HelloService.thrift的内容以下:数据结构
1
2
3
4
|
namespace java com.yuanwhy.service
service HelloService{
string sayHello(
1
:string name)
}
|
Thrift文件定义好以后,就约定好了接口的使用方式,可是仍然还不能使用,须要咱们用thrift命令来生成对应的编程语言的文件,好比用一下命令来生成HelloService.class的Java文件。架构
1
|
thrift -r --gen java HelloService.thrift
|
命令行参数 -r 表明递归生成里面引用的其余文件, --gen 后面跟生成的目标语言,最后跟上thrift文件。框架
生成的Java文件里会有一个接口HelloService.Iface,这就是一个普通的Java Interface,服务端要提供该接口的实现,实现以前须要引用libthrift的jar包,好比咱们这么实现:编程语言
1
2
3
4
5
6
7
8
9
10
|
package
com.yuanwhy.service;
import
org.apache.thrift.TException;
public
class
HelloServiceImpl
implements
HelloService.Iface {
@Override
public
String sayHello(String name)
throws
TException {
return
"Hello, "
+ name +
"!"
;
}
}
|
这样,服务端就实现了服务的逻辑部分,可是要让别人在网络上真正可用,咱们还得把这个服务发布出去,发布的方式就是借助Socket编程,监听一个对外服务的端口,这也是网络通信的基本套路。利用Thrift提供的API,在HelloServiceProvider类中启动Thrift服务:分布式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package
com.yuanwhy.demo;
import
com.yuanwhy.service.HelloService;
import
com.yuanwhy.service.HelloServiceImpl;
import
org.apache.thrift.TProcessor;
import
org.apache.thrift.server.TServer;
import
org.apache.thrift.server.TSimpleServer;
import
org.apache.thrift.transport.TServerSocket;
import
org.apache.thrift.transport.TTransportException;
public
class
HelloServiceProvider {
/**
* 启动 Thrift 服务器
*
* @param args
*/
public
static
void
main(String[] args) {
try
{
// 设置服务端口为 7911
TServerSocket serverTransport =
new
TServerSocket(
7911
);
TProcessor processor =
new
HelloService.Processor(
new
HelloServiceImpl());
TServer server =
new
TSimpleServer(
new
TServer.Args(serverTransport).processor(processor));
System.out.println(
"Start server on port 7911..."
);
server.serve();
}
catch
(TTransportException e) {
e.printStackTrace();
}
}
}
|
运行main函数以后,服务端就会监听7911端口,开始对外提供sayHello的服务了。
客户端(Client)
客户端想要使用服务端经过thrift发布的服务,只须要遵循面向接口编程的基本思想,引用Java接口,用thrift的API链接服务端便可,好比HelloServiceConsumer类中这么使用sayHello服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package
com.yuanwhy.demo;
import
com.yuanwhy.service.HelloService;
import
org.apache.thrift.TApplicationException;
import
org.apache.thrift.TEnum;
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;
/**
* Created by zeze on 2018/03/23.
*/
public
class
HelloServiceConsumer {
public
static
void
main(String[] args) {
TTransport transport =
new
TSocket(
"0.0.0.0"
,
7911
);
try
{
transport.open();
TProtocol protocol =
new
TBinaryProtocol(transport);
HelloService.Client client =
new
HelloService.Client(protocol);
System.out.println(client.sayHello(
"yuanwhy"
));
transport.close();
}
catch
(TApplicationException e) {
if
(e.getType() == TApplicationException.MISSING_RESULT) {
System.out.println(
"null"
);
}
}
catch
(TException e){
}
}
}
|
客户端运行结果会打印Hello, yuanwhy!
,代表服务调用成功。仔细观察一下客户端的代码会发现,基本和普通的Socket编程没有太大的区别,只是又被thrift作了一层的封装,让咱们能够按照约定的接口直接像client.sayHello("yuanwhy")
这样调用远程服务。
注意:客户端若是是在不一样的Java项目中调用服务,只须要服务端把thrift文件或者生成的Java接口文件以API的方式提供出来便可,客户端绝对不须要引用HelloServiceImpl实现类,由于目的就是让逻辑在服务端实现,对客户端透明。
另外,当服务端返回null时,客户端会抛一个Type为TApplicationException.MISSING_RESULT
的异常出来,若是不处理就会影响客户端正常的流程。这一点能够在Thrift的生成代码中看出来:
1
2
3
4
5
6
7
8
9
|
public
String recv_sayHello()
throws
org.apache.thrift.TException
{
sayHello_result result =
new
sayHello_result();
receiveBase(result,
"sayHello"
);
if
(result.isSetSuccess()) {
return
result.success;
}
throw
new
org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT,
"sayHello failed: unknown result"
);
}
|
总结
以上对Thrift的使用只作了个简单的介绍,真正在项目中使用Thrift还会涉及不少,好比各类Thrift数据结构的使用,在对Thrift接口进行升级过程当中struct的字段最好保留原有字段顺序以达到兼容目的,还好比客户端应该创建链接池机制,而不是每次调用服务时都去新建一次TCP链接等等。