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依赖包文件作为中间件,通过封装/解封传输请求和回复