转载:由浅入深了解Thrift(一)——Thrift介绍与用法
转载:Thrift源码分析(八)--总结加一个完整的可运行的Thrift例子
转载:架构设计:系统间通信(11)——RPC实例Apache Thrift 上篇
转载:Thrift框架调研
一、Thrift
Apache Thrift 最初是 Facebook 实现的一种支持多种编程语言、高效的远程服务器调用框架,它于 2008 年进入 Apache 开源项目。Apache Thrift 采用接口描述语言(IDL)定义 RPC 接口和数据类型,通过编译器生成不同语言的代码(支持 C++,Java,Python,Ruby等),其数据传输采用二进制格式,相对 XML 和 JSON 来说体积更小,对于高并发、大数据量和多语言的环境更有优势。在 Facebook,Apache Thrift 正是使用于其内部服务的通信,其稳定性和高性能已在生产环境中得到证明的。
二、Thrift的架构
Apache Thrift 的架构如下图所示。
Thrift是一个客户端和服务器端的架构体系,在最上层是用户自行实现的业务逻辑代码。
第二层是由thrift编译器自动生成的代码,主要用于结构化数据的解析,发送和接收。
TServer主要任务是高效的接受客户端请求,并将请求转发给Processor处理。Processor负责对客户端的请求做出响应,包括RPC请求转发,调用参数解析和用户逻辑调用,返回值写回等处理。
从TProtocol以下部分是thirft的传输协议和底层I/O通信。TProtocol是用于数据类型解析的,将结构化数据转化为字节流给TTransport进行传输。
TTransport是与底层数据传输密切相关的传输层,负责以字节流方式接收和发送消息体,不关注是什么数据类型。
底层IO负责实际的数据传输,包括socket、文件和压缩数据流等。
协议层TProtocol:
在传输协议上总体上划分为文本(text)和二进制(binary)传输协议, 为节约带宽,提供传输效率,一般情况下使用二进制类型的传输协议为多数。
1>TBinaryProtocol – 二进制编码格式进行数据传输。
2>TCompactProtocol – 高效的编码方式,使用类似于protobuffer的Variable-Length Quantity (VLQ) 编码(可以节省传输空间,使数据的传输效率更高)对数据进行压缩。 关于VLQ了解更多(http://en.wikipedia.org/wiki/Variable-length_quantity)
3>TJSONProtocol – 使用JSON的数据编码协议进行数据传输。
4>TSimpleJSONProtocol – 这种节约只提供JSON只写的协议,适用于通过脚本语言解析
5>TDebugProtocol – 在开发的过程中帮助开发人员调试用的,以文本的形式展现方便阅读。
传输层TTransport
1>TSocket- 使用阻塞式I/O进行传输,也是最常见的模式。
2>TFramedTransport- 使用非阻塞方式,按块的大小,进行传输,类似于Java中的NIO。
3>TFileTransport- 顾名思义按照文件的方式进程传输,虽然这种方式不提供Java的实现,但是实现起来非常简单。
4>TMemoryTransport- 使用内存I/O,就好比Java中的ByteArrayOutputStream实现。
5>TZlibTransport- 使用执行zlib压缩,不提供Java的实现。
6>TNonblockingTransport-使用非阻塞方式,用于构建异步客户端。
服务端类型
1>TSimpleServer - 单线程服务器端使用标准的阻塞式I/O。
2>TThreadPoolServer - 多线程服务器端使用标准的阻塞式I/O。
3>TNonblockingServer – 多线程服务器端使用非阻塞式I/O,并且实现了Java中的NIO通道。
三、Thrift开发示例
Thrift的开发流程如下图所示:
1. 搭建Thrift编译环境
Linux下的源码安装,直接wget或手动下载最tar.gz安装包编译安装:
tar -xvf thrift-0.9.1.tar.gz
cd thrift-0.9.1
./configure
make
make install
使用命令thrift -version,显示Thrift version 0.9.1 则表示安装成功。
Windows下的Thrift安装直接使用Thrift提供的安装包即可。
2. 搭建Thrift开发环境
java环境下开发thrift的客户端或者服务器程序非常简单,只需在工程文件中加上下面三个jar包(版本可能随时有更新):
libthrift-0.9.1.jar
slf4j-api-1.7.5.jar
slf4j-simple.jar
3. 编写IDL文件
# 命名空间的定义 注意‘java’的关键字
namespace java testThrift.iface
# 结构体定义
struct Request {
1:required string paramJSON;
2:required string serviceName;
}
# 另一个结构体定义
struct Reponse {
1:required RESCODE responeCode;
2:required string responseJSON;
}
# 异常描述定义
exception ServiceException {
1:required EXCCODE exceptionCode;
2:required string exceptionMess;
}
# 枚举定义
enum RESCODE {
_200=200;
_500=500;
_400=400;
}
# 另一个枚举
enum EXCCODE {
PARAMNOTFOUND = 2001;
SERVICENOTFOUND = 2002;
}
# 服务定义
service HelloWorldService {
Reponse send(1:Request request) throws (1:ServiceException e);
i32 sayInt(1:i32 param)
string sayString(1:string param)
bool sayBoolean(1:bool param)
void sayVoid()
}
Apache Thrift中支持以下几种基本类型:
- bool: 布尔值 (true or false), one byte
- byte: 有符号字节
- i16: 16位有符号整型
- i32: 32位有符号整型
- i64: 64位有符号整型
- double: 64位浮点型
- string: 字符串/字符数组
- binary: 二进制数据(在java中表现为java.nio.ByteBuffer)
在面向对象语言中,表现为“类定义”;在弱类型语言、动态语言中,表现为“结构/结构体”。定义格式如下:
struct <结构体名称> {
<序号>:[字段性质] <字段类型> <字段名称> [= <默认值>] [;|,]
}
实例:
struct Request {
1:required binary paramJSON;
2:required string serviceName
3:optional i32 field1 = 0;
4:optional i64 field2,
5: list<map<string , string>> fields3
}
1.
结构体名称:可以按照业务需求,给定不同的名称(区分大小写)。但是要注意,一组IDL定义文件中
结构体名称不能重复,且不能使用IDL已经占用的关键字(例如required 、struct 等单词)。
2. 序号:序号非常重要。正整数,按照顺序排列使用。这个属性在Apache Thrift进行 序列化的时候被使用。
3. 字段性质:包括两种关键字:required 和 optional,如果您不指定,那么系统会默认为required。required表示这个字段 必须有值,并且Apache Thrift在进行序列化时,这个字段都会被序列化;optional表示这个字段不一定有值,且Apache Thrift在进行序列化时,这个字段 只有有值的情况下才会被序列化。
4. 字段类型:在struct中,字段类型可以是某一个基础类型,也可以是某一个之前定义好的struct,还可以是某种Apache Thrift支持的容器(set、map、list),还可以是定义好的枚举。字段的类型是必须指定的。
5. 字段名称:字段名称区分大小写,不能重复,且不能使用IDL已经占用的关键字(例如required 、struct 等单词)。
6. 默认值:您可以为某一个字段指定 默认值(也可以不指定)。
7. 结束符:在struct中,支持两种结束符,您可以使用“;”或者“,”。当然您也可以不使用结束符(Apache Thrift代码生成程序,会自己识别到)
Apache Thrift支持三种类型的容器,容器在各种编程语言中普遍存在:
list< T >:有序列表(JAVA中表现为 ArrayList),T可以是某种基础类型,也可以是某一个之前定义好的struct,还可以是某种Apache Thrift支持的容器(set、map、list),还可以是定义好的枚举。有序列表中的元素允许重复。
set< T >:无序元素集合(JAVA中表现为 HashSet),T可以是某种基础类型,也可以是某一个之前定义好的struct,还可以是某种Apache Thrift支持的容器(set、map、list),还可以是定义好的枚举。无序元素集合中的元素不允许重复,一旦重复后一个元素将覆盖前一个元素。
map<T, T> Map类型,必须指明泛型
以上IDL文件是可以直接用来生成各种语言的代码的。下面给出常用的各种不同语言的代码生成命令:
# 生成java
thrift-0.9.3 -gen java ./demoHello.thrift
# 生成c++
thrift-0.9.3 -gen cpp ./demoHello.thrift
# 生成php
thrift-0.9.3 -gen php ./demoHello.thrift
# 生成node.js
thrift-0.9.3 -gen js:node ./demoHello.thrift
# 生成c#
thrift-0.9.3 -gen csharp ./demoHello.thrift
# 您可以通过以下命令查看生成命令的格式
thrift-0.9.3 -help
Thrift生成产物
以下代码为IDL内容:
namespace java com.thrift.test
struct Parameter{
1: required i32 id;
2: required string name;
}
service DemoService{
i32 demoMethod(1:string param1, 2:Parameter param2, 3:map<string,string> param3);
}
使用命令行
生成的类主要有5个部分:
1. 接口类型,默认名称都是Iface。这个接口类型被服务器和客户端共同使用。服务器端使用它来做顶层接口,编写实现类。客户端代码使用它作为生成代理的服务接口。
自动生成的接口有两个,一个是同步调用的Iface,一个是异步调用的AsyncIface。异步调用的接口多了一个回调参数。
public interface Iface {
public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException;
}
public interface AsyncIface {
public void demoMethod(String param1, Parameter param2, Map<String,String> param3, org.apache.thrift.async.AsyncMethodCallback<AsyncClient.demoMethod_call> resultHandler) throws org.apache.thrift.TException;
}
2. 客户端类型,一个同步调用的客户端Client,一个异步调用的客户端AsyncClient
客户端类型的主要工作有两个:
1. 提供一个工厂方法来创建Client对象
2.接口方法的客户端代理,只做了两件事,发送方法调用请求;接收返回值
public static class Client extends org.apache.thrift.TServiceClient implements Iface {
public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
public Factory() {}
public Client getClient(org.apache.thrift.protocol.TProtocol prot) {
return new Client(prot);
}
public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
return new Client(iprot, oprot);
}
}
public int demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException{
send_demoMethod(param1, param2, param3);
return recv_demoMethod();
}
public void send_demoMethod(String param1, Parameter param2, Map<String,String> param3) throws org.apache.thrift.TException{
demoMethod_args args = new demoMethod_args();
args.setParam1(param1);
args.setParam2(param2);
args.setParam3(param3);
sendBase("demoMethod", args);
}
public int recv_demoMethod() throws org.apache.thrift.TException {
demoMethod_result result = new demoMethod_result();
receiveBase(result, "demoMethod");
if (result.isSetSuccess()) {
return result.success;
}
throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "demoMethod failed: unknown result");
}
}
//org.apache.thrift.TServiceClient.sendBase,客户端的父类方法
protected void sendBase(String methodName, TBase args) throws TException {
// 发送消息头
oprot_.writeMessageBegin(new TMessage(methodName, TMessageType.CALL, ++seqid_));
// 发送消息体,由方法参数对象自己处理编解码
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}
protected void receiveBase(TBase result, String methodName) throws TException {
// 接收消息头
TMessage msg = iprot_.readMessageBegin();
if (msg.type == TMessageType.EXCEPTION) {
TApplicationException x = TApplicationException.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
if (msg.seqid != seqid_) {
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, methodName + " failed: out of sequence response");
}
//由返回值对象自己处理编解码
result.read(iprot_);
iprot_.readMessageEnd();
}
}
发送方法调用请求做了2件事
1. 创建方法参数对象,封装方法参数
2. 调用父类的sendBase方法来发送消息。发送消息时先通过writeMessageBegin发送消息头,再调用方法参数对象的write(TProtocol)方法发送消息体,最后结束发送
接受调用返回值做了2件事
1. 创建方法返回值对象,封装方法返回值
2. 调用父类的receiveBase方法接收方法返回值。先通过receiveMessage接收消息体,处理异常,然后调用方法参数对象的read(TProtocol)方法来接收消息体,最后结束接收
3. Processor,用来支持方法调用,
每个服务的实现类都要使用Processor来注册,这样最后服务器端调用接口实现时能定位到具体的实现类。
public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class.getName());
public Processor(I iface) {
super(iface, getProcessMap(new HashMap<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
}
protected Processor(I iface, Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
super(iface, getProcessMap(processMap));
}
private static <I extends Iface> Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
processMap.put("sayHi", new sayHi());
return processMap;
}
private static class sayHi<I extends Iface> extends org.apache.thrift.ProcessFunction<I, sayHi_args> {
public sayHi() {
super("sayHi");
}
protected sayHi_args getEmptyArgsInstance() {
return new sayHi_args();
}
protected sayHi_result getResult(I iface, sayHi_args args) throws org.apache.thrift.TException {
sayHi_result result = new sayHi_result();
result.success = iface.sayHi(args.name);
return result;
}
}
}
Processer中注册了Service中的所有方法,并关联了sayHi_args和sayHi_result这两个类。
4. 方法参数的封装类,以"方法名_args"命名
方法参数实现了TBase接口,TBase接口定义了一个对象在某种协议下的编解码接口。
public interface TBase<T extends TBase<?,?>, F extends TFieldIdEnum> extends Comparable<T>, Serializable {
public void read(TProtocol iprot) throws TException;
public void write(TProtocol oprot) throws TException;
}
方法参数对象主要做了2件事
1. 创建每个参数的 元数据,包括参数类型,顺序号。顺序号是在IDL定义的时候设置的,用来识别参数的位置,在编解码的时候有用
2. 实现自己的 编解码方法, read(TProtocol), write(TProtocol)。这里又把具体的编解码功能委托给了 XXXScheme类
// 对象自己负责解码
public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
schemes.get(iprot.getScheme()).getScheme().read(iprot, this);
}
public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
schemes.get(oprot.getScheme()).getScheme().write(oprot, this);
}
再来看XXXScheme类,这也是自动生成的,是方法参数XXX_args的内部类。
Scheme有两类,一个是StandardScheme,使用消息头+消息体的方式来编解码对象。一个是TupleScheme,直接采用写消息体的方式编解码,编码字节流更小。
5. 方法返回值的封装类,以"方法名_result"命名
3. 服务端程序设计
新建Java工程或maven工程,java工程引入thrift相关的lib(libthrift-0.9.1.jar, slf4j-api-1.7.5.jar, slf4j-simple.jar),
对Thrift生成的接口进行实现:
package com.thrift.test;
import org.apache.thrift.TException;
public class DemoServiceImpl implements DemoService.Iface{
@Override
public String sayHi(String name) throws TException {
return "Hi " + name + ", from Thrift Server";
}
}
4. 编写服务器端代码
本例中服务器采用TNonblockingServer工作模式
package com.thrift.test.server;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import com.thrift.test.DemoService;
import com.thrift.test.DemoService.Iface;
import com.thrift.test.DemoServiceImpl;
public class Server {
public static void main(String[] args){
TNonblockingServerSocket socket;
try {
socket = new TNonblockingServerSocket(9090);
TNonblockingServer.Args options = new TNonblockingServer.Args(socket);
TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());
options.processor(processor);
options.protocolFactory(new TCompactProtocol.Factory());
TServer server = new TNonblockingServer(options);
System.out.println("Thrift Server is running at 9090 port");
server.serve();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在服务器端启动thrift框架的部分代码比较简单,不过在写这些启动代码之前需要先 确定服务器采用哪种工作模式对外提供服务 ,Thrift对外提供几种工作模式,例如: TSimpleServer、TNonblockingServer、TThreadPoolServer、TThreadedSelectorServer 等模式,每种服务模式的通信方式不一样,因此在服务启动时使用了那种服务模式, 客户端程序也需要采用对应的通信方式 。
Thrift支持多种通信协议格式:TCompactProtocol、TBinaryProtocol、TJSONProtocol等,因此,在使用Thrift框架时,客户端程序与服务器端程序所使用的通信协议一定要一致,否则便无法正常通信。
服务器端创建并启动Thrift服务框架的过程为:
1. 为自己的服务实现类定义一个对象:
这里的TestThriftServiceImpl类就是服务器端对各服务接口的实现类。TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());
2. 定义一个TProcess对象,在根据Thrift文件生成java源码接口文件TestThriftService.java中,Thrift已经自动为我们定义了一个Processor:
3. 定义一个TNonblockingServerSocket对象,用于tcp的socket通信:TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());
在创建server端socket时需要指明监听端口号,即上面的变量:m_thriftPort。socket = new TNonblockingServerSocket(9090);
4. 定义TNonblockingServer所需的参数对象TNonblockingServer.Args;并设置所需的参数:
在TNonblockingServer模式下我们使用协议:TCompactProtocol,通信方式采用TFramedTransport,即以帧的方式对数据进行传输。TNonblockingServer.Args options = new TNonblockingServer.Args(socket); TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl()); options.processor(processor); options.protocolFactory(new TCompactProtocol.Factory());
5. 定义TNonblockingServer对象,并启动该服务:
TServer server = new TNonblockingServer(options); System.out.println("Thrift Server is running at 9090 port"); server.serve();
5. 编写客户端代码
客户端同样需要引入依赖和IDL生成的类,代码如下:
package com.test.client; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import com.thrift.test.DemoService; public class Client { public static void main(String[] args) throws Exception{ TSocket socket = new TSocket("127.0.0.1", 9090); socket.setTimeout(3000); TTransport transport = new TFramedTransport(socket); TProtocol protocol = new TBinaryProtocol(transport); transport.open(); System.out.println("Connected to Thrfit Server"); DemoService.Client client = new DemoService.Client.Factory().getClient(protocol); String result = client.sayHi("ITer_ZC"); System.out.println(result); } }
四、Thrift调用模型
Thrift的方法调用模型很简单,就是通过方法名和实际方法实现类的注册完成,没有使用反射机制,类加载机制。
和方法调用相关的几个核心类:
1. 自动生成的Iface接口,是远程方法的顶层接口
2. 自动生成的Processor类及相关父类,包括TProcessor接口,TBaseProcess抽象类
3. ProcessFunction抽象类,抽象了一个具体的方法调用,包含了方法名信息,调用方法的抽象过程等
4. TNonblcokingServer,是NIO服务器的默认实现,通过Args参数来配置Processor等信息
5. FrameBuffer类,服务器NIO的缓冲区对象,这个对象在服务器端收到全包并解码后,会调用Processor去完成实际的方法调用
6. 服务器端的方法的具体实现类,实现Iface接口
TProcess相关类和接口
1. TProcessor就定义了一个顶层的调用方法process,参数是输入流和输出流
public interface TProcessor { public boolean process(TProtocol in, TProtocol out) throws TException; }
2. 抽象类TBaseProcessor提供了TProcessor的process的默认实现,先读消息头,拿到要调用的方法名,然后从维护的一个Map中取ProcessFunction对象。ProcessFunction对象是实际方法的抽象,调用它的process方法,实际是调用了实际的方法。
public abstract class TBaseProcessor<I> implements TProcessor { private final I iface; private final Map<String,ProcessFunction<I, ? extends TBase>> processMap; protected TBaseProcessor(I iface, Map<String, ProcessFunction<I, ? extends TBase>> processFunctionMap) { this.iface = iface; this.processMap = processFunctionMap; } @Override public boolean process(TProtocol in, TProtocol out) throws TException { TMessage msg = in.readMessageBegin(); ProcessFunction fn = processMap.get(msg.name); if (fn == null) { TProtocolUtil.skip(in, TType.STRUCT); in.readMessageEnd(); TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'"); out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid)); x.write(out); out.writeMessageEnd(); out.getTransport().flush(); return true; } fn.process(msg.seqid, in, out, iface); return true; } }
3. Processor类是自动生成的,它依赖Iface接口,负责把实际的方法实现和方法的key关联起来,放到Map中维护自动生成的sayHi类继承了ProcessFunction类,它负载把方法参数,iface, 方法返回值这些抽象的概念组合在一起,通过抽象模型来完成实际方法的调用。实际方法的实现者实现了Iface接口。
public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(Processor.class.getName()); public Processor(I iface) { super(iface, getProcessMap(new HashMap<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>())); } protected Processor(I iface, Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) { super(iface, getProcessMap(processMap)); } private static <I extends Iface> Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(Map<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) { processMap.put("sayHi", new sayHi()); return processMap; } private static class sayHi<I extends Iface> extends org.apache.thrift.ProcessFunction<I, sayHi_args> { public sayHi() { super("sayHi"); } protected sayHi_args getEmptyArgsInstance() { return new sayHi_args(); } protected sayHi_result getResult(I iface, sayHi_args args) throws org.apache.thrift.TException { sayHi_result result = new sayHi_result(); result.success = iface.sayHi(args.name); return result; } } }
TNonblcokingServer类
TNonblockingServer是NIO服务器的实现,它通过Selector来检查IO就绪状态,进而调用相关的Channel。就方法调用而言,它处理的是读事件,用handelRead()来进一步处理。
private void select() { try { // wait for io events. selector.select(); // process the io events we received Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator(); while (!stopped_ && selectedKeys.hasNext()) { SelectionKey key = selectedKeys.next(); selectedKeys.remove(); // skip if not valid if (!key.isValid()) { cleanupSelectionKey(key); continue; } // if the key is marked Accept, then it has to be the server // transport. if (key.isAcceptable()) { handleAccept(); } else if (key.isReadable()) { // deal with reads handleRead(key); } else if (key.isWritable()) { // deal with writes handleWrite(key); } else { LOGGER.warn("Unexpected state in select! " + key.interestOps()); } } } catch (IOException e) { LOGGER.warn("Got an IOException while selecting!", e); } } protected void handleRead(SelectionKey key) { FrameBuffer buffer = (FrameBuffer) key.attachment(); if (!buffer.read()) { cleanupSelectionKey(key); return; } // if the buffer's frame read is complete, invoke the method. <strong>if (buffer.isFrameFullyRead()) { if (!requestInvoke(buffer)) { cleanupSelectionKey(key); } }</strong> } protected boolean requestInvoke(FrameBuffer frameBuffer) { frameBuffer.invoke(); return true; }
FrameBuffer类
非阻塞同步IO的NIO服务器都会使用缓冲区来存放读写的中间状态。FrameBuffer就是这样的一个缓冲区,它由于涉及到方法调用,所以提供了invoke()方法来实现对Processor的调用。
FrameBuffer使用了processorFactory来获得Processor。ProcessorFactory是在创建服务器的时候传递过来的,只是对Processor的简单封装。public void invoke() { TTransport inTrans = getInputTransport(); TProtocol inProt = inputProtocolFactory_.getProtocol(inTrans); TProtocol outProt = outputProtocolFactory_.getProtocol(getOutputTransport()); try { processorFactory_.getProcessor(inTrans).process(inProt, outProt); responseReady(); return; } catch (TException te) { LOGGER.warn("Exception while invoking!", te); } catch (Throwable t) { LOGGER.error("Unexpected throwable while invoking!", t); } // This will only be reached when there is a throwable. state_ = FrameBufferState.AWAITING_CLOSE; requestSelectInterestChange(); }
protected TServer(AbstractServerArgs args) { processorFactory_ = args.processorFactory; serverTransport_ = args.serverTransport; inputTransportFactory_ = args.inputTransportFactory; outputTransportFactory_ = args.outputTransportFactory; inputProtocolFactory_ = args.inputProtocolFactory; outputProtocolFactory_ = args.outputProtocolFactory; } public class TProcessorFactory { private final TProcessor processor_; public TProcessorFactory(TProcessor processor) { processor_ = processor; } public TProcessor getProcessor(TTransport trans) { return processor_; } } public T processor(TProcessor processor) { this.processorFactory = new TProcessorFactory(processor); return (T) this; }
五、Thrift Transport传输层
Thrift使用了Transport来封装传输层,但Transport不仅仅是底层网络传输,它还是上层流的封装。
TTransport作为顶层的抽象,使用了抽象类,没有使用接口。和Java的IO一样,Thrift的Transport也采用了装饰器模式实现了所谓的包装流。
节点流表示自身采用byte[]来提供IO读写的类:
- AutoExpandingBufferReadTransport
- AutoExpandingBufferWriteTransport
- TMemoryInputTransport
- TByteArrayOutputStream
- TMemoryBuffer
包装流表示封装了其他Transport流来提供IO读写的类:
- TFramedTransport
- TFastFramedTransport
Thrift提供的包装流主要就是两个以TFrame开头的Transort,这两个Transport在写完消息flush的时候,会加上4字节表示长度的消息头,读消息是会先读4字节表示长度的消息头。
六、Thrift TServer服务器分析
Thrift采用了TServer来作为服务器的抽象,提供了多种类型的服务器实现。用TServerTransport作为服务器的Acceptor抽象,来监听端口,创建客户端Socket连接。
TServerTransport。主要有两类
1. TNonblockingServerTransport和TNonblockingServerSocket作为非阻塞IO的Acceptor,封装了ServerSocketChannel
2. TServerSocket作为阻塞同步IO的Acceptor,封装了ServerSocket
TServer的类层次结构,主要也是两类,非阻塞IO和同步IO
非阻塞IO的Server有:
1. TNonblockingServer是单线程的,只有一个SelectAcceptThread线程来轮询IO就绪事件,调用就绪的channel来相应Accept, Read, Write事件,并且还是使用这个线程来同步调用实际的方法实现。
2. THsHaServer是所谓的半同步半异步的服务器。所谓半同步是说使用一个SelectAcceptThread线程来轮询IO就绪事件,调用就绪的channel来相应Accept, Read, Write事件。所谓的半异步是说方法的调用是封装成一个Runnable交给线程池来执行的(单线程Reactor模式),交给线程池立刻返回,不同步等待方法执行完毕,方法执行完毕的写返回是有线程池中的线程来做的,实现了所谓的异步访问的模式。
3. TThreadSelectorServer,这个服务器类比较有意思,是多线程Reactor模式的一种实现。TThreadSelectorServer在吞吐量和服务器响应时间的表现都是最优的
1 采用了一个AcceptorThread来专门监听端口,处理Accept事件,然后创建SocketChannel。创建完成之后交给一个线程池来处理后续动作,将SocketChannel放到SelecotrThread的阻塞队列acceptedQueue中
2 采用多个SelectorThread来处理创建好的SocketChannel。每个SelectorThread绑定一个Selector,这样将SocketChannel分给多个Selector。同时SelectorThread又维护了一个阻塞队列acceptedQueue,从acceptedQueue中拿新创建好的SocketChannel,来注册读事件。
同步阻塞IO的Server有:
TThreadPoolServer,关联一个TServerSocket,采用同步IO的方式来Accept,然后交给一个线程池来处理后续动作。