定义
- Thrift框架是什么?
答:thrift是二进制的高性能的通讯中间件,支持数据(对象)序列化和多种类型的RPC服务。 - Thrift框架解决了什么问题?
答:它提供了一套解决方案,可以解决各系统间大数据量的传输通信以及系统之间语言环境不同,需要跨平台的问题。 - Thrift的特点是什么?
答:
- Thrift是一个服务端和客户端的架构体系;需要你在系统中搭建好通信双方的接口逻辑。
- Thrift 具有自己内部定义的传输协议规范(TProtocol)和传输数据标准(TTransports),
- Thrift 是通过IDL脚本对传输数据的数据结构(struct) 和传输数据的业务逻辑(service)根据不同的运行环境(跨语言)快速的构建相应的代码,并且通过自己内部的序列化机制(自己封装了序列化的逻辑代码)对传输的数据进行简化和压缩提高高并发、 大型系统中数据交互的成本
- Thrift的应用场景
答:当我想开发一个快速计算的RPC服务,它主要通过接口函数getInt对外提供服务,这个RPC服务的getInt函数使用用户传入的参数,经过复杂的计算,计算出一个整形值返回给用户;服务器端使用java语言开发,而调用客户端可以是java、c、python等语言开发的程序,在这种应用场景下,我们只需要使用Thrift的IDL描述一下getInt函数(以.thrift为后缀的文件),然后使用Thrift的多语言编译功能,将这个IDL文件编译成C、java、python几种语言对应的“特定语言接口文件”(每种语言只需要一条简单的命令即可编译完成),这样拿到对应语言的“特定语言接口文件”之后,就可以开发客户端和服务器端的代码了,开发过程中只要接口不变,客户端和服务器端的开发可以独立的进行。
用法
总结
其实使用很简单,比起普通的开发,我们需要做的事情就是:
- 配置java和thrift的编译环境,并且导入thrift相关的包。
- 熟悉一下thrift的脚本编写语法。通过脚本生成java文件,而后再将文件放到对应的包即可。
- 最后是对生成的java接口(服务端一般是一个service接口,实现它即可织入业务逻辑;客户端则直接连接端口,调用对应类的方法即可,这个类是client类,由thrift自动生成)进行实现,而后启动TNonblockingServer进行监听端口即可实现服务端。
那么,接下来详细讲每一步了
配置环境
- 参考这个
脚本编写语法
1. 数据类型
基本类型:
[1]string, 字符串类型,注意是全部小写形式;例如:string aString
[2]i16, 16位整形类型,例如:i16 aI16Val;
[3]i32,32位整形类型,对应C/C++/java中的int类型;例如: I32 aIntVal
[4]i64,64位整形,对应C/C++/java中的long类型;例如:I64 aLongVal
[5]byte,8位的字符类型,对应C/C++中的char,java中的byte类型;例如:byte aByteVal
[6]bool, 布尔类型,对应C/C++中的bool,java中的boolean类型; 例如:bool aBoolVal
[7]double,双精度浮点类型,对应C/C++/java中的double类型;例如:double aDoubleVal
[8]void,空类型,对应C/C++/java中的void类型;该类型主要用作函数的返回值,例如:void testVoid(),
对象类型:
[1]map,map类型,例如,定义一个map对象:map<i32, i32> newmap;
[2]set,集合类型,例如,定义set<i32>对象:set<i32> aSet;
[3]list,链表类型,例如,定义一个list<i32>对象:list<i32> aList;
自定义数据类型:
[1]enum, 枚举类型,语法如下:
enum Numberz
{
ONE = 1,
TWO,
THREE,
FIVE = 5,
SIX,
EIGHT = 8
}
注意,枚举类型里没有序号,结构体内就要了
[2]struct,自定义结构体类型,在IDL中可以自己定义结构体,对应C中的struct,c++中的struct和class,java中的class。例如:
struct TestV1 {
1: i32 begin_in_both,
3: string old_string,
12: i32 end_in_both
}
注意,在struct定义结构体时需要对每个结构体成员用序号标识:“序号: ”。
上面对应的就是数据结构了,那么基础的语法书写,thrift采用的其实是c那一套,直接按c来写就好了。如下:
namespace java com.test.service
include "thrift_datatype.thrift"
service TestThriftService
{
/**
*value 中存放两个字符串拼接之后的字符串
*/
thrift_datatype.ResultStr getStr(1:string srcStr1, 2:string srcStr2),
thrift_datatype.ResultInt getInt(1:i32 val)
}
这里的TestThriftService就被用作生成的特定语言的文件名,例如我想用该Thrift文件生成一个java版本的接口文件,那么生成的java文件名就是:TestThriftService.java。
补充:
还可以取别名,Thrift的IDL支持C/C++中类似typedef的功能,例如:
typedefi32 Integer
就可以为i32类型重新起个名字Integer。
2. 总结
thrift的语法类似c,只是换了一些数据类型的写法而已。并且,作用很明确:是定义dto和接口用的。所以不需要一些if,else,switch之类的语法。因此入手很快。
生成Thrift服务接口文件
搭建Thrift编译环境之后,使用下面命令即可将IDL文件编译成对应语言的接口文件:
thrift --gen <language> <Thrift filename>
例如:如果使用上面的thrift文件(见上面的代码2.1):test_service.thrift生成一个java语言的接口文件,则只需在搭建好thrift编译环境的机子上,执行如下命令即可:
thrift --gen java test_service.thrift
注意,要到thrift文件的当前目录执行,不然就要写绝对路径。
执行后,生成的gen-java的目录,目录下面有com、test、service三级目录,这三级目录也是根据test_service.thrift文件中命名空间的名字:com.test.service生成的,进入目录之后可以看到生成的java语言的接口文件名为:TestThriftService.java
实现生成的接口——服务端
- 先导入thrift相关的包。
- 将上面步骤生成的java接口文件TestThriftService.java拷贝到自己的工程文件中
那么就是实现接口了,访问器程序需实现TestThriftService.Iface接口(Iface是内部接口,自动生成的),在实现接口中完成自己要提供的服务
package com.test.service;
import org.apache.thrift.TException;
public class TestThriftServiceImpl implements TestThriftService.Iface
{
@Override
public String getStr(String srcStr1, String srcStr2) throws TException {
long startTime = System.currentTimeMillis();
String res = srcStr1 + srcStr2;
long stopTime = System.currentTimeMillis();
System.out.println("[getStr]time interval: " + (stopTime-startTime));
return res;
}
@Override
public int getInt(int val) throws TException {
long startTime = System.currentTimeMillis();
int res = val * 10;
long stopTime = System.currentTimeMillis();
System.out.println("[getInt]time interval: " + (stopTime-startTime));
return res;
}
}
业务逻辑植入后,再启动即可:
package com.test.service;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TTransportException;
public class testMain {
private static int m_thriftPort = 12356; //指定服务器监听的端口
private static TestThriftServiceImpl m_myService = new TestThriftServiceImpl(); //我们自己植入的业务逻辑
private static TServer m_server = null;// 服务核心类
private static void createNonblockingServer() throws TTransportException
{
TProcessor tProcessor = new TestThriftService.Processor<TestThriftService.Iface>(m_myService); //Service实现类进行装载,该类内部定义了一个map,它保存了所有函数名到函数对象的映射,一旦Thrift接到一个函数调用请求,就从该map中根据函数名字找到该函数的函数对象,然后执行它;
TNonblockingServerSocket nioSocket = new TNonblockingServerSocket(m_thriftPort);//初始化socket
TNonblockingServer.Args tnbArgs = new TNonblockingServer.Args(nioSocket);//初始化服务核心类
tnbArgs.processor(tProcessor);//配置服务核心类
tnbArgs.transportFactory(new TFramedTransport.Factory());//配置服务核心类,选择帧模式(这个模式对应NIO多路复用那种)
tnbArgs.protocolFactory(new TBinaryProtocol.Factory());//配置服务核心类,选择二进制协议
// 使用非阻塞式IO,服务端和客户端需要指定TFramedTransport数据传输的方式
m_server = new TNonblockingServer(tnbArgs);//完成服务核心类的初始化和配置。
}
public static boolean start()
{
try {
createNonblockingServer();//配置服务
} catch (TTransportException e) {
System.out.println("start server error!" + e);
return false;
}
System.out.println("service at port: " + m_thriftPort);
m_server.serve();//启动服务
return true;
}
public static void main(String[] args)
{
if(!start())//启动服务
{
System.exit(0);
}
}
}
在服务器端启动thrift框架的部分代码比较简单,不过在写这些启动代码之前需要先确定服务器采用哪种工作模式对外提供服务,Thrift对外提供几种工作模式,例如:TSimpleServer、TNonblockingServer、TThreadPoolServer、TThreadedSelectorServer等模式,每种服务模式的通信方式不一样,因此在服务启动时使用了那种服务模式,客户端程序也需要采用对应的通信方式。
另外,Thrift支持多种通信协议格式:TCompactProtocol、TBinaryProtocol、TJSONProtocol等,因此,在使用Thrift框架时,客户端程序与服务器端程序所使用的通信协议一定要一致,否则便无法正常通信。
实现生成的接口——客户端
- Thrift的客户端代码同样需要服务器开头的那两步:添加三个jar包和生成的java接口文件TestThriftService.java。
- 客户端不需要实现接口,而是直接把接口当成已经实现的,直接调用即可。thrift会动态代理接口,每次调用接口方法,其实就是向远端发起来一次请求,返回了一个结果(RPC那一套)。
m_transport = new TSocket(THRIFT_HOST, THRIFT_PORT,2000);//创建一个传输层对象(TTransport),具体采用的传输方式是TFramedTransport,要与服务器端保持一致,这里的THRIFT_HOST, THRIFT_PORT分别是Thrift服务器程序的主机地址和监听端口号,这里的2000是socket的通信超时时间;
TProtocol protocol = new TBinaryProtocol(m_transport);//创建一个通信协议对象(TProtocol),具体采用的通信协议是二进制协议
TestThriftService.Client testClient = new TestThriftService.Client(protocol);//创建一个Thrift客户端对象(TestThriftService.Client),Thrift的客户端类TestThriftService.Client已经在文件TestThriftService.java中,由Thrift编译器自动为我们生成
try {
m_transport.open();//打开socket,建立与服务器直接的socket连接
String res = testClient.getStr("test1", "test2");//通过客户端对象调用服务器服务函数getStr,这就是远端调用了。
System.out.println("res = " + res);
m_transport.close();//使用完成关闭socket
} catch (TException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
至此,一个简单的demo,和rpc调用的过程就讲完了,是不是很简单?
那么,接下来再讲一下源码的理解:
参考
- 由浅入深了解Thrift(1)
- 由浅入深了解Thrift(2)
- 由浅入深了解Thrift(3)
- Thrift 是什么?
- javaNIO理解:这一篇是因为rpc的几个任务模式其实很想java的nio复用,所以我也对比学习了一下