Thrift
Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用,是由Facebook为"大规模跨语言服务开发"而开发的。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统)、Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。虽然它以前是由Facebook开发的,但它现在是Apache软件基金会的开源项目了。该实现被描述在2007年4月的一篇由Facebook发表的技术论文中,该论文现由Apache掌管。
IDL接口描述语言
1.基本类型
bool
:布尔值(true 或 false)byte
:一个 8 位有符号整数i16
:一个 16 位有符号整数i32
:一个 32 位有符号整数i64
:一个 64 位有符号整数double
:一个 64 位浮点数string
:使用 UTF-8 编码编码的文本字符串binary
:未编码字节的序列
请注意,缺少无符号整数类型。这是因为许多编程语言中没有原生的无符号整数类型。
- 容器类型
Thrift 容器是强类型容器,可映射到大多数编程语言中常用和常用的容器类型。
有三种容器类型:
list<type>
:元素为type类型的有序列表。与 java 的 List 对应。set<type>
:一组无序的唯一元素。与 java 的 Set 对应map<type1,type2>
:严格唯一的键到值的映射。与 java 的 Map 对应
容器元素可以是任何有效的 Thrift 类型。
- 常量类型
const
常量类型 常量名称 = 常量值 ,如
idl
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world','goodnight':'moon'}
- 枚举类型
enum
,一组 32 位整数常量,不支持嵌套,如
idl
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPY = 3,
}
也可以省略常量值, 如
idl
enum Operation {
ADD,
SUBTRACT,
MULTIPY,
}
默认从1开始自动递增
- 结构体类型
struct
,封装一组不同类型的数据,与 Java 中的类对应,如
idl
struct Work {
1: i32 num1 = 0,
2: i32 num2, // 默认为 optional
3: Operation op,
4: optional string comment,
}
字段修饰符:required
(必须赋值)、optional
(可选,推荐使用)。
optional 关键字表示该字段值可选,如果构建的结构体类型数据中可选字段没有设置值,则在编码生成的消息数据中不会包含可选字段
- struct 不能继承,可以嵌套,不能嵌套自己
- 编号不能重复成员分隔符可以是逗号(,)也可以是分号(;)
- optional不填充就不序列化,required是必须填充,一定会序列化
- 字段可以设置默认值
- 异常类型
exception
,可以自定义异常中包含的数据内容,如
idl
exception FileException {
1: i32 code,
2: string message
}
- 服务接口
service
, 定义服务接口的方法和参数
使用 namespace
关键字,按语言指定包路径:
idl
namespace <语言> <包路径>
service 服务名称 {
返回值类型 方法名(参数列表) [throws (异常列表)],
// 其他方法...
}
例
idl
namespace java com.huang.thrift
service Calculator {
i32 add(1:i32 a, 2:i32 b), // 简单方法
double divide(1:double x, 2:double y) throws (1:FileException e), // 抛出异常
oneway void log(1:string message) // 异步方法
}
说明:
- 方法可以不带参数,如带参数,须指明参数的序号和参数类型
- 方法名前须指明返回值,void 表示没有返回值
- oneway 表示客户端发起请求后不再等待响应返回,oneway 方法必须是 void 返回类型
- throws 表示可能抛出的异常
- 服务继承
使用 extends
可以继承扩展另一个服务
通过 include
引入其他文件的服务,通过 文件名.xxx 可以进行使用
idl
include "base.thrift"
service UserService extends base.Service {
i32 getUsername(i:i32 id) throw (1:Excepion e)
}
- 其他
Thrift 支持多种注释方式
idl
// 单行注释
/* 多行注释*/
使用 typedef
可以为类型取别名,如
idl
typedef i32 int
Thrift编译器安装
我这里只介绍在 windows 安装
下载地址:[[Apache Download Mirrors](Apache Archive Distribution Directory)](thrift.apache.org/download)
下载18版本,新版本我没解决报错问题,我不知道有没有影响,但是看着不舒服
下载完毕后,找到一个文件夹放入,并将其名称更改为thrift.exe
打开环境变量,将thrift.exe的目录写入
cmd打开黑窗口,输入thrift --version,出现版本号即安装成功
IDL文件编译
IDL 文件可以直接用来生成各种语言的代码
bash
thrift --gen <语言> [选项] <文件名.thrift>
示例:生成Java代码
bash
thrift --gen java user_service.thrift
// 指定输出目录
thrift --gen java -o target user_service.thrift
常用编译选项
选项 | 作用 |
---|---|
-out <目录> |
指定代码生成目录 |
-I <路径> |
添加include文件搜索路径 |
-strict |
启用严格模式(警告视为错误) |
-v |
显示详细编译日志 |
-r |
递归编译include的依赖文件 |
Thrift 协议(Protocol)定义了数据在网络传输中的编码方式,直接影响通信效率和兼容性。以下是 Thrift 支持的主要协议类型及其核心特性
协议名称 | 编码方式 | 特点 | 适用场景 |
---|---|---|---|
TBinaryProtocol | 二进制编码 | 高性能,跨语言支持完善 | 默认选择,生产环境 |
TCompactProtocol | 紧凑二进制编码 | 体积比二进制更小,效率更高 | 高吞吐、带宽敏感场景 |
TJSONProtocol | JSON文本编码 | 可读性强,但体积大、效率低 | 调试、与前端交互 |
TSimpleJSONProtocol | 简化JSON | 仅生成JSON,无元信息 | 兼容非Thrift系统 |
TDebugProtocol | 调试文本格式 | 人类可读,用于日志记录 | 开发阶段调试 |
编译生成代码
我在这里提前准备了一个idl的文件(.thrift 结尾)
在 thrift 的文件目录执行上面讲的编译命令,便会生成对应的java文件,从图中可以发现,有一些报错,这是因为我们没有导入thrift的相关依赖
解决报错:
导入相关依赖
xml
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.18.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.11</version>
</dependency>
入门Demo实现
服务端实现
- 实现定义的服务接口
把生成的代码复制到我们自己的service目录中
编写服务端逻辑,实现IDL定义的接口
然后创建一个类实现生成的这个 UserService
类中的Iface
接口,并实现它的抽象方法
java
public class UserServiceImpl implements UserService.Iface {
@Override
public String getUserName(int userId) throws TException {
System.out.println("getUserName, userid :" + userId);
return "成功获取用户名!";
}
}
- 启动服务端的服务器
java
public class Service {
public static void main(String[] args) {
// 创建处理器和传输层
UserService.Processor<UserServiceImpl> processor =
new UserService.Processor<>(new UserServiceImpl());
TServerSocket serverSocket = null;
try {
serverSocket = new TServerSocket(8081);
// 配置协议和传输方式
TServer.Args serverArgs = new TServer.Args(serverSocket)
.processor(processor)
.protocolFactory(new TBinaryProtocol.Factory());
// 启动服务
TServer server = new TSimpleServer(serverArgs);
System.out.println("Server started on port 8081...");
server.serve();
} catch (TTransportException e) {
throw new RuntimeException(e);
}finally {
if (serverSocket != null)
serverSocket.close();
}
}
}
客户端实现
java
public class Client {
public static void main(String[] args) {
try {
// 配置传输和协议
TSocket tSocket = new TSocket("localhost", 8081);
tSocket.open();
TProtocol protocol = new TBinaryProtocol(tSocket);
UserService.Client client = new UserService.Client(protocol);
// 调用远程方法
String result = client.getUserName(156);
System.out.println(result);
tSocket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
能分别从服务端和客户端看到信息的打印,就算成功了。