一、简介
首先还是那句话,概念网上已经很多了,我们就不多逼逼了。我来大致介绍一下。
- Thrift是一个RPC框架
- 可以进行异构系统(服务的提供者 和 服务的调用者 不同编程语言开发系统)的RPC调用
- 为什么在当前的系统开发中,会存在着异构系统的RPC?
- 存在异构系统的调用
Python ------> Hbase(Java)
thrift - 遗留系统的整合
- 存在异构系统的调用
- 设计一个异构系统的RPC 解决的核心问题是什么?
肯定:异构系统的RPC 没有问题 只要双方 用各自的编程语言实现 网络编程中(client server)建立网络连接,进行通信即可。
挑战:
1. 得需要精通不同编程语言的网络 IO 线程等相关技术内容 (java go python c delphi…)
2. 通信数据的格式,尤其是二进制的格式,得统一处理(中间的格式)
而thrift就是解决以上问题,作为rpc的框架封装出来给开发使用。
于是我们可以来看一下他的一些定义和性质特点。
1. 基本概念:是apache组织开源的一个顶级异构系统RPC框架,用于完成异构系统的PRC通信。
多种编程语言 Java C++ PHP Phyton Ruby Node.js c# ....
2007 FaceBook Thrift 开源。
2. 特点:
1. 跨语言支持
2. 开发快速
3. 学习简单 IDL语言
4. 稳定
3. Thrift的设计思想
1. 针对于不同的编程语言提供了一个库(jar)
作用:网络通信的代码 协议(序列化)相关的内容 java libthrift
2. IDL语言 中立语言 用于服务发布
3. Thrift命令把IDL语言 自动转换成 你需要的编程语言。
所以我们现在来总结一下,他就是个rpc框架,并且提供了多语言的异构,我们只需要编写对应的IDL中间语言文件.thrift文件,然后使用thrift命令,把这个idl中间文件编译成为目标语言的文件,这样就生成了对应的类。我们暂时先不说rpc的问题,这个放在我们的实践篇来说。
二、thrift的使用
1、ThriftRPC安装
我们需要去参考一下thrift官方文档,我们看一下其中的安装操作。
这个安装操作安装的就是把IDL转换成具体编程语言代码的命令。你可以理解为一个编译器。
我们可以看到他提供了多种操作系统的安装方式。
我们以mac为例,那就很简单了。
brew install thrift
完成安装即可
thrift -help
thrift --version
以上两个命令都可以用来验证是否安装成功。
因为我们是java环境,我们在java环境中开发的,所以我们需要安装一个库,针对于不同的编程语言 安装库
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.18.0</version>
</dependency>
如果你是python那就pip install thrift ,每种语言都有自己的包管理方式。这里就不多说了。
然后我们还需要在idea中安装一下thrift的插件支持。
这个插件可以支持我们在idea中进行相应的idl开发。
2、IDL语法
我们上面说了,thrift对于多语言异构的支持是我们编写一份.thrift文件,这个文件称之为IDL,然后使用对应的命令来翻译成目标语言的文件,可见其核心就在这个IDL上,我们来看一下如何编写IDL的语法。
我们的目的是通过在thrift文件中定义相关的信息,然后通过thrift命令生成各种语言的代码,这里我们以java为例。
2.1、包路径:
namespace java com.levi.thrift // 定义java语言的包路径
namespace go levi.thrift // 定义go语言的包路径
namespace py levi.thrift // 定义python语言的包路径
namespace php levi.thrift // 定义php语言的包路径
namespace csharp levi.thrift // 定义c#语言的包路径
namespace cpp levi.thrift // 定义c++语言的包路径
namespace rb levi.thrift // 定义ruby语言的包路径
namespace perl levi.thrift // 定义perl语言的包路径
namespace nodejs levi.thrift // 定义nodejs语言的包路径
namespace lua levi.thrift // 定义lua语言的包路径
namespace dart levi.thrift // 定义dart语言的包路径
namespace rust levi.thrift // 定义rust语言的包路径
namespace swift levi.thrift // 定义swift语言的包路径
namespace kotlin levi.thrift // 定义kotlin语言的包路径
namespace typescript levi.thrift // 定义typescript语言的包路径
2.2、基本类型:
i8: 有符号8位整数类型 在java中就是byte 下面都是java的类型,其余的语言有自己的
i16: 有符号16位整数类型 在java中就是short
i32: 有符号32位整数类型 在java中就是int
i64: 有符号64位整数类型 在java中就是long
double: 64位浮点数类型 在java中就是double
string: 字符串类型 在java中就是String 在所有语言中就是UTF-8编码的字符串 可以是单引号也可以是双引号
binary: 二进制类型 在java中就是byte[]
void: 空类型 在java中就是void
2.3、集合类型:
list<T> 有序可重复类型 在java中就是java.util.List<T>
set<T> 无序不可重复类型 在java中就是java.util.Set<T>
map<K,V> 键值对类型 在java中就是java.util.Map<K,V>
举一个map的例子,如果你想生成一个Map<Integer,String>类型的代码,就要这样声明。
map<i32,string> sex = {1:"男",2:"女"}
集合类型是这样,list<i32> age = [1,2,3,4,5]
2.4、struct 自定义对象
在java中就是实体类,类似于c的结构体:
struct User {
1:i32 id,
2:string name,
3:i32 age
}
1、举一个struct的例子,如果你想生成一个User类型的代码,就要这样声明。里面有序号,类型,属性名称。
2、struct的类不能继承,成员与成员之间的分割可以是逗号,也可以是分号。
3、结构体里面的每一个字段都要进行编号,从1开始。
4、结构是变量类型,变量名。
5、还有一种类型,叫做optionl,就是可选类型,就是可以为空的类型,就是在序列化的时候是可选的,没有值就不序列化,有就序列化。
默认为每一个成员都加入的关键字,可以不写。我上面就没写,其实默认就是。你妹提供默认值,他就不做序列化。
6、还有一个关键字叫required,就是必须的,就是在序列化的时候是必须的,没有值就报错。和optionl相反。
你妹提供默认值,他就报错。
举个例子:其中email就是可选的,name就是必须要有值的。
namespace java com.levi.entity
struct User{
1: string name ='levi',
2: optional i32 age,
3: list<i32> ages = [1,2,3,4],
4. required i32 hieght
}
2.5、enum 枚举类型
在java中就是枚举类:枚举以逗号分隔,并且不支持嵌套(枚举内部不能再定义枚举)
而且枚举中的整形只能是i32
namespace java com.levi.entity
enum Sex {
MALE=1,
FEMALE=2
}
2.6、异常类型:
exception 异常类型,在java中就是异常类:异常以逗号分隔也可以不分割,啥也不加都行,并且不支持嵌套(异常内部不能再定义异常)
而且异常中的整形只能是i32
namespace java com.levi.exception
exception UserException {
1: i32 code,
2: string message
}
2.7、服务 (Service)
其实就是每种语言中的那些业务类。
服务接口
service UserService{
bool login(1:string name,2:string password)
void register(1:User user) // 这个User不是实体类,而是我们前面定义的struct, idl语言中的结构体,也就是说你要先定义User这个结构体
}
注意:
1. 异常:throws关键字可以抛出异常之类的,异常列表里面可以有多个异常,这些异常也必须是我们前面定义的idl中的异常
service UserService{
bool login(1:string name,2:string password) throws (1:UserException e,2:XXXException e)
void register(1:User user) // 这个User不是实体类,而是我们前面定义的struct, idl语言中的结构体,也就是说你要先定义User这个结构体
}
3. oneway 表示客户端发起请求后不等待响应返回,立刻往下执行,因为没有返回值的概念,只能和void 这种操作配合。
service UserService{
bool login(1:string name,2:string password) throws (1:UserException e,2:XXXException e)
oneway void register(1:User user) // 这个User不是实体类,而是我们前面定义的struct, idl语言中的结构体,也就是说你要先定义User这个结构体
}
4. 继承,和struct不同,这里可以继承
service BaseService{
void m1(1:string name)
}
service UserService extends BaseService{
}
2.8、include(模块化)
作用:进行IDL模块化编程
可以在一个thrift文件中,以include命令引入其他的thrift文件作为使用,最终生成的文件里面会引用,并且去掉前缀啥的。他的含义就是对应着你在一个类里面引用其他的包的类或者实体,这样的一个效果。
levi1.thrift
struct User{
1:string name
}
levi2.thrift
include "levi1.thrift"
service UserService{
void register(1:levi1.User user)
}
2.9、注释
# 单行注释
// 单行注释
/*
* 多行注释
*/
3、实际操作IDL
先在pom文件中引入一下jar包依赖。
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.22.0</version>
</dependency>
Thrift把IDL生成对应代码的命令:
thrift --gen java xx.thrift
thrift --gen py xx.thrift
以上是两种翻译为java和py的命令,其余的语言可以查看文档。thrift --help
levi@192 ~ % thrift --help
Usage: thrift [options] file
Options:
-version Print the compiler version
-o dir Set the output directory for gen-* packages
(default: current directory)
-out dir Set the ouput location for generated files.
(no gen-* folder will be created)
-I dir Add a directory to the list of directories
searched for include directives
-nowarn Suppress all compiler warnings (BAD!)
-strict Strict compiler warnings on
-v[erbose] Verbose mode
-r[ecurse] Also generate included files
-debug Parse debug trace to stdout
--allow-neg-keys Allow negative field keys (Used to preserve protocol
compatibility with older .thrift files)
--allow-64bit-consts Do not print warnings about using 64-bit constants
--gen STR Generate code with a dynamically-registered generator.
STR has the form language[:key1=val1[,key2[,key3=val3]]].
Keys and values are options passed to the generator.
Many options will not require values.
Options related to audit operation
--audit OldFile Old Thrift file to be audited with 'file'
-Iold dir Add a directory to the list of directories
searched for include directives for old thrift file
-Inew dir Add a directory to the list of directories
searched for include directives for new thrift file
Available generators (and options):
c_glib (C, using GLib):
cl (Common Lisp):
no_asd: Do not define ASDF systems for each generated Thrift program.
sys_pref= The prefix to give ASDF system names. Default: thrift-gen-
cpp (C++):
cob_style: Generate "Continuation OBject"-style classes.
no_client_completion:
Omit calls to completion__() in CobClient class.
no_default_operators:
Omits generation of default operators ==, != and <
templates: Generate templatized reader/writer methods.
pure_enums: Generate pure enums instead of wrapper classes.
include_prefix: Use full include paths in generated files.
moveable_types: Generate move constructors and assignment operators.
no_ostream_operators:
Omit generation of ostream definitions.
no_skeleton: Omits generation of skeleton.
d (D):
dart (Dart):
library_name: Optional override for library name.
library_prefix: Generate code that can be used within an existing library.
Use a dot-separated string, e.g. "my_parent_lib.src.gen"
pubspec_lib: Optional override for thrift lib dependency in pubspec.yaml,
e.g. "thrift: 0.x.x". Use a pipe delimiter to separate lines,
e.g. "thrift:| git:| url: git@foo.com"
delphi (Delphi):
register_types: Enable TypeRegistry, allows for creation of struct, union
and container instances by interface or TypeInfo()
constprefix: Name TConstants classes after IDL to reduce ambiguities
events: Enable and use processing events in the generated code.
xmldoc: Enable XMLDoc comments for Help Insight etc.
async: Generate IAsync interface to use Parallel Programming Library (XE7+ only).
com_types: Use COM-compatible data types (e.g. WideString).
old_names: Compatibility: generate "reserved" identifiers with '_' postfix instead of '&' prefix.
rtti: Activate {$TYPEINFO} and {$RTTI} at the generated API interfaces.
erl (Erlang):
legacynames: Output files retain naming conventions of Thrift 0.9.1 and earlier.
delimiter= Delimiter between namespace prefix and record name. Default is '.'.
app_prefix= Application prefix for generated Erlang files.
maps: Generate maps instead of dicts.
go (Go):
package_prefix= Package prefix for generated files.
thrift_import= Override thrift package import path (default:github.com/apache/thrift/lib/go/thrift)
package= Package name (default: inferred from thrift file name)
ignore_initialisms
Disable automatic spelling correction of initialisms (e.g. "URL")
read_write_private
Make read/write methods private, default is public Read/Write
skip_remote
Skip the generating of -remote folders for the client binaries for services
gv (Graphviz):
exceptions: Whether to draw arrows from functions to exception.
haxe (Haxe):
rtti Enable @:rtti for generated classes and interfaces
buildmacro=my.macros.Class.method(args)
Add @:build macro calls to generated classes and interfaces
html (HTML):
standalone: Self-contained mode, includes all CSS in the HTML files.
Generates no style.css file, but HTML files will be larger.
noescape: Do not escape html in doc text.
java (Java):
beans: Members will be private, and setter methods will return void.
private_members: Members will be private, but setter methods will return 'this' like usual.
private-members: Same as 'private_members' (deprecated).
nocamel: Do not use CamelCase field accessors with beans.
fullcamel: Convert underscored_accessor_or_service_names to camelCase.
android: Generated structures are Parcelable.
android_legacy: Do not use java.io.IOException(throwable) (available for Android 2.3 and above).
option_type=[thrift|jdk8]:
thrift: wrap optional fields in thrift Option type.
jdk8: Wrap optional fields in JDK8+ Option type.
If the Option type is not specified, 'thrift' is used.
rethrow_unhandled_exceptions:
Enable rethrow of unhandled exceptions and let them propagate further. (Default behavior is to catch and log it.)
java5: Generate Java 1.5 compliant code (includes android_legacy flag).
future_iface: Generate CompletableFuture based iface based on async client.
reuse_objects: Data objects will not be allocated, but existing instances will be used (read and write).
reuse-objects: Same as 'reuse_objects' (deprecated).
sorted_containers:
Use TreeSet/TreeMap instead of HashSet/HashMap as a implementation of set/map.
generated_annotations=[undated|suppress]:
undated: suppress the date at @Generated annotations
suppress: suppress @Generated annotations entirely
unsafe_binaries: Do not copy ByteBuffers in constructors, getters, and setters.
jakarta_annotations: generate jakarta annotations (javax by default)
annotations_as_metadata:
Include Thrift field annotations as metadata in the generated code.
javame (Java ME):
js (Javascript):
jquery: Generate jQuery compatible code.
node: Generate node.js compatible code.
ts: Generate TypeScript definition files.
with_ns: Create global namespace objects when using node.js
es6: Create ES6 code with Promises
thrift_package_output_directory=<path>:
Generate episode file and use the <path> as prefix
imports=<paths_to_modules>:
':' separated list of paths of modules that has episode files in their root
json (JSON):
merge: Generate output with included files merged
kotlin (Kotlin):
lua (Lua):
omit_requires: Suppress generation of require 'somefile'.
markdown (Markdown):
suffix: Create files/links with/out 'md|html' default None
noescape: Do not escape with html-entities in doc text.
netstd (C#):
wcf: Adds bindings for WCF to generated classes.
serial: Add serialization support to generated classes.
union: Use new union typing, which includes a static read function for union types.
pascal: Generate Pascal Case property names according to Microsoft naming convention.
net6: Enable features that require net6 and C# 8 or higher.
net8: Enable features that require net8 and C# 12 or higher.
no_deepcopy: Suppress generation of DeepCopy() method.
async_postfix: Append "Async" to all service methods (maintains compatibility with existing code).
ocaml (OCaml):
perl (Perl):
php (PHP):
inlined: Generate PHP inlined files
server: Generate PHP server stubs
oop: Generate PHP with object oriented subclasses
classmap: Generate old-style PHP files (use classmap autoloading)
rest: Generate PHP REST processors
nsglobal=NAME: Set global namespace
validate: Generate PHP validator methods
json: Generate JsonSerializable classes (requires PHP >= 5.4)
getters_setters: Generate Getters and Setters for struct variables
py (Python):
zope.interface: Generate code for use with zope.interface.
twisted: Generate Twisted-friendly RPC services.
tornado: Generate code for use with Tornado.
no_utf8strings: Do not Encode/decode strings using utf8 in the generated code. Basically no effect for Python 3.
coding=CODING: Add file encoding declare in generated file.
slots: Generate code using slots for instance members.
dynamic: Generate dynamic code, less code generated but slower.
dynbase=CLS Derive generated classes from class CLS instead of TBase.
dynfrozen=CLS Derive generated immutable classes from class CLS instead of TFrozenBase.
dynexc=CLS Derive generated exceptions from CLS instead of TExceptionBase.
dynfrozenexc=CLS Derive generated immutable exceptions from CLS instead of TFrozenExceptionBase.
dynimport='from foo.bar import CLS'
Add an import line to generated code to find the dynbase class.
package_prefix='top.package.'
Package prefix for generated files.
old_style: Deprecated. Generate old-style classes.
enum: Generates Python's IntEnum, connects thrift to python enums. Python 3.4 and higher.
type_hints: Generate type hints and type checks in write method. Requires the enum option.
rb (Ruby):
rubygems: Add a "require 'rubygems'" line to the top of each generated file.
namespaced: Generate files in idiomatic namespaced directories.
rs (Rust):
st (Smalltalk):
swift (Swift 3.0):
log_unexpected: Log every time an unexpected field ID or type is encountered.
debug_descriptions:
Allow use of debugDescription so the app can add description via a cateogory/extension
async_clients: Generate clients which invoke asynchronously via block syntax.
namespaced: Generate source in Module scoped output directories for Swift Namespacing.
cocoa: Generate Swift 2.x code compatible with the Thrift/Cocoa library
promise_kit: Generate clients which invoke asynchronously via promises (only use with cocoa flag)
safe_enums: Generate enum types with an unknown case to handle unspecified values rather than throw a serialization error
xml (XML):
merge: Generate output with included files merged
no_default_ns: Omit default xmlns and add idl: prefix to all elements
no_namespaces: Do not add namespace definitions to the XML model
xsd (XSD):
我们这里来试试翻译java。thrift -r --gen java xx.thrift -r表示include也一起生成,其他模块的也引入进来。
我们首先来编一个idl文件test1.thrift
namespace java com.levi.entity
struct User{
1: string name ='levi',
2: optional i32 age,
3: list<i32> ages = [1,2,3,4],
4: required i32 hieght
}
exception UserException {
# 异常编码
1: i32 code,
# 异常信息
2: string message
}
然后我们再来编写一个idl文件test2.thrift
并且我们引入了这个test1里面的User。
include "test1.thrift"
service UserService{
void add(1:test1.User user)
}
我们进入这个目录:
然后执行thrift -r --gen java xx.thrift 来生成对应的类,但是还有一个问题,我们的test2里面引用了test1,所以我们应该先生成test1然后test2,要是引用关系复杂那不就烦死了,于是thrift提供了一个命令-r参数。
他可以递归的翻译,所有包括include的一起生成,比如我们test2中引用了test1,那么我们只需要-r test2就可以了,test1可以顺带一起生成,于是我们的命令就变成了。
thrift -r --gen java test2.thrift
执行之后就会在同级目录下生成对应的thrift的java类。
此时注意,他不是我们的java源代码,我们需要把他复制到我们的src类包里面才能用。复制完了之后就可以把那些thrift的产物删了。
或者你也可以使用-o参数来指定你要输出的路径。
ok我们来看下我们的类。
我们发现@javax.annotation.Generated这个注解报错,这是因为我用的是jdk11,jdk11及其以上移除了该包,可以使用jar替代,我们重新引入一下即可。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
此时我的pom如下。
<properties>
<thrift.version>0.22.0</thrift.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>${thrift.version}</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
ok,此时我们就生成了thrift rpc的一些必备前置物料了,后面我们将使用这个rpc框架进行通信,而他封装的那些语言异构也好,还是传输协议上的极致的压缩消息都是非常优秀的,这个框架在dubbo中大放异彩。
4、踩坑
register是thrift的关键字,不要用这个做命名方法或者参数啥的。