一、简介
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掌管
apache thrift支持语言:C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
Thrift本身不但具备消息的格式,并且提供了数据传输的机制,但是也可以使用netty作为底层传输
二、使用场景
企业之间内网系统做分布式应用开发,thrift是首选的,作为两个系统间RPC通信的框架。
三、Thrift工作原理
1、数据传输使用socket,数据以特定格式(字符串/二进制)发送,接收方进行解析
2、定义thrift的IDL文件,生成双方语言的接口、model、解码/编码的代码
四、Thrift的IDL的语法
1、数据类型
thrift不支持无符号类型,因为很多编程语言不支持无符号类型,比如JAVA
1)基本类型
byte | 有符号字节 |
i16 | 16位有符号整数 |
i32 | 32位有符号整数 |
i64 | 64位有符号整数 |
double | 64位浮点数 |
string | 字符串 |
2)容器类型
list | 一系列由T类型的数据组成的有序列表,元素可以重复 |
set | 一系列由T类型的数据组成的无序集合,元素不可以重复 |
map | 一个字典结构,key为K类型,value为V类型,相当于Java中的HashMap |
1)集合中的元素可以是除了service之外的任何类型,包括exception,其中service指服务端与客户端通信定义的接口
2)定义容器可以使用泛型
3)其他说明
thrift中没有日期类型,通常在thrift的IDL中定义成string类型,再在特定语言中转换成日期类型
2、组件
1)结构体(struct)
将数据聚合在一起,方便传输管理,结构体编译后生成的内容就是:类,(相当于google protobuf的message)
struct People{
1:string name;
2:i32 age;
3:string gender;
}
2)异常(exception)
thrift支持自定义exception,规则与struct一样
exception RequestException{
1:i32 code;
2:string reason;
}
3)服务(service)
thrift定义服务相当于java中创建interface一样,创建的service经过代码生成命令之后,就会生成客户端和服务端的框架代码。
service HelloWorldService{
//service中定义的函数,相当于java interface中定义的方法
string doAction(1:string name,2:i32 age);
}
4)枚举
枚举的定义形式和java的Enum定义类似:
enum Gender{
MALE;
FEMALE;
}
3、语言特性
1)类型定义
thrift支持类似c++一样的typedef定义,作用实际上就是对类型定义一个别名
typedef i32 int
typedef i64 long
上图中i32与i64不太习惯,可以定义成int和long的别名,之后使用int与long标识i32与i64
2)常量
thrift支持常量定义,使用const关键字:
const i32 MAX_RETRIES_TIME = 10
const string MY_WEBSITE = "http://facebook.com"
3)命名空间
Thrift的命名空间相当于java中的package,主要用来设置生成代码的包路径,关键字:namespace
语法:namespace 语言名 路径
namesapce java com.mzj.thrift.demo
4)文件包含
thrift支持文件包含,相当于java中的import。使用关键字include定义,可以定义两个.thrift文件A,B,在A中includeB,这样直接编译A就可以一起生成B的代码
include "global.thrift"
5)注释
thrift注释方式支持shell风格,支持c/c++风格,即:#和//开头,用/**/包裹的都是注释
6)字段必须性
thrift提供两个关键字:必填required、可选optional
struct People{
1:required string name;
2:optional i32 age;
}
最佳实践:所有字段使用optional,有业务控制传与不传,定义required字段不便于扩展
五、Thrift的IDL的组成部分
- Struct:代表传输的对象类型(Service调用过程中传递的数据类型)
- Exception:定义rpc方法调用过程中可能抛出的异常,供服务端再处理请求时如果抛出异常,可以把异常抛给客户端,客户端通过异常内容可以获取异常信息。
- Service:相当于java中的接口,Service中定义若干方法,用于在客户端与服务端之间调用使用
六、开发thrift程序
1、搭建开发环境
1)下载并安装thrift编译器
MAC下推荐使用homebrew进行包管理
目前版本是0.12:
- windows平台可以直接下载官网提供的exe可执行程序,配置path后即可使用:
- 其他平台(Windows平台也可以)通过源代码进行现编译并使用
通过源码编译方式在特定平台下生成可执行程序这里省略,需要时见官网:
2)工程中引入thrift依赖
在maven中央仓库中搜索:(groupid=org.apache.thrif)org.apache.thrift,进而找到(artifactId=libthrift )libthrift包
maven:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.13.0</version>
</dependency>
2、编写IDL文件
namespace java thrift.generated
//---------类型定义(起别名)----------
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
//---------定义结构体---------
struct Person{
1: optional String username,//默认是optional,可以不写,但是最好写上
2: optional int age,
3: optional boolean married
}
//---------定义异常---------
exception DataException{
1: optional String message,
2: optional String callsStack,
3: optional String date
}
//---------定义service---------
service PersonService{
Person getPersonByUsername(1:required String username) throws (1: DataException dataException),
void savePerson(1: required Person person) throws (1: DataException dataException)
}
3、使用thrift编译器将IDL生成对应语言的代码
与protobuf不同的是,protobuf只生成一个源码文件,而thrift生成很多源码文件。
thrift --gen java src/main/resources/thrift/data.thrift
执行完后,会生成如下源码文件:
将生成的源码文件拷贝到对应包路径下:
此步,可以通过git submodule或者git subtree(推荐)进行优化流程:自动化将生成的文件git pull到所依赖的工程
4、编写服务端、客户端、业务实现代码
(新去熟悉一个thrift项目时,需要了解都有哪些结构体、接口等等,此时不要去看thrift自动生成的代码,而是去看.thrift的IDL文件)
实际项目结构体数量:100个左右,service有50个左右
1)服务端代码:ThriftServer.java
package com.mzj.netty.ssy._07_thrift.mycode;
import com.mzj.netty.ssy._07_thrift.PersonService;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TTransportException;
/**
* @Auther: mazhongjia
* @Description:
*/
public class ThriftServer {
public static void main(String[] args) throws TTransportException {
//Socket对象:异步非阻塞服务器
TNonblockingServerSocket socket = new TNonblockingServerSocket(8899);
//Server参数
THsHaServer.Args arg = new THsHaServer.Args(socket).minWorkerThreads(2).maxWorkerThreads(4);
//骨架 (PersonService.Processor)
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
arg.protocolFactory(new TCompactProtocol.Factory());//面向协议层(对应网络7层结构):决定数据如何进行压缩(解码与编码)
arg.transportFactory(new TFramedTransport.Factory());//面向传输层(对应网络7层结构):决定底层以什么形式将数据由一端传给另一端
arg.processorFactory(new TProcessorFactory(processor));
//半同步半异步的Server,(Thrift可以做集群,性能不错)
TServer server = new THsHaServer(arg);
//启动服务器,相当于死循环
server.serve();
}
}
2)服务实现代码:PersonServiceImpl.java
package com.mzj.netty.ssy._07_thrift.mycode;
import com.mzj.netty.ssy._07_thrift.DataException;
import com.mzj.netty.ssy._07_thrift.Person;
import com.mzj.netty.ssy._07_thrift.PersonService;
import org.apache.thrift.TException;
/**
* @Auther: mazhongjia
* @Description:
*/
public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getPersonByUsername(String username) throws DataException, TException {
System.out.println("getPersonByUsername被调用");
//处理业务逻辑
Person person = new Person();
person.setUsername(username);
person.setAge(20);
person.setMarried(false);
return person;
}
@Override
public void savePerson(Person person) throws DataException, TException {
System.out.println("savePerson被调用");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
3)客户端代码:ThriftClient.java
package com.mzj.netty.ssy._07_thrift.mycode;
import com.mzj.netty.ssy._07_thrift.Person;
import com.mzj.netty.ssy._07_thrift.PersonService;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
/**
* @Auther: mazhongjia
* @Description:
*/
public class ThriftClient {
public static void main(String[] args) throws TTransportException {
//客户端transport类型与服务端需要对应——C/S传输方式得一致,600是超时时间
TTransport transport = new TFramedTransport(new TSocket("localhost", 8899), 600);
//客户端protocol类型与服务端需要对应——C/S编解码类型得一致
TProtocol protocol = new TCompactProtocol(transport);//客户端与服务端的Protocol一定要对应,
//桩 (PersonService.Client)
PersonService.Client client = new PersonService.Client(protocol);
try {
//发起连接
transport.open();
//rpc调用
Person person = client.getPersonByUsername("张山");
//
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
System.out.println("------------------");
client.savePerson(person);
} catch (Exception e) {
e.printStackTrace();
} finally {
transport.close();//关闭连接
}
}
}
运行服务端,再允许客户端,服务端输出结果为:
客户端输出结果:
5、完整工程示例
https://github.com/mazhongjia/nettyssynetty02/tree/master/src/main/java/com/mzj/netty/ssy/_07_thrift