thrift编写规则,及常见问题

Thrift简单介绍

1.1、  Thrift是什么?能做什么?

Thrift是Facebook于2007年开发的跨语言的rpc服框架,提供多语言的编译功能,并提供多种服务器工作模式;用户通过Thrift的IDL(接口定义语言)来描述接口函数及数据类型,然后通过Thrift的编译环境生成各种语言类型的接口文件,用户可以根据自己的需要采用不同的语言开发客户端代码和服务器端代码。

例如,我想开发一个快速计算的RPC服务,它主要通过接口函数getInt对外提供服务,这个RPC服务的getInt函数使用用户传入的参数,经过复杂的计算,计算出一个整形值返回给用户;服务器端使用java语言开发,而调用客户端可以是java、c、python等语言开发的程序,在这种应用场景下,我们只需要使用Thrift的IDL描述一下getInt函数(以.thrift为后缀的文件),然后使用Thrift的多语言编译功能,将这个IDL文件编译成C、java、python几种语言对应的“特定语言接口文件”(每种语言只需要一条简单的命令即可编译完成),这样拿到对应语言的“特定语言接口文件”之后,就可以开发客户端和服务器端的代码了,开发过程中只要接口不变,客户端和服务器端的开发可以独立的进行。

Thrift为服务器端程序提供了很多的工作模式,例如:线程池模型、非阻塞模型等等,可以根据自己的实际应用场景选择一种工作模式高效地对外提供服务;

1.2、  Thrift的相关网址和资料:

(1)  Thrift的官方网站:http://thrift.apache.org/

(2)  Thrift官方下载地址:http://thrift.apache.org/download

(3)  Thrift官方的IDL示例文件(自己写IDL文件时可以此为参考):

https://git-wip-us.apache.org/repos/asf?p=thrift.git;a=blob_plain;f=test/ThriftTest.thrift;hb=HEAD

(4)  各种环境下搭建Thrift的方法:

http://thrift.apache.org/docs/install/

该页面中共提供了CentOS\Ubuntu\OS X\Windows几种环境下的搭建Thrift环境的方法。

二、  Thrift的使用

Thrift提供跨语言的服务框架,这种跨语言主要体现在它对多种语言的编译功能的支持,用户只需要使用IDL描述好接口函数,只需要一条简单的命令,Thrift就能够把按照IDL格式描述的接口文件翻译成各种语言版本。其实,说搭建Thrift环境的时候,实际上最麻烦的就是搭建Thrift的编译环境,Thrift的编译和通常的编译一样经过词法分析、语法分析等等最终生成对应语言的源码文件,为了能够支持对各种语言的编译,你需要下载各种语言对应的编译时使用的包;

1 编写IDL文件时需要注意的问题

[1]函数的参数要用数字依序标好,序号从1开始,形式为:“序号:参数名”;

[2]每个函数的最后要加上“,”,最后一个函数不加;

[3]在IDL中可以使用/*……*/添加注释

 2 IDL支持的数据类型

IDL大小写敏感,它共支持以下几种基本的数据类型:

[1]string, 字符串类型,注意是全部小写形式;例如:string aString

[2]i16, 16位整形类型,例如:i16 aI16Val;

[3]i32,32位整形类型,对应C/C++/java中的int类型;例如:      I32  aIntVal

[4]i64,64位整形,对应C/C++/java中的long类型;例如:I64 aLongVal

[5]byte,8位的字符类型,对应C/C++中的char,java中的byte类型;例如:byte aByteVal

[6]bool, 布尔类型,对应C/C++中的bool,java中的boolean类型; 例如:bool aBoolVal

[7]double,双精度浮点类型,对应C/C++/java中的double类型;例如:double aDoubleVal

[8]void,空类型,对应C/C++/java中的void类型;该类型主要用作函数的返回值,例如:void testVoid(),

除上述基本类型外,ID还支持以下类型:

[1]map,map类型,例如,定义一个map对象:map<i32, i32> newmap;

[2]set,集合类型,例如,定义set<i32>对象:set<i32> aSet;

[3]list,链表类型,例如,定义一个list<i32>对象:list<i32> aList;

 在Thrift文件中自定义数据类型

在IDL中支持两种自定义类型:枚举类型和结构体类型,具体如下:

[1]enum, 枚举类型,例如,定义一个枚举类型:

enum Numberz
{
  ONE = 1,
  TWO,
  THREE,
  FIVE = 5,
  SIX,
  EIGHT = 8
}
注意,枚举类型里没有序号

[2]struct,自定义结构体类型,在IDL中可以自己定义结构体,对应C中的struct,c++中的struct和class,java中的class。例如:

struct TestV1 {
       1: i32 begin_in_both,
       3: string old_string,
       12: i32 end_in_both
}
注意,在struct定义结构体时需要对每个结构体成员用序号标识:“序号: ”。

(4)  定义类型别名

Thrift的IDL支持C/C++中类似typedef的功能,例如:

typedefi32  Integer 

就可以为i32类型重新起个名字Integer。

2.4、  生成Thrift服务接口文件

搭建Thrift编译环境之后,使用下面命令即可将IDL文件编译成对应语言的接口文件:

thrift --gen <language> <Thrift filename>

 这里有以下几点需要说明:

[1]在同步方式使用客户端和服务器的时候,socket是被一个函数调用独占的,不能多个调用同时使用一个socket,例如通过m_transport.open()打开一个socket,此时创建多个线程同时进行函数调用,这时就会报错,因为socket在被一个调用占着的时候不能再使用;

[2]可以分时多次使用同一个socket进行多次函数调用,即通过m_transport.open()打开一个socket之后,你可以发起一个调用,在这个次调用完成之后,再继续调用其他函数而不需要再次通过m_transport.open()打开socket;

需要注意的问题

(1)Thrift的服务器端和客户端使用的通信方式要一样,否则便无法进行正常通信;

Thrift的服务器端的种模式所使用的通信方式并不一样,因此,服务器端使用哪种通信方式,客户端程序也要使用这种方式,否则就无法进行正常通信了。例如,上面的代码2.3中,服务器端使用的工作模式为TNonblockingServer,在该工作模式下需要采用的传输方式为TFramedTransport,也就是在通信过程中会将tcp的字节流封装成一个个的帧,此时就需要客户端程序也这么做,否则便会通信失败。出现如下问题:

服务器端会爆出如下出错log:

2015-01-06 17:14:52.365 ERROR [Thread-11] Read an invalid frame size of -2147418111. Are you using TFramedTransport on the client side?

应用技巧

(1)  为调用加上一个事务ID

在分布式服务开发过程中,一次事件(事务)的执行可能跨越位于不同机子上多个服务程序,在后续维护过程中跟踪log将变得非常麻烦,因此在系统设计的时候,系统的一个事务产生之处应该产生一个系统唯一的事务ID,该ID在各服务程序之间进行传递,让一次事务在所有服务程序输出的log都以此ID作为标识。

在使用Thrift开发服务器程序的时候,也应该为每个接口函数提供一个事务ID的参数,并且在服务器程序开发过程中,该ID应该在内部函数调用过程中也进行传递,并且在日志输出的时候都加上它,以便问题跟踪。

(2)  封装返回结果

Thrift提供的RPC方式的服务,使得调用方可以像调用自己的函数一样调用Thrift服务提供的函数;在使用Thrift开发过程中,尽量不要直接返回需要的数据,而是将返回结果进行封装,例如上面的例子中的getStr函数就是直接返回了结果string,见Thrift文件test_service.thrift中对该函数的描述:

在实际开发过程中,这是一种很不好的行为,在返回结果为null的时候还可能造成调用方产生异常,需要对返回结果进行封装,例如:

/*String类型返回结果*/
struct ResultStr
{
  1: ThriftResult result,
  2: string value
}
其中ThriftResult是自己定义的枚举类型的返回结果,在这里可以根据自己的需要添加任何自己需要的返回结果类型:

enum ThriftResult
{
  SUCCESS,           /*成功*/
  SERVER_UNWORKING,  /*服务器处于非Working状态*/
  NO_CONTENT,           /*请求结果不存在*/
  PARAMETER_ERROR,     /*参数错误*/
  EXCEPTION,          /*内部出现异常*/
  INDEX_ERROR,         /*错误的索引或者下标值*/
  UNKNOWN_ERROR,      /*未知错误*/
  DATA_NOT_COMPLETE,      /*数据不完全*/
  INNER_ERROR,      /*内部错误*/
}
 

此时可以将上述定义的getStr函数修改为:

ResultStr  getStr(1:string srcStr1, 2:string srcStr2)

在此函数中,任何时候都会返回一个ResultStr对象,无论异常还是正常情况,在出错时还可以通过ThriftResult返回出错的类型。

将服务与数据类型分开定义

在使用Thrift开发一些中大型项目的时候,很多情况下都需要自己封装数据结构,例如前面将返回结果进行封装的时候就定义了自己的数据类型ResultStr,此时,将数据结构和服务分开定义到不通的文件中,可以增加thrift文件的易读性。例如:

在thrift文件:thrift_datatype.thrift中定义数据类型,如:

namespace java com.browan.freepp.thriftdatatype
const string VERSION = "1.0.1"
/**为ThriftResult添加数据不完全和内部错误两种类型
*/

/****************************************************************************************************
* 定义返回值,
* 枚举类型ThriftResult,表示返回结果,成功或失败,如果失败,还可以表示失败原因
* 每种返回类型都对应一个封装的结构体,该结构体其命名遵循规则:"Result" + "具体操作结果类型",结构体都包含两部分内容:
* 第一部分为枚举类型ThriftResult变量result,表示操作结果,可以 表示成功,或失败,失败时可以给出失败原因
* 第二部分的变量名为value,表示返回结果的内容;
*****************************************************************************************************/
enum ThriftResult
{
  SUCCESS,           /*成功*/
  SERVER_UNWORKING,  /*服务器处于非Working状态*/
  NO_CONTENT,           /*请求结果不存在*/
  PARAMETER_ERROR,     /*参数错误*/
  EXCEPTION,          /*内部出现异常*/
  INDEX_ERROR,         /*错误的索引或者下标值*/
  UNKNOWN_ERROR      /*未知错误*/
  DATA_NOT_COMPLETE      /*数据不完全*/
  INNER_ERROR      /*内部错误*/
}

/*bool类型返回结果*/
struct ResultBool 
{
  1: ThriftResult result,
  2: bool value
}

/*int类型返回结果*/
struct ResultInt
{
  1: ThriftResult result,
  2: i32 value
}

/*String类型返回结果*/
struct ResultStr
{
  1: ThriftResult result,
  2: string value
}

/*long类型返回结果*/
struct ResultLong
{
  1: ThriftResult result,
  2: i64 value
}

/*double类型返回结果*/
struct ResultDouble
{
  1: ThriftResult result,
  2: double value
}

/*list<string>类型返回结果*/
struct ResultListStr 
{
  1: ThriftResult result,
  2: list<string> value
}

/*Set<string>类型返回结果*/
struct ResultSetStr 
{
  1: ThriftResult result,
  2: set<string> value
}

/*map<string,string>类型返回结果*/
struct ResultMapStrStr 
{
  1: ThriftResult result,
  2: map<string,string> value
}

由于在接口服务定义的thrift文件test_service.thrift中要用到对数据类型定义的thrift文件:thrift_datatype.thrift,因此需要在其文件前通过include把自己所使用的thrift文件包含进来,另外在使用其他thrift文件中定义的数据类型时要加上它的文件名,如:thrift_datatype.ResultStr

(4)  为Thrift文件添加版本号

在实际开发过程中,还可以为Thrift文件加上版本号,以方便对thrift的版本进行控制
原文链接:https://blog.csdn.net/houjixin/article/details/42778335

Guess you like

Origin blog.csdn.net/weixin_43841155/article/details/119913520