java-5.4 thrift

176 阅读11分钟

需求

The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.

Thrift是一个轻量级、跨语言的远程服务调用框架,最初由Facebook开发,后面进入Apache开源项目。它通过自身的IDL中间语言, 并借助代码生成引擎生成各种主流语言的RPC服务端/客户端模板代码。

Thrift支持多种不同的编程语言,包括C++、Java、Python、PHP、Ruby等,本系列主要讲述基于Java语言的Thrift的配置方式和具体使用。

设计

分层

image.png Thrift软件栈分层从下向上分别为:传输层(Transport Layer)、协议层(Protocol Layer)、处理层(Processor Layer)和服务层(Server Layer)。

  • 传输层(Transport Layer):传输层负责直接从网络中读取和写入数据,它定义了具体的网络传输协议;比如说TCP/IP传输等。
  • 协议层(Protocol Layer):协议层定义了数据传输格式,负责网络传输数据的序列化和反序列化;比如说JSON、XML、二进制数据等。
  • 处理层(Processor Layer):处理层是由具体的IDL(接口描述语言)生成的,封装了具体的底层网络传输和序列化方式,并委托给用户实现的Handler进行处理。
  • 服务层(Server Layer):整合上述组件,提供具体的网络线程/IO服务模型,形成最终的服务。

transport

封装socket通信API client端为TTransport server端为TServerTransport

实现种类:

TSocket/TServerSocket:使用阻塞式I/O进行传输,是最常见的模式

TFramedTransport/TNonblockingServerSocket:使用非阻塞方式,按块的大小进行传输,类似于Java中的NIO

protocol

指定消息传递格式 client端为TProtocol,持有transport对象,封装按照消息传递指定格式进行读写socket流 server端TProtocolFactory,指定协议类型

Thrift可以让用户选择客户端与服务端之间传输通信协议的类别,在传输协议上总体划分为文本(text)和二进制(binary)传输协议。为节约带宽,提高传输效率,一般情况下使用二进制类型的传输协议为多数,有时还会使用基于文本类型的协议,这需要根据项目/产品中的实际需求。常用协议有以下几种:

TBinaryProtocol:二进制编码格式进行数据传输

TCompactProtocol:高效率的、密集的二进制编码格式进行数据传输

TJSONProtocol: 使用JSON文本的数据编码协议进行数据传输

TSimpleJSONProtocol:只提供JSON只写的协议,适用于通过脚本语言解析

Iface/processor/Client

定义rpc接口与接口实现 client端 Client实现Iface接口,并且持有protocol对象可以对定义的接口方法以发送消息给服务端并接收返回信息,因为Client是thrift自动生成,所以用户可以直接使用 server端 processor持有Iface接口的服务端实现,封装通用的处理消息逻辑,按照用户指定的protocol读取消息,回调实现的Iface接口方法,返回指定消息格式应答

server

持有transport和processor对象,指定protocolFactory,可以统一thrift服务模型

仅server端,实现种类:

TSimpleServer:单线程服务器端,使用标准的阻塞式I/O

TThreadPoolServer:多线程服务器端,使用标准的阻塞式I/O

TNonblockingServer:单线程服务器端,使用非阻塞式I/O

THsHaServer:半同步半异步服务器端,基于非阻塞式IO读写和多线程工作任务处理

TThreadedSelectorServer:多线程选择器服务器端,对THsHaServer在异步IO模型上进行增强

特性

多语言/跨语言支持

接口维护简单

Usage

IDL

写hello.thrift的IDL文件

service HelloWorldService {
  string say(1: string username)
}

使用代码生成工具生成代码,执行以下命令

thrift -gen java hello.thrift

接口

由于未指定代码生成的目标目录,生成的类文件默认存放在gen-java目录下生成一个HelloWorldService.java类文件

Thrift框架,仅需要关注以下四个核心内部接口/类:Iface, AsyncIface, Client和AsyncClient。

  • Iface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的同步业务逻辑。

  • AsyncIface:服务端通过实现HelloWorldService.Iface接口,向客户端的提供具体的异步业务逻辑。

  • Client:客户端通过HelloWorldService.Client的实例对象,以同步的方式访问服务端提供的服务方法。

  • AsyncClient:客户端通过HelloWorldService.AsyncClient的实例对象,以异步的方式访问服务端提供的服务方法。

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<String> resultHandler) throws org.apache.thrift.TException;
    }

    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 Client(org.apache.thrift.protocol.TProtocol prot) {
            super(prot, prot);
        }

        public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
            super(iprot, oprot);
        }

        public String say(String username) throws org.apache.thrift.TException {
            send_say(username);
            return recv_say();
        }
        // 省略.....
    }

    public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {
        public static class Factory implements org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {
            private org.apache.thrift.async.TAsyncClientManager clientManager;
            private org.apache.thrift.protocol.TProtocolFactory protocolFactory;

            public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) {
                this.clientManager = clientManager;
                this.protocolFactory = protocolFactory;
            }

            public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) {
                return new AsyncClient(protocolFactory, clientManager, transport);
            }
        }

        public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) {
            super(protocolFactory, clientManager, transport);
        }

        public void say(String username, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException {
            checkReady();
            say_call method_call = new say_call(username, resultHandler, this, ___protocolFactory, ___transport);
            this.___currentMethod = method_call;
            ___manager.call(method_call);
        }
        // 省略.....
    }
    // 省略.....
}

实现

TServer

TServer定义了静态内部类Args,Args继承自抽象类AbstractServerArgs。AbstractServerArgs采用了建造者模式,向TServer提供各种工厂:

工厂属性工厂类型作用
ProcessorFactoryTProcessorFactory处理层工厂类,用于具体的TProcessor对象的创建
InputTransportFactoryTTransportFactory传输层输入工厂类,用于具体的TTransport对象的创建
OutputTransportFactoryTTransportFactory传输层输出工厂类,用于具体的TTransport对象的创建
InputProtocolFactoryTProtocolFactory协议层输入工厂类,用于具体的TProtocol对象的创建
OutputProtocolFactoryTProtocolFactory协议层输出工厂类,用于具体的TProtocol对象的创建

下面是TServer的部分核心代码:

TServer的三个方法:serve()、stop()和isServing()。serve()用于启动服务,stop()用于关闭服务,isServing()用于检测服务的起停状态。

TServer的不同实现类的启动方式不一样,因此serve()定义为抽象方法。不是所有的服务都需要优雅的退出, 因此stop()方法没有被定义为抽象。

public abstract class TServer {
    public static class Args extends org.apache.thrift.server.TServer.AbstractServerArgs<org.apache.thrift.server.TServer.Args> {
        public Args(TServerTransport transport) {
            super(transport);
        }
    }

    public static abstract class AbstractServerArgs<T extends org.apache.thrift.server.TServer.AbstractServerArgs<T>> {
        final TServerTransport serverTransport;
        TProcessorFactory processorFactory;
        TTransportFactory inputTransportFactory = new TTransportFactory();
        TTransportFactory outputTransportFactory = new TTransportFactory();
        TProtocolFactory inputProtocolFactory = new TBinaryProtocol.Factory();
        TProtocolFactory outputProtocolFactory = new TBinaryProtocol.Factory();

        public AbstractServerArgs(TServerTransport transport) {
            serverTransport = transport;
        }
    }

    protected TProcessorFactory processorFactory_;
    protected TServerTransport serverTransport_;
    protected TTransportFactory inputTransportFactory_;
    protected TTransportFactory outputTransportFactory_;
    protected TProtocolFactory inputProtocolFactory_;
    protected TProtocolFactory outputProtocolFactory_;
    private boolean isServing;
    protected TServer(org.apache.thrift.server.TServer.AbstractServerArgs args) {
        processorFactory_ = args.processorFactory;
        serverTransport_ = args.serverTransport;
        inputTransportFactory_ = args.inputTransportFactory;
        outputTransportFactory_ = args.outputTransportFactory;
        inputProtocolFactory_ = args.inputProtocolFactory;
        outputProtocolFactory_ = args.outputProtocolFactory;
    }
    public abstract void serve();
    public void stop() {}
    public boolean isServing() {
        return isServing;
    }
    protected void setServing(boolean serving) {
        isServing = serving;
    }
}

TSimpleServer

TSimpleServer的工作模式采用最简单的阻塞IO,实现方法简洁明了,便于理解,但是一次只能接收和处理一个socket连接,效率比较低。它主要用于演示Thrift的工作过程,在实际开发过程中很少用到它。

public void serve() {
    try {
      serverTransport_.listen();//开启监听
    } catch (TTransportException ttx) {
      LOGGER.error("Error occurred during listening.", ttx);
      return;
    }
    // Run the preServe event
    if (eventHandler_ != null) {
      eventHandler_.preServe();
    }
    setServing(true);

    while (!stopped_) {//单线程轮询处理
      TTransport client = null;
      TProcessor processor = null;
      TTransport inputTransport = null;
      TTransport outputTransport = null;
      TProtocol inputProtocol = null;
      TProtocol outputProtocol = null;
      ServerContext connectionContext = null;
      try {
        client = serverTransport_.accept();//接收客户端请求
        if (client != null) {
          processor = processorFactory_.getProcessor(client);
          inputTransport = inputTransportFactory_.getTransport(client);
          outputTransport = outputTransportFactory_.getTransport(client);
          inputProtocol = inputProtocolFactory_.getProtocol(inputTransport);
          outputProtocol = outputProtocolFactory_.getProtocol(outputTransport);
          if (eventHandler_ != null) {
            connectionContext = eventHandler_.createContext(inputProtocol, outputProtocol);
          }
          while (true) {
            if (eventHandler_ != null) {
              eventHandler_.processContext(connectionContext, inputTransport, outputTransport);
            }
            processor.process(inputProtocol, outputProtocol);//thrirft生成的processor进行处理,调用服务端实现并返回结果
          }
        }
      }
  }

ThreadPoolServer

TThreadPoolServer模式采用阻塞socket方式工作,主线程负责阻塞式监听是否有新socket到来,具体的业务处理交由一个线程池来处理。

  public void serve() {
  	if (!preServe()) {
  		return;
  	}
  	execute();
  	waitForShutdown();
    setServing(false);
  }
  protected void execute() {
    int failureCount = 0;
    while (!stopped_) {
      try {
        TTransport client = serverTransport_.accept();//接收客户端连接请求
        WorkerProcess wp = new WorkerProcess(client);

        int retryCount = 0;
        long remainTimeInMillis = requestTimeoutUnit.toMillis(requestTimeout);
        while(true) {
          try {
            executorService_.execute(wp);//以阻塞的方式接受客户端的连接请求,每进入一个连接,将通道对象封装成一个WorkerProcess对象(WorkerProcess实现了Runnabel接口),并提交到线程池。
            break;
          }
        }
    }
  }
  • TThreadPoolServer模式的优点 拆分了监听线程(Accept Thread)和处理客户端连接的工作线程(Worker Thread),数据读取和业务处理都交给线程池处理。因此在并发量较大时新连接也能够被及时接受。

线程池模式比较适合服务器端能预知最多有多少个客户端并发的情况,这时每个请求都能被业务线程池及时处理,性能也非常高。

  • TThreadPoolServer模式的缺点 线程池模式的处理能力受限于线程池的工作能力,当并发请求数大于线程池中的线程数时,新请求也只能排队等待。

TNonblockingServer

TNonblockingServer模式也是单线程工作,但是采用NIO的模式,借助Channel/Selector机制, 采用IO事件模型来处理。

所有的socket都被注册到selector中,在一个线程中通过seletor循环监控所有的socket。

每次selector循环结束时,处理所有的处于就绪状态的socket,对于有数据到来的socket进行数据读取操作,对于有数据发送的socket则进行数据发送操作,对于监听socket则产生一个新业务socket并将其注册到selector上。

注意:TNonblockingServer要求底层的传输通道必须使用TFramedTransport。

  public void serve() {
    // start any IO threads
    if (!startThreads()) {//启动线程处理io
      return;
    }
    // start listening, or exit
    if (!startListening()) {
      return;
    }
    setServing(true);
    // this will block while we serve
    waitForShutdown();
    setServing(false);
    // do a little cleanup
    stopListening();
  }
public void run() {
  try {
    if (eventHandler_ != null) {
      eventHandler_.preServe();
    }

    while (!stopped_) {
      select();//nio select模型
      processInterestChanges();
    }
    for (SelectionKey selectionKey : selector.keys()) {
      cleanupSelectionKey(selectionKey);
    }
  }
}
private void select() {
  try {
    // wait for io events.
    selector.select();//等待io事件
    // 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());
      }
    }
  }
}
  • TNonblockingServer模式优点 相比于TSimpleServer效率提升主要体现在IO多路复用上,TNonblockingServer采用非阻塞IO,对accept/read/write等IO事件进行监控和处理,同时监控多个socket的状态变化。
  • TNonblockingServer模式缺点 TNonblockingServer模式在业务处理上还是采用单线程顺序来完成。在业务处理比较复杂、耗时的时候,例如某些接口函数需要读取数据库执行时间较长,会导致整个服务被阻塞住,此时该模式效率也不高,因为多个调用请求任务依然是顺序一个接一个执行。

THsHaServer

鉴于TNonblockingServer的缺点,THsHaServer继承于TNonblockingServer,引入了线程池提高了任务处理的并发能力。THsHaServer是半同步半异步(Half-Sync/Half-Async)的处理模式,Half-Aysnc用于IO事件处理(Accept/Read/Write),Half-Sync用于业务handler对rpc的同步处理上。

注意:THsHaServer和TNonblockingServer一样,要求底层的传输通道必须使用TFramedTransport。

THsHaServer继承于TNonblockingServer,新增了线程池并发处理工作任务的功能,查看线程池的相关代码:

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.
  if (buffer.isFrameFullyRead()) {
    if (!requestInvoke(buffer)) {//AbstractNonblockingServer.java处理read操作时,调用下面THsHaServer的requestInvoke方法
      cleanupSelectionKey(key);
    }
  }
}
protected boolean requestInvoke(FrameBuffer frameBuffer) {
    try {
      Runnable invocation = getRunnable(frameBuffer);
      invoker.execute(invocation);//交由线程池处理
      return true;
    } catch (RejectedExecutionException rx) {
      LOGGER.warn("ExecutorService rejected execution!", rx);
      return false;
    }
}
  • THsHaServer的优点 THsHaServer与TNonblockingServer模式相比,THsHaServer在完成数据读取之后,将业务处理过程交由一个线程池来完成,主线程直接返回进行下一次循环操作,效率大大提升。
  • THsHaServer的缺点 主线程仍然需要完成所有socket的监听接收、数据读取和数据写入操作。当并发请求数较大时,且发送数据量较多时,监听socket上新连接请求不能被及时接受。

TThreadedSelectorServer

TThreadedSelectorServer是对THsHaServer的一种扩充,它将selector中的读写IO事件(read/write)从主线程中分离出来。同时引入worker工作线程池,它也是种Half-Sync/Half-Async的服务模型。

image.png TThreadedSelectorServer模式是目前Thrift提供的最高级的线程服务模型,它内部有如果几个部分构成:

  • 一个AcceptThread线程对象,专门用于处理监听socket上的新连接。
  • 若干个SelectorThread对象专门用于处理业务socket的网络I/O读写操作,所有网络数据的读写均是有这些线程来完成。
  • 一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
  • 一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求数据读取之后,交给ExecutorService线程池中的线程完成此次调用的具体执行。主要用于处理每个rpc请求的handler回调处理(这部分是同步的)。
  protected boolean startThreads() {
    try {
      for (int i = 0; i < args.selectorThreads; ++i) {
        selectorThreads.add(new SelectorThread(args.acceptQueueSizePerThread));//将网络I/O操作分散到多个SelectorThread线程中来完成,因此能够快速对网络I/O进行读写操作,能够很好地应对网络I/O较多的情况。
      }
      acceptThread = new AcceptThread((TNonblockingServerTransport) serverTransport_,
        createSelectorThreadLoadBalancer(selectorThreads));//专门的线程AcceptThread用于处理新连接请求,因此能够及时响应大量并发连接请求
      for (SelectorThread thread : selectorThreads) {
        thread.start();
      }
      acceptThread.start();
      return true;
    } catch (IOException e) {
      LOGGER.error("Failed to start threads!", e);
      return false;
    }
  }

参考

官方文档 Apache Thrift系列讲解博客