Thrift框架干货总结一:总体认识

一、thrift基本点介绍

1、是一个通信框架,跨语言(支持28种编程语言),跨平台(win、linux不在话下)。

2、灵活(IPC、RPC方式),实现通信的底层可以是通过文件实现通信、通过内存共享实现通信、通过管道实现通信、通过TCP/IP实现通信等等,废话不多说,看下图感受一下灵活的支持程度吧。

Thrift框架干货总结一:总体认识_第1张图片

二、语言参考

1、类型系统

Thrift类型系统由预定义的基本类型,用户定义的结构,容器类型,异常和服务定义组成。

基本类型

  • bool:布尔值(true or false),一个字节

  • byte:一个有符号的字节

  • i16:16位有符号整数

  • i32:32位有符号整数

  • i64:64位有符号整数

  • double:64位浮点数

  • binary:一个字节数组

  • string:编码不可知的文本或二进制字符串

注意,Thrift不支持无符号整数,因为在许多Thrift的目标语言中,它们都无法直接转换为本机(原始)类型。

容器类型

容器是强类型的容器,它们以流行的编程语言映射到最常用的容器。它们使用Java泛型样式进行注释。共有三种容器类型:

  • list :类型为t1的元素的有序列表。可能包含重复项。

  • set :类型为t1的无序唯一元素集。

  • map :类型为t1的严格唯一键到类型为t2的值的 映射。

容器中使用的类型可以是任何有效的Thrift类型(包括结构和异常),但服务除外。

结构和异常

Thrift结构在概念上类似于C结构。

在语法上和功能上,异常与结构等效,不同之处在于使用异常关键字而不是struct关键字声明了异常。它们在语义上不同于结构-在定义RPC服务时,开发人员可能会声明远程方法引发异常。

服务(其实就是提供函数接口的抽象类)

服务定义在语义上等效于在面向对象的编程中定义接口(或纯虚拟抽象类)。Thrift编译器生成实现该接口的功能齐全的客户端和服务器存根。

2、类型定义

Thrift支持C / C ++样式typedef。

typedef i32 MyInteger   // 请注意,没有结尾的分号
typedef Tweet ReTweet   // 结构也可以在typedef中使用

3、枚举

enum TweetType {
    TWEET,       // 枚举指定为C样式。编译器分配从0开始的默认值。
    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 // 指定默认值时需使用完全限定名称。
    16: optional string language = "english"
}

注意,与协议缓冲区不同,Thrift还不支持嵌套枚举(就此而言,还是结构)。

枚举器常数必须在 32位整数范围内。

4、注释

# 这是有效的注释。

/*
 * 这是多行注释。
 * 就像在C中一样。
 */

//  C ++ / Java样式的单行注释也可以正常工作。

5、命名空间

Thrift允许基于每种语言自定义名称空间行为(类似于C ++中的命名空间、Java中的程序包、Python的模块):

namespace cpp com.example.project  // 转换为命名空间com.example.project
namespace java com.example.project // 转换为com.example.project包

6、Include

include "tweet.thrift"           // 文件名必须加引号;再次注意缺少分号。
...
struct TweetSearchResult {
    1: list tweets; // 注意tweet前缀。
}

7、常数

Thrift可让您定义用于多种语言的常量。复杂类型和结构使用JSON表示法指定。

const  i32  INT_CONST  =  1234 ;     // 分号可以不用,十六进制值在这里有效。
const  map < string ,string >  MAP_CONST  =  { “ hello” : “ world” , “ goodnight” : “ moon” }

8、定义结构

struct Location {                            // 可以在同一个Thrift文件中定义和引用多个结构
    1: required double latitude;
    2: required double longitude;
}

struct Tweet {
    1: required i32 userId;                  // 每个字段必须具有唯一的正整数标识符
    2: required string userName;             // 字段可以标记为required 或optional 
    3: required string text;
    4: optional Location loc;                // 结构可能包含其他结构
    16: optional string language = "english" // 指定一个可选的“默认”值
}

9、定义服务

尽管有多种流行的序列化/反序列化框架(如协议缓冲区),但很少有框架像thrift这样为跨多种语言的基于RPC的服务提供现成的支持。

服务可理解为一个抽象类,定义了一套抽象的函数接口。

Thrift编译器将以您选择的语言生成服务接口代码(针对服务器)和存根(针对客户端)。Thrift附带了RPC库,支持大多数语言,您可以使用它们来运行客户端和服务器。

service Twitter {
    // 方法定义看起来像C代码。它具有返回类型,参数,和(可选)可能抛出的异常列表。
    // 请注意,参数列表和异常列表是使用与结构中的字段列表完全相同的语法指定的。
    void ping(),                                                             // 方法定义可以使用逗号或分号终止
    bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable), // 参数可以是原始类型或结构
    TweetSearchResult searchTweets(1:string query);                          // 同样对于返回类型

    // oneway 修饰符表示客户端仅发出请求,但不等待任何响应。oneway方法必须void返回。
    oneway void zip()                                                        
}

注意,函数的参数列表(和异常列表)的指定与结构完全相同。

服务支持继承:服务可以选择使用extends关键字从另一个服务继承。

三、生成的代码

程序员可根据需要由Thrift生成的对应的目标语言代码。通用概念控制所生成代码的结构。

(一)概念

Thrift网络栈:

+ ------------------------------------------- +
| cGRE |
| 服务器|
| (单线程,事件驱动等)|
+ ------------------------------------------- +
| cBLU |
| 处理器|
| (编译器生成)|
+ ------------------------------------------- +
| cGRE |
| 协议|
| (JSON,紧凑型等)|
+ ------------------------------------------- +
| cGRE |
| 传输|
| (原始TCP,HTTP等)|
+ ------------------------------------------- +

1、传输

传输层为从网络读取/向网络写入提供了简单的抽象。这使Thrift可以将基础传输与系统的其余部分解耦(例如,序列化/反序列化)。以下是传输接口公开的一些方法:

 

  • open

  • close

  • read

  • write

  • flush

除了上面的传输接口之外,Thrift还使用 ServerTransport接口来接受或创建原始传输对象。顾名思义,ServerTransport主要用于服务器端,为传入的连接创建新的传输对象。

  • open

  • listen

  • accept

  • close

以下是大多数Thrift支持的语言可用的传输方式:

  • file: read/write to/from a file on disk

  • http: as the name suggests

2、协议

协议抽象定义了一种将内存中的数据结构映射为序列格式的机制。换句话说,协议指定数据类型如何使用基础传输来对其自身进行编码/解码。因此,协议实现控制编码方案,并负责序列化、反序列化。从这个意义上来说,协议的一些示例包括JSON,XML,纯文本,紧凑二进制等。协议接口有:

writeMessageBegin(name, type, seq)
writeMessageEnd()
writeStructBegin(name)
writeStructEnd()
writeFieldBegin(name, type, id)
writeFieldEnd()
writeFieldStop()
writeMapBegin(ktype, vtype, size)
writeMapEnd()
writeListBegin(etype, size)
writeListEnd()
writeSetBegin(etype, size)
writeSetEnd()
writeBool(bool)
writeByte(byte)
writeI16(i16)
writeI32(i32)
writeI64(i64)
writeDouble(double)
writeString(string)

name, type, seq = readMessageBegin()
                  readMessageEnd()
name = readStructBegin()
       readStructEnd()
name, type, id = readFieldBegin()
                 readFieldEnd()
k, v, size = readMapBegin()
             readMapEnd()
etype, size = readListBegin()
              readListEnd()
etype, size = readSetBegin()
              readSetEnd()
bool = readBool()
byte = readByte()
i16 = readI16()
i32 = readI32()
i64 = readI64()
double = readDouble()
string = readString()

协议通过设计面向流。不需要任何明确的框架。例如,在开始序列化字符串之前,不必知道字符串的长度或列表中的项目数。

大多数Thrift支持的语言可用的协议:

  • binary:非常简单的二进制编码-字段的长度和类型被编码为字节,然后是字段的实际值。

  • compact紧凑型,描述于 THRIFT-110

  • json:

3、处理器

处理器封装了从输入流读取数据并写入输出流的功能。输入和输出流由协议对象表示。处理器接口非常简单:

interface TProcessor {
    bool process(TProtocol in, TProtocol out) throws TException
}

特定于服务的处理器实现由编译器生成。处理器本质上是从序列读取数据(使用输入协议),将处理委托给处理程序(由用户实现),并通过序列写入响应(使用输出协议)。

4、服务器

服务器将上述所有各种功能汇总在一起:

  • 创建传输

  • 创建用于传输的输入/输出协议

  • 根据输入/输出协议创建处理器

  • 等待传入的连接并将其交给处理器

 

接下来,我们讨论为特定语言生成的代码。除非另有说明,否则以下各节将采用以下Thrift规范:

Example IDL

namespace cpp thrift.example
namespace java thrift.example

enum TweetType {
    TWEET,
    RETWEET = 2,
    DM = 0xa,
    REPLY
}

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;
    5: optional TweetType tweetType = TweetType.TWEET;
    16: optional string language = "english";
}

typedef list TweetList

struct TweetSearchResult {
    1: TweetList tweets;
}

exception TwitterUnavailable {
    1: string message;
}

const i32 MAX_RESULTS = 100;

service Twitter {
    void ping(),
    bool postTweet(1:Tweet tweet) throws (1:TwitterUnavailable unavailable),
    TweetSearchResult searchTweets(1:string query);
    oneway void zip()
}

嵌套结构如何初始化?

对于嵌套结构,一种合理的方法是将嵌套结构视为指针或引用,并使用NULL对其进行初始化,直到用户明确设置为止。但对于许多语言,Thrift使用按值传递模型,考虑上面示例中为Tweet结构生成的C ++代码:

  ...
  int32_t userId;
  std::string userName;
  std::string text;
  Location loc;
  TweetType::type tweetType;
  std::string language;
  ...

嵌套的Location结构loc完全inline分配的。因为Location是可选的,所以代码使用内部__isset标志来确定该字段是否实际上已由用户“设置”。由此会导致一些问题,如下:

  • 由于可能会在初始化时以某些语言分配每个子结构的全部大小,因此内存使用可能比您预期的要高,尤其是对于具有许多未设置字段的复杂结构。

  • 服务方法的参数和返回类型可能不是“可选”,并且您不能以任何动态语言分配或返回null。因此,要从方法返回“无值”结果,必须声明一个包含可选字段的信封结构,该字段包含该值,然后返回未设置该字段的信封。

  • 但是,传输层可以在缺少参数的情况下,从服务定义的较早版本中调度方法调用。因此,如果原始服务包含方法postTweet(1:Tweet tweet),而更高版本将其更改为postTweet(1:Tweet tweet,2:string group),则较旧的客户端调用先前的方法将导致新的服务器接收到未设置新参数的调用。例如,如果新服务器使用Java,则实际上您可能会收到新参数的值。但是,您可能尚未在IDL中声明参数为可空值。

(二)JAVA

1、生成的文件

包含所有常量定义 的单个文件(Constants.java)+每个文件对应一个结构,枚举和服务。

$ tree gen-java
`-- thrift
    `-- example
        |-- Constants.java
        |-- Location.java
        |-- Tweet.java
        |-- TweetSearchResult.java
        |-- TweetType.java
        `-- Twitter.java

2、类型

Thrift将各种基本类型和容器类型映射为Java类型。

bool: boolean

binary: byte[]

byte: byte

i16: short

i32: int

i64: long

double: double

string: String

list: List

set: Set

map: Map

3、类型定义

Java语言没有对“ typedefs”的任何本机支持。因此,当Thrit Java代码生成器遇到typedef声明时,它仅将其替换为原始类型。也就是说,即使你可能有typedefd 类型A类型B,在生成的Java代码,所有引用 的TypeB将被替换类型A

4、枚举

Thrift枚举映射到Java 枚举类型。您可以使用getValue方法(通过接口TEnum)获取枚举的数值。另外,编译器生成findByValue方法以获得与数值对应的枚举。这比使用Java枚举的序数功能更强大。

5、常量

Thrift将所有定义的常量放入名为Constants的公共类中,作为public static final成员。支持任何原始类型的常量。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(C/C++语言,框架心得)