Thrift框架详解(一)

概诉与入门

Posted by Jason Lee on 2020-02-29

Thrift 简介

Thrift是一个轻量级、跨语言的远程服务调用框架,最初由Facebook开发,后面进入Apache开源项目。它通过自身的IDL中间语言, 并借助代码生成引擎生成各种主流语言的RPC服务端/客户端模板代码。
Thrift支持多种不同的编程语言,包括C++、Java、Python、PHP、Ruby等,本系列主要讲述基于Java语言的Thrift的配置方式和具体使用。

Thrift的主要特点有:

  1. 基于二进制的高性能的编解码框架
  2. 基于NIO的底层通信
  3. 相对简单的服务调用模型
  4. 使用IDL支持跨平台调用

Thrift 安装

具体的安装不做介绍,可以去官网查询,本系列主要使用的是0.8.0, 截止到目前,最新版本是0.13.0, 由于目前就职的公司普遍使用的是0.8.0,则就以这个为准,后边会单独开一片0.8.0和0.13.0的区别。

Thrift 设计

设计架构

Thrift软件栈分层从下向上分别为:

  • 传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。

  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;比如说JSON、XML、二进制数据等。

  • 处理层(Processor Layer):处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的Handler进行处理。

  • 服务层(Server Layer):整合上述组件,提供具体的网络线程/IO服务模型,形成最终的服务。

特性

(一) 开发速度快

通过编写RPC接口Thrift IDL文件,利用编译生成器自动生成服务端骨架(Skeletons)和客户端桩(Stubs)。从而省去开发者自定义和维护接口编解码、消息传输、服务器多线程模型等基础工作。

服务端:只需要按照服务骨架即接口,编写好具体的业务处理程序(Handler)即实现类即可。
客户端:只需要拷贝IDL定义好的客户端桩和服务对象,然后就像调用本地对象的方法一样调用远端服务。

(二) 接口维护简单

通过维护Thrift格式的IDL(接口描述语言)文件(注意写好注释),即可作为给Client使用的接口文档使用,也自动生成接口代码,始终保持代码和文档的一致性。且Thrift协议可灵活支持接口的可扩展性。

(三) 学习成本低

因为其来自Google Protobuf开发团队,所以其IDL文件风格类似Google Protobuf,且更加易读易懂;特别是RPC服务接口的风格就像写一个面向对象的Class一样简单。
初学者只需参照:,一个多小时就可以理解Thrift IDL文件的语法使用。

(四) 多语言/跨语言支持

Thrift支持C++、 Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk等多种语言,即可生成上述语言的服务器端和客户端程序。
对于我们经常使用的Java、PHP、Python、C++支持良好。

(五) 稳定/广泛使用

Thrift在很多开源项目中已经被验证是稳定和高效的,例如Cassandra、Hadoop、HBase等;国外在Facebook中有广泛使用,国内包括百度、美团小米、和饿了么等公司。

Thrift 快速入门

项目包路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
com.thrift
|- client
|- client.java //主要存放 client的代码
|- constant
| - ServerConfig.java // 主要存放服务器配置等关键信息
|- idl
|- hello.thrift // IDL描述文件
|- idlcode
| - HelloWorldService.java // 主要存放根据IDL自动生成的文件
| -server
|- handler // rpc 接口的具体实现
|- HelloWorldServiceImpl.java
server.java // 服务器测试代码
gen_code.sh // IDL生成命令

具体实现

  • (1) 编写IDL 文件,命名为 hello.thrift放到com.thrift.idl 这个包名下。如果需要提示,在Idea上安装 Thrift 编辑插件,具体可以搜索 ThirftSupport 这个插件。
1
2
3
service HelloWorldService {
string say(1: string username)
}
  • (2) 运行gen_code.sh 用于生成 thrift 的各种代码块。
    我们来看一下 gen_code.sh 的代码
1
2
3
4
5
6
7
8
9
#!/bin/zsh
path_root=`pwd`
idlDir=$path_root/idl
idlCode=$path_root/idlCode

for f in `ls $idlDir/*.thrift`
do
thrift --gen java -out $idlCode $f
done

运行完成或,我们会在 idlcode 生成一个HelloWorldService.java 文件,这个就是上文中服务器和客户端交互的核心代码。

我们来简单看一下这个java文件的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HelloWorldService {
// 同步接口定义
public interface Iface {
public String say(String username) throws org.apache.thrift.TException;
}

// 异步接口定义
public interface AsyncIface {
public void say(String username, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.say_call> resultHandler) throws org.apache.thrift.TException;
}
// 客户端
public static class Client extends org.apache.thrift.TServiceClient implements Iface {/** ...*/}
// 异步客户端
public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {/** ...*/}
// Processor 组件
public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {/** ...*/}

public static class say_args implements org.apache.thrift.TBase<say_args, say_args._Fields>, java.io.Serializable, Cloneable {/** ...*/}

public static class say_result implements org.apache.thrift.TBase<say_result, say_result._Fields>, java.io.Serializable, Cloneable {/** ...*/}
}

对于开发人员而言,使用原生的Thrift框架,仅需要关注以下四个核心内部接口/类:Iface, AsyncIface, Client和AsyncClient

  • Iface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的同步业务逻辑。
  • AsyncIface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的异步业务逻辑。
  • Client:客户端通过HelloWorldService.Client的实例对象,以同步的方式访问服务端提供的服务方法。
  • AsyncClient:客户端通过HelloWorldService.AsyncClient的实例对象,以异步的方式访问服务端提供的服务方法。

这里的结构不做详细分析,等到下一章节在做分析。

  • (3) 安装thrift插件,我这里用的是0.8.0版本,所以在pom.xml 文件添加如下
1
2
3
4
5
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.8.0</version>
</dependency>
  • (4) 编写RPC的实现类
    首先 我们要对 HelloWorldService 服务做实现,也就是我们要提供更多具体的RPC 方法
    server.handler 包下创建 HelloWorldServiceImpl.java
1
2
3
4
5
6
public class HelloWorldServiceImpl implements HelloWorldService.Iface {
@Override
public String say(String username) throws TException {
return "hello ==> " + username;
}
}
  • (5) 编写服务器代码
  • Thrift的服务端类型
    1. TSimpleServer :单线程服务器端,使用标准的阻塞式I/O
    2. TThreadPoolServer :多线程服务器端,使用标准的阻塞式I/O
    3. TNonblockingServer :单线程服务器端,使用非阻塞式I/O
    4. THsHaServer :半同步半异步服务器端,基于非阻塞式IO读写和多线程工作任务处理
    5. TThreadedSelectorServer :多线程选择器服务器端,对THsHaServer在异步IO模型上进行增强

这里我们使用TSimpleServer,在 server包下新建 server.java以及定义 ServerConfig 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Server {
public static void main(String[] args) throws IOException {
SimpleServer();
}

public static void SimpleServer () throws IOException {
ServerSocket serverSocket = new ServerSocket(ServerConfig.SERVER_PORT);
// 转换层
TServerSocket serverTransport = new TServerSocket(serverSocket);
// processor 层
HelloWorldService.Processor processor =
new HelloWorldService.Processor<HelloWorldService.Iface>(new HelloWorldServiceImpl());
// 协议工厂
TBinaryProtocol.Factory protocolFactory = new TBinaryProtocol.Factory();
TSimpleServer.Args tArgs = new TSimpleServer.Args(serverTransport);
tArgs.processor(processor);
tArgs.protocolFactory(protocolFactory);

// 简单的单线程服务模型 一般用于测试
TServer tServer = new TSimpleServer(tArgs);
System.out.println("Running Simple Server");
tServer.serve();
}
}

ServerConfig.java 代码

1
2
3
4
5
public class ServerConfig {
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 9988;
public static final int TIMEOUT = 30;
}
  • (6) client 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Client {
public static void main(String[] args) {
SimpleClient();
}

private static void SimpleClient() {
TTransport transport = null;
try {
transport = new TSocket(ServerConfig.SERVER_IP, ServerConfig.SERVER_PORT, ServerConfig.TIMEOUT);
TProtocol protocol = new TBinaryProtocol(transport);
HelloWorldService.Client client = new HelloWorldService.Client(protocol);
transport.open();
String result = client.say("Leo");
System.out.println("Result =: " + result);
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
}
  • (7) 运行server
    server控制台输出
1
Running Simple Server

client 输出

1
Result =: hello ==> Leo

总结

Thrift是一个RPC框架,内部实现的机制和概念还有很多,更重要的留下几个问题。、
1、Thrift如何做服务发现? 这个留着下次再分析。
2、生成的java文件起着什么样的作用。
3、Rpc的流程是什么。

参考



支付宝打赏 微信打赏

赞赏一下