Thrift简介
The Apache Thrift software framework, for scalable cross-language services development, combines a software stack with a code generation engine to build services that work efficiently and seamlessly between C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, OCaml and Delphi and other languages.
http://thrift.apache.org
通过官方介绍,我们可以了解到Thrift是一个软件框架,可以提供跨语言的服务开发。Thrift框架包含一个软件栈,包含生成各种语言的引擎,我们通过Thrift提供的接口定义语言(IDL)来定义接口,然后引擎会自动生成各种语言的代码。
Thrift最初是由Facebook研发,主要用于各服务之间的RPC通信,支持跨语言,常用的语言比如C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk、 OCaml and Delphi and other languages
Thrift是一个典型的C/S(客户端/服务端)结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端可以使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,这种语言就是IDL(Interface Definition Language)。
Thrift架构
Thrift传输格式
图中,TProtocol(协议层),定义数据传输格式,例如:
- TBinaryProtocol:二进制格式;
- TCompactProtocol:压缩格式;
- TJSONProtocol:JSON格式;
- TSimpleJSONProtocol:提供JSON只写协议, 生成的文件很容易通过脚本语言解析;
- TDebugProtocol:使用易懂的可读的文本格式,以便于debug
Thrift数据传输方式
TTransport(传输层),定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
- TSocket:阻塞式socker;
- TFramedTransport:以frame为单位进行传输,非阻塞式服务中使用;
- TFileTransport:以文件形式进行传输;
- TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream;
- TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用,当前无java实现;
Thrift支持的服务模型
- TSimpleServer:简单的单线程服务模型,常用于测试;
- TThreadPoolServer:多线程服务模型,使用标准的阻塞式IO;
- TNonblockingServer:多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式);
- THsHaServer:THsHa引入了线程池去处理,其模型把读写任务放到线程池去处理;HaIf-sync/HaIf-async的处理模式,HaIf-async是在处理IO事件上(accept/read/write io),HaIf-sync用于handler对rpc的同步处理。
Thrift实际上是实现了C/S模式,通过代码生成工具将thrift文生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后客户端调用服务,服务器端提服务便可以了。
一般将服务放到一个.thrift文件中,服务的编写语法与C语言语法基本一致,在.thrift文件中有主要有以下几个内容:变量声明(variable)、数据声明(struct)和服务接口声明(service, 可以继承其他接口)。
Thrift数据类型
- Thrift不支持无符号类型,因为很多编程语言不存在无符号类型,比如Java。
- byte:有符号字节
- i16:16位有符号整数
- i32:32位有符号整数
- i64:64位有符号整数
- dubbo:64位有浮点数
- string:字符串
- bool: 布尔值 (true or false), one byte
- 特殊类型(括号内为对应的Java类型):binary(ByteBuffer):未经过编码的字节流
Thrift容器类型
- 集合中的元素可以是除了service之外的任何类型,包括exception
- list<T>:一系列由T类型的数据组成的有序列表,元素可以重复
- set<T>:一系列由T类型的数据组成的无序集合,元素不可以重复
- map<K,V>:一个字典结构,key为K类型,value为V类型,相当于Java中的HashMap
Thrift工作原理
- 如何实现多语言的通信?
- 数据传输使用socket(多种语言均支持),数据在以特定的格式(String等)发送,接收方语言进行解析
- 定义thrift的文件,由thrift文件(IDL)生成双方语言的接口、model,在生成的model以及接口中会有解码编码的代码。
Thrift IDL文件
namespace java com.example.project
struct News{
1:i32 id;
2:string title;
3:string content;
4:string mediaFrom;
5:string author;
}
service IndexNewsOperatorServices{
bool indexNews(1:NewsModel indexNews),
bool removeNewsById(1:i32 id)
}
结构体(struct)
- 就像C语言一样,Thrift 支持struct 类型,目的就是将一些数据聚合在一起,方便传输管理。struct 的定义形式如下:
struct Peopel{
1:string name;
2:i32 age;
3:string gender;
}
枚举(Enums)
- 枚举的定义形式和Java的Enum定义类似:
enum Gender{
MALE,
FEMALE
}
异常(exception)
- Thrift 支持自定义exception,规则与struct 一样。
exception RequestException{
1:i32 code;
2:string reason;
}
服务(service)
- Thrift 定义服务相当于Java中创建Interface一样,创建的service经过代码生成命令之后,就会生成客户端和服务端的框架代码,定义形式如下:
service HelloWorldService{
//service中定义的函数,相当于Java interface中定义的方法
string doAction(1:string name,2:i32 age);
}
类型定义
- Thrift 支持类型C++一样的typedef定义:
typedef i32 int
typedef i64 long
常量(const)
- Thrift 也支持常量定义,使用const关键字:
const i32 MAX_RETRIES_TIME = 10;
const string MY_WEBSITE = "http://facebook.com"
命名空间
- Thrift 的命名空间相当于Java中的package的意思,主要目的是组织代码。Thrift使用关键字namespace定义命名空间:
namespace java com.test.thrift.demo
- 格式是:namespace 语言名 路径
文件包含
- Thrift 也支持文件包含,相当于C/C++中的include,Java中的import,使用关键字include定义:
include "global.thrift"
注释
- Thrift 注释方式支持shell风格的注释,支持C/C++风格的注释,即#和//开头的语句都当做注释,/**/包裹的语句也是注释。
可选与必选
- Thrift 提供两个关键字required、optional,分别用于表示对应的字段是必填的还是可选的
struct Peopel{
1:required string name;
2:optional i32 age;
}
Windows平台下Thrift安装与使用
下载与安装:http://thrift.apache.org/download
下载thrift-0.12.0.exe文件命名为thrift .exe放在D盘下的一个thtift文件夹中。
建议:修改名称为thrift.exe,方便使用
建议:配置环境变量
在系统变量Path中添加thrift路径(D:\thrift)到环境变量中
这样就可以在dos窗口来使用thrift命令了,”thrift -version ”
生成代码
- 了解了如何定义thrift文件之后,我们需要用定义好的thrift文件生成我们需要的目标语言的源码
- 首先需要定义thrift接口描述文件,如data.thrift
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,
2: optional int age,
3: optional boolean married
}
exception DataException{
1: optional String message,
2: optional String callStack,
3: optional String data
}
service PersonService{
Person getPersonByUsername(1: required String username) throws (1: DataException dataException),
void savePerson(1: required Person person) throws (1: DataException dataException)
}
-
生成代码,在idea控制台上输入:
thrift --gen java src/thrift/data.thrift
引入maven或gradle依赖
//maven依赖
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.12.0</version>
</dependency>
//gradle依赖
compile group: 'org.apache.thrift', name: 'libthrift', version: '0.12.0'
实现Thrift定义的service
/**
* @author: huangyibo
* @Date: 2019/3/13 23:38
* @Description: 实现Thrift定义的服务接口
*/
public class PersonServiceImpl implements PersonService.Iface {
@Override
public Person getPersonByUsername(String username) throws DataException, TException {
System.out.println("Got client Param:" + username);
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("Got client Param:" + person);
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
Thrift服务端实现
public class ThriftServer {
public static void main(String[] args) throws TTransportException {
//非阻塞socket
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
//高可用server
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
//处理器
PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(arg);
System.out.println("Thrift server started!");
server.serve();
}
}
Thrift客户端实现
public class ThriftClient {
public static void main(String[] args) {
TTransport transport = new TFramedTransport(new TSocket("localhost",8899),1000);
TProtocol protocol = new TCompactProtocol(transport);
PersonService.Client client = new PersonService.Client(protocol);
try {
transport.open();//打开socket
Person person = client.getPersonByUsername("张三");
System.out.println(person.getUsername());
System.out.println(person.getAge());
System.out.println(person.isMarried());
System.out.println("-------------------------");
Person person1 = new Person();
person1.setUsername("李四");
person1.setAge(20);
person1.setMarried(false);
client.savePerson(person1);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
} finally {
transport.close();
}
}
}
Thrift Python实现的服务端和客户端
修改Thrift的IDL文件
namespace py py.thrift.generated
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct Person{
1: optional String username,
2: optional int age,
3: optional boolean married
}
exception DataException{
1: optional String message,
2: optional String callStack,
3: optional String data
}
service PersonService{
Person getPersonByUsername(1: required String username) throws (1: DataException dataException),
void savePerson(1: required Person person) throws (1: DataException dataException)
}
通过thrift命令生成Python版thrift代码
thrift --gen py src/thrift/data.thrift
实现Thrift定义的service(Python)
# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'
from py.thrift.generated import ttypes
class PersonServiceImpl:
def getPersonByUsername(self, username):
print 'Got client param:' + username
person = ttypes.Person()
person.username = username
person.age = 20
person.married = False
return person
def savePerson(self, person):
print 'Got client param:'
print person.username
print person.age
print person.married
Thrift服务端代码实现(Python)
# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'
from py.thrift.generated import PersonService
from PersonServiceImpl import PersonServiceImpl
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
from thrift.server import TServer
try:
personServiceHandler = PersonServiceImpl()
processor = PersonService.Processor(personServiceHandler)
serverSocket = TSocket.TServerSocket(port=8899)
transportFactory = TTransport.TFramedTransportFactory()
protocolFactory = TCompactProtocol.TCompactProtocolFactory()
server = TServer.TSimpleServer(processor,serverSocket,transportFactory,protocolFactory)
server.service()
except Thrift.TException, ex:
print '%s' % ex.message
Thrift客户端代码实现(Python)
# _*_ coding:utf-8 _*_
__author__ = 'huangyibo'
from py.thrift.generated import PersonService
from py.thrift.generated import ttypes
from thrift import Thrift
from thrift.transport import TSocket
from thrift.transport import TTransport
from thrift.protocol import TCompactProtocol
import sys
reload(sys)
sys.setdefaultencoding('utf8')
try:
tSocket = TSocket.TSocket('localhost',8899)
tSocket.setTimeout(1000)
transport = TTransport.TFramedTransport(tSocket)
protocol = TCompactProtocol.TCompactProtocol(transport)
client = PersonService.Client(protocol)
transport.open()
person = client.getPersonByUsername('张三')
print person.username
print person.age
print person.married
print '-------------------------'
newPerson = ttypes.Person()
newPerson.username = '李四'
newPerson.age = 20
newPerson.married = True
client.savePerson(newPerson)
transport.close()
except Thrift.TException, tx;
print '%s' % tx.message
这样就可以实现Java客户端调用Python服务端、Python客户端调用Java服务端(其他语言之间的跨语言调用和这个类似)、Java客户端调用Java服务端、Python客户端调用Python服务端,特别是跨语言异构平台之间的调用价值很大,且比基于HTTP调用方式的RPC框架效率高很多。