Thrift入门与实例

Thrift

定义

Thrift是属于跨语言开发的软件服务框架,集成代码生成引擎,高效无缝连接C/Java/Python/PHP/Ruby/Erlang/Perl/Haskell/C#/Cocoa/JavaScript/Node.js/Smalltakl以及OCaml等语言

定位

1.跨语言的通信桥梁
2.远程过程调用框架

Thrift数据类型

Thrift支持类型
基本类型/用户定义结构/容器/Exceptions/Service

基本类型

bool: 布尔类型 (true or false), one byte
byte: 8位有符号整数
i16: 16位有符号整数
i32: 32位有符号整数
i64: 64位有符号整数
double: 64位浮点数
binary: 二进制
string: 文本字符串或UTF-8编码

附:Thrift不支持无符号整型,因为大多数Thrift目标语言不支持无符号整型直接转化为基本类型

容器
支持主流类型
list => 列表,有序的,元素可重复
set => 集合,无序的,元素不可重复
map => 键值对,Key: Value

用户定义结构/Exception
类似C语言的struct,Structs将自动转换成面向对象编程(oop)类型

struct Location { 
    1: required double latitude;
    2: required double longitude;
}

struct Tweet {
    1: required i32 userId;
    2: required string userName;
    3: required string text;
    4: optional Location loc;
    16: optional string language = "english"
}

附:Exception类型和Struct差异仅存在关键字,替换struct为exception即可

Service
类似面向对象编程(oop)接口定义(或抽象类)

service Twitter {
    // 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 list are specified using the exact same syntax as
    // field lists in structs.
    void ping(),
    bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable),
    TweetSearchResult searchTweets(1:string query);

    // The 'oneway' modifier indicates that the client only makes a request and
    // does not wait for any response at all. Oneway methods MUST be void.
    oneway void zip()
}

扩展功能
1.支持C/C++类型的typedef,定义别名(包括struct)

typedef i32 MyInteger
typedef Tweet ReTweet

2.支持枚举(常量必须在32位整理范围内)

enum TweetType {
    TWEET,
    RETWEET = 2,
    DM = 0xa,
    REPLY
}
struct Tweet {
    1: required i32 userId;
    2: required string userName;
    3: required string text;
    4: optional Location loc;
    5: optional TweetType tweetType = TweetType.TWEET
    6: optional string language = "english"
}

3.注释方式支持shell/C/C++/Java

# This is a valid comment.

/*
 * This is a multi-line comment.
 * Just like in C.
 */

// C++/Java style single-line comments work just as well.
传输协议

Thrift在客户端和服务器交互可以选择通信协议,总体可分为Text和Binary两种

TBinaryProtocol      二进制编码格式进行数据传输
TCompactProtocol     高效率的、密集的二进制编码格式进行数据传输
TJSONProtocol        使用 JSON 的数据编码协议进行数据传输
TSimpleJSONProtocol  只提供 JSON 只写的协议,适用于通过脚本语言解析
传输方式
TSocket           使用堵塞式 I/O 进行传输,也是最常见的模式
TFramedTransport  使用非阻塞方式,按块的大小,进行传输,类似于 Java 中的NIO
TFileTransport    按照文件的方式进程传输,虽然这种方式不提供 Java 的实现,但是实现起来非常简单
TMemoryTransport  使用内存 I/O,就好比 Java 中的 ByteArrayOutputStream实现
TZlibTransport    使用执行 zlib 压缩,不提供 Java 的实现
服务器类型
TSimpleServer            单线程服务模型,一般用于测试
TThreadPoolServer        多线程服务器端使用标准的阻塞式 I/O
TNonblockingServer       多线程服务器端使用非阻塞式 I/O
传输过程

这里写图片描述
1.将客户端程序调用的函数名和参数传递给协议层(TProtocol),协议层将函数名和参数按照协议格式进行封装,然后封装的结果交给下层的传输层。此处需要注意:要与Thrift服务器程序所使用的协议类型一样,否则Thrift服务器程序便无法在其协议层进行数据解析;
2.传输层(TTransport)将协议层传递过来的数据进行处理,例如传输层的实现类TFramedTransport就是将数据封装成帧的形式,即“数据长度+数据内容”,然后将处理之后的数据通过网络发送给Thrift服务器;此处也需要注意:要与Thrift服务器程序所采用的传输层的实现类一致,否则Thrift的传输层也无法将数据进行逆向的处理;
3.Thrift服务器通过传输层(TTransport)接收网络上传输过来的调用请求数据,然后将接收到的数据进行逆向的处理,例如传输层的实现类TFramedTransport就是将“数据长度+数据内容”形式的网络数据,转成只有数据内容的形式,然后再交付给Thrift服务器的协议类(TProtocol);
4.Thrift服务端的协议类(TProtocol)将传输层处理之后的数据按照协议进行解封装,并将解封装之后的数据交个Processor类进行处理;
5.Thrift服务端的Processor类根据协议层(TProtocol)解析的结果,按照函数名找到函数名所对应的函数对象;
6.Thrift服务端使用传过来的参数调用这个找到的函数对象;
7.Thrift服务端将函数对象执行的结果交给协议层;
8.Thrift服务器端的协议层将函数的执行结果进行协议封装;
9.Thrift服务器端的传输层将协议层封装的结果进行处理,例如封装成帧,然后发送给Thrift客户端程序;
10.Thrift客户端程序的传输层将收到的网络结果进行逆向处理,得到实际的协议数据;
11.Thrift客户端的协议层将数据按照协议格式进行解封装,然后得到具体的函数执行结果,并将其交付给调用函数;

务必保证Client与Server两端协议层/传输层实现类一致,才能保证数据的正确封装和解封

Erlang实例

定义calculator.thrift文件

service Calculator{
    i32 add(1:i32 N1, 2:i32 N2)
}

生成erl文件,执行一下命令将生成gen-erl文件夹

thrift --gen erl calculator.thrift

附1:
[1]函数的参数要用数字依序标好,序号从1开始,形式为:“序号:参数名”;
[2]每个函数的最后要加上“,”,最后一个函数不加;
[3]在IDL中可以使用/……/添加注释
附2:
建议将数据类型定义和服务接口分开定义,以便于易读
例如:thrift_datatype.thrift/test_service.thrift(记得在test_service.thrift引用类型定义 include “thrift_datatype.thrift”,并且在调用定义类型的时候添加前缀thrift_datatype.xxx)

定义server.erl文件,服务器开发

-include("calculator_thrift.hrl").

-export([start/0, start/1, handle_function/2,
         stop/1, add/2]).

debug(Format, Data) ->
    error_logger:info_msg(Format, Data).

add(N1, N2) ->
    debug("add(~p,~p)",[N1,N2]),
    N1+N2.
%%

start() ->
    start(9090).

start(Port) ->
    Handler   = ?MODULE,
    thrift_socket_server:start([{handler, Handler},
                                {service, calculator_thrift},
                                {port, Port},
                                {name, tutorial_server}]).

stop(Server) ->
    thrift_socket_server:stop(Server).

handle_function(Function, Args) when is_atom(Function), is_tuple(Args) ->
    case apply(?MODULE, Function, tuple_to_list(Args)) of
        ok -> ok;
        Reply -> {reply, Reply}
    end.

定义client,erl文件,客户端开发

-module(client).

-include("calculator_thrift.hrl").

-export([t/0]).

p(X) ->
    io:format("~p~n", [X]),
    ok.

t() ->
    Port = 9090,

    {ok, Client0} = thrift_client_util:new("127.0.0.1",
                                           Port,
                                           calculator_thrift,
                                           []),

    {Client2, {ok, Sum}} = thrift_client:call(Client0, add,  [1, 1]),
    io:format("1+1=~p~n", [Sum]),

    {Client3, {ok, Sum1}} = thrift_client:call(Client2, add, [1, 4]),
    io:format("1+4=~p~n", [Sum1]),

    {_Client4, ok} = thrift_client:close(Client3),
    ok.

附:记得将thrift依赖包,编译后的beam文件路径添加到erl节点
实例执行

Server:
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V7.3  (abort with ^G)
1> server:start().
{ok,<0.36.0>}

=INFO REPORT==== 10-Oct-2017::19:13:46 ===
thrift service listening on port 90902> 
=INFO REPORT==== 10-Oct-2017::19:14:13 ===
add(1,1)
=INFO REPORT==== 10-Oct-2017::19:14:13 ===
add(1,4)
=ERROR REPORT==== 10-Oct-2017::19:14:13 ===
{thrift_socket_server,244,{child_error,undef,[]}}


Client:
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V7.3  (abort with ^G)
1> client:t().
1+1=2
1+4=5
ok
经验总结

1.编写接口定义文件.thrift,通过执行
thrift --gen <language> <Thrift filename>
生成接口说明文件(erl => function_info)
2.服务器实现接口处理方式(erl =>调用handle_function处理请求)
3.thrift依赖包文件作为中间件,通过封装/解封传输请求和回复

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值