RPC入门总结(六)Thrift的介绍和用法

转载:由浅入深了解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中支持以下几种基本类型:

  1. bool: 布尔值 (true or false), one byte
  2. byte: 有符号字节
  3. i16: 16位有符号整型
  4. i32: 32位有符号整型
  5. i64: 64位有符号整型
  6. double: 64位浮点型
  7. string: 字符串/字符数组
  8. 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支持多种通信协议格式:TCompactProtocolTBinaryProtocolTJSONProtocol等,因此,在使用Thrift框架时,客户端程序与服务器端程序所使用的通信协议一定要一致,否则便无法正常通信。
服务器端创建并启动Thrift服务框架的过程为:
1. 为自己的服务实现类定义一个对象

TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());


这里的TestThriftServiceImpl类就是服务器端对各服务接口的实现类。
2. 定义一个TProcess对象,在根据Thrift文件生成java源码接口文件TestThriftService.java中,Thrift已经自动为我们定义了一个Processor
TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());

3. 定义一个TNonblockingServerSocket对象,用于tcp的socket通信:
socket = new TNonblockingServerSocket(9090);

在创建server端socket时需要指明监听端口号,即上面的变量:m_thriftPort。
4. 定义TNonblockingServer所需的参数对象TNonblockingServer.Args;并设置所需的参数:
            TNonblockingServer.Args options = new TNonblockingServer.Args(socket);  
            TProcessor processor = new DemoService.Processor<Iface>(new DemoServiceImpl());  
            options.processor(processor);  
            options.protocolFactory(new TCompactProtocol.Factory());  

在TNonblockingServer模式下我们使用协议:TCompactProtocol,通信方式采用TFramedTransport,即以帧的方式对数据进行传输

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的调用。

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();  
}  
FrameBuffer使用了processorFactory来获得Processor。ProcessorFactory是在创建服务器的时候传递过来的,只是对Processor的简单封装。

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. TNonblockingServerTransportTNonblockingServerSocket作为非阻塞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,然后交给一个线程池来处理后续动作。




  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值