thrift快速入门教程

小TOT 创建于 2017-02-24

Thrift简介

Thrfit是由 Facebook 开发的远程服务调用(rpc)框架 Apache Thrift,它采用接口描述语言定义并创建服务,支持可扩展的跨语言服务开发,所包含的代码生成引擎可以在多种语言中,如 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk 等创建高效的、无缝的服务,其传输数据采用二进制格式,相对 XML 和 JSON 体积更小,对于高并发、大数据量和多语言的环境更有优势。该文章旨在帮助读者快速的了解thrift的使用。

thrift 编译器安装

  • windows平台安装
    windows平台由于编译安装会比较麻烦,考虑到大多数开发人员没有c++的编译环境,因此官方提供了二进制可执行文件,见下载页面。为了可以方便的运用thrift命令,我们可以将thrift-xxx.exe所在的目录加入到PATH变量中,或者索性将该文件拷贝到c://windows/目录下,并将文件名称改为thrift.exe。运行cmd,执行thrift -v命令查看thrift编译器版本。若成功提示版本信息,则配置成功。
  • linux平台安装
    官方没有提供linux平台下的二进制包,而是以源码的形式提供下载,供开发人员自己编译安装。对于ubuntu系统,官方软件仓库提供了二进制的软件包,你可以尝试 apt search thrift进行搜索,不过ubuntu软件仓库的的版本比较老了,如果你要尝试最新的版本,最好还是使用编译的方式安装。详细的编译安装步骤见官方文档。官方文档详细的介绍了各大平台的安装方式。由于是编译安装,可能会因环境不同而出现不同的异常,这个时候就要根据异常信息一步一步的解决了。
  • mac平台安装
    mac平台相对于linux好好些,因为mac平台有很多的第三方开发软件库,比如,macport,brew。一般来讲,对于像thrift这种蛮流行的工具,还是有收录的。笔者的mac安装thrft就是用的macport,和bubuntu一样,版会落后于官方,但较编译安装还是要简单许多,因此如果嫌麻烦的朋友可以使用这种方式进行安装。如果你更加乐意使用编译安装,参照官方文档

thrift 定义文件书写

thrift的的接口描述语言(IDL)以thrift为扩展名,目前大部分的ide都已经支持对thrift文件的编写,因此编写thrift的idl文件还是比较简单的。现在我们就先来看看一份简单的IDL文件来了解thrift文件的编写。该文件来自官方教程


/**
 * The first thing to know about are types. The available types in Thrift are:
 *
 *  bool        Boolean, one byte
 *  i8 (byte)   Signed 8-bit integer
 *  i16         Signed 16-bit integer
 *  i32         Signed 32-bit integer
 *  i64         Signed 64-bit integer
 *  double      64-bit floating point value
 *  string      String
 *  binary      Blob (byte array)
 *  map<t1,t2>  Map from one type to another
 *  list<t1>    Ordered list of one type
 *  set<t1>     Set of unique elements of one type
 *
 * Did you also notice that Thrift supports C style comments?
 */

// Just in case you were wondering... yes. We support simple C comments too.

/**
 * Thrift files can reference other Thrift files to include common struct
 * and service definitions. These are found using the current path, or by
 * searching relative to any paths specified with the -I compiler flag.
 *
 * Included objects are accessed using the name of the .thrift file as a
 * prefix. i.e. shared.SharedObject
 */
include "shared.thrift"

/**
 * Thrift files can namespace, package, or prefix their output in various
 * target languages.
 */
namespace cpp tutorial
namespace d tutorial
namespace dart tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial
namespace haxe tutorial
namespace netcore tutorial

/**
 * Thrift lets you do typedefs to get pretty names for your types. Standard
 * C style here.
 */
typedef i32 MyInteger

/**
 * Thrift also lets you define constants for use across languages. Complex
 * types and structs are specified using JSON notation.
 */
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}

/**
 * You can define enums, which are just 32 bit integers. Values are optional
 * and start at 1 if not supplied, C style again.
 */
enum Operation {
  ADD = 1,
  SUBTRACT = 2,
  MULTIPLY = 3,
  DIVIDE = 4
}

/**
 * Structs are the basic complex data structures. They are comprised of fields
 * which each have an integer identifier, a type, a symbolic name, and an
 * optional default value.
 *
 * Fields can be declared "optional", which ensures they will not be included
 * in the serialized output if they aren't set.  Note that this requires some
 * manual management in some languages.
 */
struct Work {
  1: i32 num1 = 0,
  2: i32 num2,
  3: Operation op,
  4: optional string comment,
}

/**
 * Structs can also be exceptions, if they are nasty.
 */
exception InvalidOperation {
  1: i32 whatOp,
  2: string why
}

/**
 * Ahh, now onto the cool part, defining a service. Services just need a name
 * and can optionally inherit from another service using the extends keyword.
 */
service Calculator extends shared.SharedService {

  /**
   * A method definition looks like C code. It has a return type, arguments,
   * and optionally a list of exceptions that it may throw. Note that argument
   * lists and exception lists are specified using the exact same syntax as
   * field lists in struct or exception definitions.
   */

   void ping(),

   i32 add(1:i32 num1, 2:i32 num2),

   i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),

   /**
    * This method has a oneway modifier. That means the client only makes
    * a request and does not listen for any response at all. Oneway methods
    * must be void.
    */
   oneway void zip()

}

shared.thrift:


namespace cpp shared
namespace d share // "shared" would collide with the eponymous D keyword.
namespace dart shared
namespace java shared
namespace perl shared
namespace php shared
namespace haxe shared
namespace netcore shared

struct SharedStruct {
  1: i32 key
  2: string value
}

service SharedService {
  SharedStruct getStruct(1: i32 key)
}

如果读者详细的看过以上的的文件内容,相信你对thrift的idl文件有了一个大概的认识。需要说明的是,thrift的service支持继承extends,但是struct不支持继承,见trhift的功能。完成idl文件编译之后就可以使用thrfit编译器生成相应的代码了:

thrift --gen <language> <Thrift filename>

maven工程thrift代码生成插件配置

为什么要使用配置thrift插件,前面已经提到使用thrift -gen <lan> <idl>既能生成代码,然后将代码拷到项目中即可使使用。使用maven插件可以省去这一繁琐的步骤,并且项目中不用维护java代码,只用维护idl即可,这使得项目结构更轻量。

mvaen thrift配置示例:

 <!-- thrift 文件自动生成的插件 -->
            <plugin>
                <groupId>org.apache.thrift.tools</groupId>
                <artifactId>maven-thrift-plugin</artifactId>
                <version>0.1.11</version>
                <configuration>
                    <thriftExecutable>${thrift.exe.dir}</thriftExecutable>
                    <generator>java:bean</generator>
                    <outputDirectory>${basedir}/src/main/thrift-gen</outputDirectory>
                    <thriftSourceRoot>${basedir}/src/main/resources</thriftSourceRoot>
                </configuration>
                <executions>
                    <execution>
                        <id>thrift-sources</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>thrift-test-sources</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>1.7</version>
                <executions>
                    <execution>
                        <id>add-source</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <!-- 我们可以通过在这里添加多个source节点,来添加任意多个源文件夹 -->
                                <source>${basedir}/src/main/thrift-gen</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

以上主要配置了两个插件第一个是,thrft编译插件,第二个是指定源文件目录插件。thrift生成的代码会依赖到thrfit的lib文件。因此还需要在依赖中添加如下依赖:

<!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.9.2</version>
</dependency>

服务端代码

thrift的远程调用方法通过IDL中的service来定义,比如以上配置的service Calculator服务。服务端需要实现该服务定义的方法,下面我们就来实现以下我们之前定义的Caculator服务接口。

/**
 * Created by guest on 17/2/24.
 */
public class CalculatorHandler implements Calculator.Iface {
    @Override
    public SharedStruct getStruct(int key) throws TException {
        return new SharedStruct(1,"ok");
    }

    @Override
    public void ping() throws TException {
        System.out.print("get the ping ====>");
    }

    @Override
    public int add(int num1, int num2) throws TException {
        System.out.println("addd=====>");
        return num1 + num2;
    }

    @Override
    public int calculate(int logid, Work w) throws InvalidOperation, TException {
        return 0;
    }

    @Override
    public void zip() throws TException {
        System.out.println("zip method");
    }
}

实现了接口后我们需要配置服务端的服务:

public class ThriftServer {
    public static CalculatorHandler handler;

    public static Calculator.Processor processor;

    public static void main(String [] args) {
        try {
            handler = new CalculatorHandler();
            processor = new Calculator.Processor(handler);

            Runnable simple = new Runnable() {
                public void run() {
                    simple(processor);
                }
            };
            new Thread(simple).start();
        } catch (Exception x) {
            x.printStackTrace();
        }
    }

    public static void simple(Calculator.Processor processor) {
        try {
            TServerTransport serverTransport = new TServerSocket(9090);
            TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));

            // Use this for a multithreaded server
            // TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(serverTransport).processor(processor));

            System.out.println("Starting the simple server...");
            server.serve();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客服端代码调用配置

客服端调用代码:

public class ThriftClient {
    public static void main(String[] args) {
        TTransport transport;
        try {
            transport = new TSocket("localhost", 9090);
            transport.open();

            TProtocol protocol = new TBinaryProtocol(transport);
            Calculator.Client client = new Calculator.Client(protocol);

            //使用client调用接口方法
            // add 方法
            int add = client.add(1, 5);
            System.out.println("the add result===>"+add);

            //关闭连接

            transport.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

运行服务端代码后,运行客服端代码调用服务端服务,得到如下输出:

server console:

Starting the simple server...
addd=====>

client console:

the add result===>6

总结

本文简单介绍了Thrfit框架的使用,旨在帮助大家快速的运行第一个基于thrift的远程调用程序。文中的例子来源于官方文档,做了一些调整,删去了基于SLL的远程调用,为的是最简化用例。例子虽然简单,单涵盖了使用thrift的所有步骤:

  1. 定义thrift IDL文件
  2. 生成响应语言的代码
  3. 配置Thrift服务端
  4. 客服端配置
  5. 客服端调用接口服务

可以看到thrift的使用还是比较简单的,并且3,4步在不配置新的接口服务时不需要重复做,一旦配置完毕,后续的开放只需要关注接口的定义,实现,和使用。本文章只介绍了thrift最基本的使用。thrift其他一些高级的特性在后面文章再做介绍。

引用

  1. thrift java示例
  2. thrift的主要功能
  3. thrift下载
  4. thrift各平台的安装