[thrift] thrift基本原理及使用

参考文章RPC 基本原理与 Apach Thrift 初体验
java

RPC基本原理

RPC(Remote Procedure Call),远程过程调用,大部分的RPC框架都遵循以下三个开发步骤:python

1. 定义一个接口说明文件:描述了对象(结构体)、对象成员、接口方法等一系列信息;
2. 经过RPC框架所提供的编译器,将接口说明文件编译成具体的语言文件;
3. 在客户端和服务器端分别引入RPC编译器所生成的文件,便可像调用本地方法同样调用服务端代码;

   
   
   

  

RPC通讯过程以下图所示
RPC通讯过程
通讯过程包括如下几个步骤:web

一、客户过程以正常方式调用客户桩(client stub,一段代码);
二、客户桩生成一个消息,而后调用本地操做系统;
三、客户端操做系统将消息发送给远程操做系统;
四、远程操做系统将消息交给服务器桩(server stub,一段代码);
五、服务器桩将参数提取出来,而后调用服务器过程;
六、服务器执行要求的操做,操做完成后将结果返回给服务器桩;
七、服务器桩将结果打包成一个消息,而后调用本地操做系统;
八、服务器操做系统将含有结果的消息发送回客户端操做系统;
九、客户端操做系统将消息交给客户桩;
十、客户桩将结果从从消息中提取出来,返回给调用它的客户过程;

   
   
   

  

全部这些步骤的效果是,将客户过程对客户桩发出的本地调用转换成对服务器过程的本地调用,而客户端和服务器都不会意识到有中间步骤的存在。apache

这个时候,你可能会想,既然是调用另外一台机器的服务,使用 RESTful API 也能够实现啊,为何要选择 RPC 呢?咱们能够从两个方面对比:编程

  • 资源粒度。RPC 就像本地方法调用,RESTful API 每一次添加接口均可能须要额外地组织开放接口的数据,这至关于在应用视图中再写了一次方法调用,并且它还须要维护开发接口的资源粒度、权限等;
  • 流量消耗。RESTful API 在应用层使用 HTTP 协议,哪怕使用轻型、高效、传输效率高的 JSON 也会消耗较大的流量,而 RPC 传输既可使用 TCP 也可使用 UDP,并且协议通常使用二制度编码,大大下降了数据的大小,减小流量消耗。

对接异构第三方服务时,一般使用 HTPP/RESTful 等公有协议,对于内部的服务调用,应用选择性能更高的二进制私有协议。json

Thrift架构

thrift主要用于各个服务之间的RPC通讯,支持跨语言。thrift是一个典型的CS结构,客户端和服务端可使用不一样的语言开发,thrift经过IDL(Interface Description Language)来关联客户端和服务端。thrift的总体架构图以下图所示
服务器

thrift架构

图中Your Code是用户实现的业务逻辑,接下来的 FooService.ClientFoo.write()/read()是thrift根据IDL生成的客户端和服务端的代码,对应于RPC中Client stub和Server stub。TProtocol 用来对数据进行序列化与反序列化,具体方法包括二进制,JSON 或者 Apache Thrift 定义的格式。TTransport 提供数据传输功能,使用 Apache Thrift 能够方便地定义一个服务并选择不一样的传输协议。
以下图所示为thrift的网络栈结构
thrift网络栈结构

thirft使用socket进行数据传输,数据以特定的格式发送,接收方进行解析。咱们定义好thrift的IDL文件后,就可使用thrift的编译器来生成双方语言的接口、model,在生成的model以及接口代码中会有解码编码的代码。markdown

TTransport层

表明thrift的数据传输方式,thrift定义了以下几种经常使用数据传输方式网络

  • TSocket: 阻塞式socket;
  • TFramedTransport: 以frame为单位进行传输,非阻塞式服务中使用;
  • TFileTransport: 以文件形式进行传输;

TProtocol层

表明thrift客户端和服务端之间传输数据的协议,通俗来说就是客户端和服务端之间传输数据的格式(例如json等),thrift定义了以下几种常见的格式多线程

  • TBinaryProtocol: 二进制格式;
  • TCompactProtocol: 压缩格式;
  • TJSONProtocol: JSON格式;
  • TSimpleJSONProtocol: 提供只写的JSON协议;

thrift支持的Server模型

thrift主要支持如下几种服务模型

  • TSimpleServer: 简单的单线程服务模型,经常使用于测试;
  • TThreadPoolServer: 多线程服务模型,使用标准的阻塞式IO;
  • TNonBlockingServer: 多线程服务模型,使用非阻塞式IO(须要使用TFramedTransport数据传输方式);
  • THsHaServer: THsHa引入了线程池去处理,其模型读写任务放到线程池去处理,Half-sync/Half-async处理模式,Half-async是在处理IO事件上(accept/read/write io),Half-sync用于handler对rpc的同步处理;

thrift IDL文件

thrift IDL不支持无符号的数据类型,由于不少编程语言中不存在无符号类型,thrift支持一下几种基本的数据类型

  • byte: 有符号字节
  • i16: 16位有符号整数
  • i32: 32位有符号整数
  • i64: 63位有符号整数
  • double: 64位浮点数
  • string: 字符串

此外thrift还支持如下容器类型:

  • list: 一系列由T类型的数据组成的有序列表,元素能够重复;
  • set: 一系列由T类型的数据组成的无序集合,元素不可重复;
  • map: 一个字典结构,Key为K类型,Value为V类型,至关于java中的HashMap;

thrift容器中元素的类型能够是除了service以外的任何类型,包括exception

thirft支持struct类型,目的就是讲一些数据聚合在一块儿,方便传输管理,struct定义形式以下:

struct People {
    1:string name;
    2:i32 age;
    3:string gender;
}
   
   
   

  

thrift支持枚举类型,定义形式以下:

enum Gender {
    MALE,
    FEMALE
}
   
   
   

  

thrift支持自定义异常类型exception,异常定义形式以下:

exception RequestException {
    1:i32 code;
    2:string reason;
}
   
   
   

  

thrift定义服务至关于Java中建立接口同样,建立的service通过代码生thrift代码生成工具编译后就会生成客户端和服务端的框架代码,service的定义形式以下:

service HelloWorldService {
    // service中能够定义若干个服务,至关于Java Interface中定义的方法
    string doAction(1:string name, 2:i32 age);
}
   
   
   

  

thrift支持给类型定义别名,以下所示:

typedef i32 int
typedef i64 long
   
   
   

  

thrift也支持常量的定义,使用const关键字:

const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com";
   
   
   

  

thrift支持命名空间,命名空间至关于Java中的package,主要用于组织代码,thrift使用关键字namespace定义命名空间,格式是namespace 语言名 路径,以下示例所示:

namespace java com.test.thrift.demo
   
   
   

  

thrift也支持文件包含,至关于CPP中的include,Java中的import,使用关键字include:

include "global.thrift"
   
   
   

  

#///**/均可以做为thrift文件中的注释。

thrift提供两个关键字requiredoptional,分别用于表示对应的字段是必填的仍是可选的(推荐尽可能使用optional),以下所示:

struct People {
    1:required string name;
    2:optional i32 age;
}
   
   
   

  

thrift应用示例

本示例中咱们使用java编写thrift的服务端程序,使用python编写thrift的客户端程序。

首先定义thrift IDL文件

// data.thrift
namespace java thrift.generated
namespace py py.thrift.generated

typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String

// struct关键字用于定义结构体,至关于面向对象编程语言中的类
struct Person {
    // 至关于定义类中的成员,并生成相应的get和set方法,optional表示username这个成员能够没有
    1: optional String username,
    2: optional int age,
    3: optional boolean married
}

// 定义一个异常类型,用于接口中可能抛出的异常
exception DataException {
    1: optional String message,
    2: optional String callStack,
    3: optional String date
}

// 定义服务接口
service PersonService {
    Person getPersonByUsername(1: required String username) throws (1: DataException data),
    void savePerson(1: required Person person)
}
   
   
   

  

执行thrift --gen java src/thrift/data.thrift生成对应的java代码,并引入到Java工程当中,代码结构以下图所示

thrift示例代码结构

编写Java服务端代码,data.thrift的service中定义了两个服务,咱们须要定义相应服务的实现类(至关于handler),以下所示:

import thrift.generated.DataException;
import thrift.generated.Person;
import thrift.generated.PersonService;

public class PersonServiceImpl implements PersonService.Iface {
    @Override
    public Person getPersonByUsername(String username) throws DataException {
        System.out.println("Got Client Param: " + username);

        return new Person().setUsername(username).setAge(20).setMarried(false);
    }

    @Override
    public void savePerson(Person person) throws DataException {
        System.out.println("Got Client Param:");

        System.out.println(person.username);
        System.out.println(person.age);
        System.out.println(person.married);
    }
}
   
   
   

  

同时咱们须要借助thrift为咱们提供的类库实现一个服务器来监听rpc请求,代码以下所示:

import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import thrift.generated.PersonService;

public class ThriftServer {
    public static void main(String[] args) throws Exception {

        // 定义服务器使用的socket类型
        TNonblockingServerSocket tNonblockingServerSocket = new TNonblockingServerSocket(8899);

        // 建立服务器参数
        THsHaServer.Args arg = new THsHaServer.Args(tNonblockingServerSocket).minWorkerThreads(2).maxWorkerThreads(4);

        // 请求处理器
        PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());

        // 配置传输数据的格式
        arg.protocolFactory(new TCompactProtocol.Factory());
        // 配置数据传输的方式
        arg.transportFactory(new TFramedTransport.Factory());
        // 配置处理器用来处理rpc请求
        arg.processorFactory(new TProcessorFactory(processor));

        // 本示例中使用半同步半异步方式的服务器模型
        TServer server = new THsHaServer(arg);
        System.out.println("Thrift Server Started!");
        // 启动服务
        server.serve();
    }
}
   
   
   

  

编写python客户端,执行thrift --gen py src/thrift/data.thrift生成对应的python代码,并引入到python工程当中,代码结构以下图所示

thrift python代码结构

python客户端代码以下所示:

# -*- coding:utf-8 -*-
__author__ = 'kpzhang'

from py.thrift.generated import PersonService
from py.thrift.generated import ttypes

from thrift import Thrift
from  thrift.transport import TSocket
from  thrift.transport import TTransport
from  thrift.protocol import TCompactProtocol

import sys

reload(sys)
sys.setdefaultencoding('utf8')

try:
    tSocket = TSocket.TSocket("localhost", 8899)
    tSocket.setTimeout(900)

    transport = TTransport.TFramedTransport(tSocket)
    protocol = TCompactProtocol.TCompactProtocol(transport)
    client = PersonService.Client(protocol)

    transport.open()

    person = client.getPersonByUsername("张三")

    print person.username
    print person.age
    print person.married

    print '---------------------'

    newPerson = ttypes.Person();
    newPerson.username = "李四"
    newPerson.age = 30
    newPerson.married = True

    client.savePerson(newPerson)

    transport.close()

except Thrift.TException, tx:
    print '%s' % tx.message
   
   
   

  

客户端能够向调用本地的方法同样调用服务端的方法。

相关文章
相关标签/搜索