Thrift学习笔记。
1 Thrift基本用法
1 | ## 1,搭建thrift编译环境 |
1.1 搭建thrift编译环境
1.1.1 tar包下载
http://www.apache.org/dyn/closer.cgi?path=/thrift/0.13.0/thrift-0.13.0.tar.gz
1.1.2 Manven依赖
1 | <dependency> |
1.1.3 构建并安装thrift编译器
官网参考:https://thrift.apache.org/docs/install
1.2 定义thrift接口文件
thrift接口定义文件IDL通常是以.thrift后缀结尾。
命名空间:相当于Java中的包。格式:namespace 语言 命名空间名称 例:
namespace java org.apache.hive.service.cli.thrift
接口:生成的接口文件名。格式:service ServerName { … }
IDL中支持8种基本类型:string,i16,i32,i64,byte,bool,double,void;三种复杂类型map,set,list;还支持自定义数据类型:enum(枚举),const(常量)和struct(结构体)。
另外还支持对类型重定义(相当于定义变量):typedef i32 Myi32
接口定义示例
SayHello.thrift
1 | // 命名空间 |
1.3 编译接口文件生成源代码
编译语法:
thrift –gen
编译后的源码中主要有如下几个重要的类:(其中Iface,Client,Processor是核心类)
Iface:同步接口类。真实的业务逻辑类实现了该接口。
Client:同步客户端类,实现了Iface接口。
Processor:请求处理代理类。内部map中保存了所有的接口函数名和函数对象的映射。这里处理请求,通过代理调用真实的业务逻辑方法,并返回结果。包括请求的反序列化和返回值的序列化。
AsyncIface:异步接口类(不常用)。
AsyncClient:异步客户端类(不常用)。
函数类:在Processor中所有的接口函数都定义成了类。例:
public static class SayHello extends org.apache.thrift.ProcessFunction<I, SayHello_args>
参数类:命名规则: 函数名 + “_args”, 例:OpenSession_args
返回值类:命名规则:函数名 + “_result”, 例: OpenSession_result
thrift编译生成的源代码中函数名都是硬编码,并不是通过反射的方式调用的,很小的接口文件(50行)编译后的源代码都会很大(1700+ 行),相对而言比较重。
thrift编译生成源代码示例
thrift –gen java:beans SayHello.thrift
源代码见附件。
1.4 编写接口实现类
接口实现类需要实现Iface接口。
1 | ## 1,实现Iface接口 |
接口实现类示例
1 | package org.jeff.demo.thrift; |
1.5 编写服务端并启用服务
1 | ## 5,编写服务端并启动服务 |
服务端实现示例
1 | package org.jeff.demo.thrift; |
1.6 编写客户端并远程调用
1 | ## 6,编写客户端并远程调用(客户端传输层和协议层需和服务端保持一致) |
客户端实现示例
1 | package org.jeff.demo.thrift; |
2 Thrift原理及源码解读
2.1 架构原理
RPC,远程过程调用,即实现远端函数之间的调用,区别于本地函数调用。
Thrift是Facebook开源的一款跨语言服务部署框架,通过TDL语言定义RPC接口,并由thrift编译生成不同语言的源代码,实现RPC底层通信需要的传输层,协议层等。
架构原理图
Thrift框架提供了多种server服务模式,多种transport传输方式,多种protocal协议。
2.2 server服务模式
所有的服务模式都是TServer抽象类的子类,继承关系如下:
TServer定义了server抽象方法,需要子类实现:
1 | /** |
所有的入口都是server方法。
2.2.1 TSimpleServer
1)简介:
1 | /** |
单线程的用于测试的服务模式,不适用于生产。
2)源码解读:
执行流程:
1 | // 1, 启动一个监听器 |
附源码实现:
1 | public void serve() { |
2.2.2 TThreadPoolServer
1)简介:
1 | /** |
采用java线程池管理的多线程服务,多用于生产环境。Hive源码中的Thrift采用的便是这种模式。
适用场景:可以有效预估并发量的情况下,可以合理设置线程池大小,提高处理并发请求效率。
2)源码解读:
采用主线程只负责接收请求,线程池负责处理请求;优点是可以及时响应并发用户请求;在线程池大小内提高效率,在并发请求超过线程池大小时,会阻塞。
执行流程:
1 | // 1, 启动监听器 |
server入口,比较简单,主要调用了三个方法:preServer,execute,waitForShutDown
1 | public void serve() { |
preServer():在这里主要负责启动了一个监听器。
1 | protected boolean preServe() { |
execute():这里每接收到一个请求,创建一个独立的线程,交给线程池执行。
1 | protected void execute() { |
waitForShutDown:这里比较简单,就是调用了线程池的shutDown方法。
1 | protected void waitForShutdown() { |
2.2.3 AbstractNonblockingServer
1)简介
1 | /** |
非阻塞服务的抽象类,有三个子类实现:
service逻辑:子类主要重写了startThreads方法。
1 | /** |
2.2.4 TThreadedSelectorServer
1)简介
1 | /** |
半同步半异步服务器,用一个独立的线程池来处理非阻塞I/O。接收请求用单独的一个线程来处理,并且通过可配置数量的非阻塞选择器线程管理客户端连接的读写。一个同步的工作线程池处理处理中的请求。
当单个选择器线程上的瓶颈是CPU处理I/O时,在多核环境中,性能要优于TNonblockingServer/THsHaServer。另外,因为接受处理请求和读写以及调用时隔离的,服务器便有更好的能力来处理来自新连接的压力(比如当繁忙的时候停止接受请求)。
和TNonblockingServer一样,它依赖于TFramedTransport的使用。
优点:性能最好的服务器模式。
2)源码解读
1 | // 1,初始化一组选择线程用于处理业务(数量可配置)。我们称作工作线程 |
主线程:
1 |
|
acceptThread:这里调用了select()方法。
1 | /** |
select方法:在select方法里监听请求,并且通过 handleAccept 方法处理请求。
1 | /** |
handleAccept方法:
1 | // 1,负载均衡器选择一个选择线程(这里的负载均衡器策略是轮训策略) |
1 | /** |
2.2.5 TNonblockingServer
1)简介
1 | /** |
非阻塞TServer的接口。允许所有已连接的客户端调用都是公平的。
这个服务本质上是单线程的。如果你想在有限的线程池中实现公平性,可以参考THsHaServer。
使用这个服务,你必须在最外层使用TFramedTransport传输协议,否则服务将无法确定何时整个方法调用已经断开。Client端必须使用TFramedTransport传输协议。
优点:采用选择器实现多个连接的公平调用
缺点:单线程处理,依然存在瓶颈
2)源码解读
1 | /** |
1 | /** |
2.2.6 THsHaServer
1)简介
1 | /** |
继承于TNonblockingServer的半同步半异步的服务器。
和TNonblockingServer一扬,依赖于使用TFramedTransport传输协议。
优点:THsHaServer的产生是为了解决TNonblockingServer单线程的瓶颈,在THsHaServer中采用线程池来处理业务请求。
缺点:主线程依然还是采用单线程处理请求,在高并发的场景依然存在瓶颈。
2)源码解读
重新了requestInvoke方式,使用线程池处理请求。
1 | /** |
2.3 传输层
TTransport是所有传输层的基类。定义了传输过程中信息如何读如何写等。
抽象方法主要有:close,isOpen,open,read,write
基于不同的传输方式,有多种实现。继承关系如下:
未完待续~~~
附:thrift编译生成的源代码 HelloThriftServer.java
1 | /** |
本文链接: https://stefanxiepj.github.io/archives/6cd1c454.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
![知识共享许可协议](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)