Thrift 是一个软件框架(远程过程调用框架),用来进行可扩展且跨语言的rpc服务的开发。
是一种C/S架构,最本质的区别,就是RPC主要是基于TCP/IP协议的,而HTTP服务主要是基于HTTP协议的,
们都知道HTTP协议是在传输层协议TCP之上的,所以效率来看的话,RPC当然是要更胜一筹啦!
网络五层模型
应用层: HTTP
传输层:RPC
网络层
数据链路层
物理层
一个完整的RPC架构里面包含了四个核心的组件,分别是Client ,Server,Client Stub以及Server Stub,这个Stub大家可以理解为存根。
客户端(Client),服务的调用方。
服务端(Server),真正的服务提供者。
客户端存根,存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
服务端存根,接收客户端发送过来的消息,将消息解包,并调用本地的方法。
(客户端存根:服务端地址+请求数据 ,打包并发送消息
服务端存根:解包消息,并调用相关方法)
RPC主要是用在大型企业里面,项目一般使用maven来管理。比如我们有一个处理订单的系统服务,先声明它的所有的接口(这里就是具体指Java中的interface),然后将整个项目打包为一个jar包,服务端这边引入这个二方库,然后实现相应的功能,客户端这边也只需要引入这个二方库即可调用了。
但是对于大型企业来说,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。
举例:
开发一个快速计算的RPC服务,通过接口函数getInt对外提供服务
功能:getInt函数使用用户传入的参数,经过复杂的计算,计算出一个整形值返回给用户;
服务器端使用java语言开发
调用客户端可以是java、c、python等语言开发的程序,
在这种应用场景下,我们需要:
(1)使用Thrift的IDL(接口定义语言)描述一下getInt函数(以.thrift为后缀的文件),
(2)使用Thrift的多语言编译功能,将这个IDL文件编译成C、java、python几种语言对应的“特定语言接口文件”(每种语言只需要一条简单的命令即可编译完成)
这样拿到对应语言的“特定语言接口文件”之后,就可以开发客户端和服务器端的代码了,开发过程中只要接口不变,客户端和服务器端的开发可以独立的进行。
总结步骤:
(1)接口的定义 接口定义语言描述getInt(.thrift)
(2)将该文件编译成特定语言的接口文件
客户端 服务端分别实现具体代码
Thrift为服务器端程序提供了很多的工作模式,例如:线程池模型、非阻塞模型等等,可以根据自己的实际应用场景选择一种工作模式高效地对外提供服务;
参考https://blog.csdn.net/houjixin/article/details/42778335
参考https://blog.csdn.net/sinat_28394909/article/details/84645782
下载地址:http://archive.apache.org/dist/thrift/0.14.1/。将thrift-0.14.1.exe放到一个文件下,如F:\thrift下,将其重命名为thrift.exe。如果不重命名,需要使用thrift-0.14.1调用thrift命令。
配置环境变量
向Path中添加变量值,值为thrift.exe的地址,如F:\thrift。
测试
命令行输入thrift -version,如果输出thrift的版本即表明安装成功。
例子参考
https://blog.csdn.net/zhiguoliu11/article/details/73228359
(1)首先建立一个maven项目、
包括client、server两个模块,每个模块包括main和test
使用Thrift开发程序,首先要做的事情就是使用IDL对接口进行描述, 然后再使用Thrift的多语言编译能力将接口的描述文件编译成对应语言的版本,本文中将IDL对接口的描述文件称为“Thrift文件”。
我用的记事本
namespace java com.demo.thrift
service Hello{
string helloString(1:string para)
}
格式为:namespace语言 命名空间的名字
IDL文件中对所有接口函数的描述都放在service中,service的名字可以自己指定,该名字也将被用作生成的特定语言接口文件的名字,接口函数需要对参数使用序号标号,除最后一个接口函数外,要以,结束对函数的描述。
例如两个函数时:
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)
}
编写IDL文件时需要注意的问题
[1]函数的参数要用数字依序标好,序号从1开始,形式为:“序号:参数名”;
[2]每个函数的最后要加上“,”,最后一个函数不加;
[3]在IDL中可以使用/……/添加注释
IDL支持的数据类型
IDL大小写敏感,它共支持以下几种基本的数据类型:
[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(),
除上述基本类型外,ID还支持以下类型:
[1]map,map类型,例如,定义一个map对象:map
[2]set,集合类型,例如,定义set对象:set aSet;
[3]list,链表类型,例如,定义一个list对象:list aList;
还支持在Thrift文件中自定义数据类型
在IDL中支持两种自定义类型:枚举类型和结构体类型,具体如下:
[1]enum, 枚举类型,例如,定义一个枚举类型:
enum Numberz
{
ONE = 1,
TWO,
THREE,
FIVE = 5,
SIX,
EIGHT = 8
}
注意,枚举类型里没有序号
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的IDL支持C/C++中类似typedef的功能,例如:
typedefi32 Integer
就可以为i32类型重新起个名字Integer。
生成Thrift服务接口文件
搭建Thrift编译环境之后,使用下面命令即可将IDL文件编译成对应语言的接口文件:
thrift --gen
例如:如果使用上面的thrift文件(见上面的代码2.1):test_service.thrift生成一个java语言的接口文件,则只需在搭建好thrift编译环境的机子上,执行如下命令即可:
thrift --gen java Hello.thrift
生成了对应的文件
把生成的java文件拷贝到main中
服务端程序需实现Hello.Iface接口,在实现接口中完成自己要提供的服务,服务器端对服务接口实现的代码如下所示:
package com.demo.thrift;
import org.apache.thrift.TException;
public class HelloImpl implements Hello.Iface {
@Override
public String helloString(String para) throws TException {
return "result:" + para;
}
}
通信方式和通信协议格式 两端一致
在服务器端启动thrift框架的部分代码比较简单,不过在写这些启动代码之前需要先确定服务器采用哪种工作模式对外提供服务,Thrift对外提供几种工作模式,例如:TSimpleServer、TNonblockingServer、TThreadPoolServer、TThreadedSelectorServer、TServer等模式,==每种服务模式的通信方式不一样,因此在服务启动时使用了那种服务模式,客户端程序也需要采用对应的通信方式。==本文用TSimpleServer
另外,Thrift支持多种通信协议格式:TCompactProtocol、TBinaryProtocol、TJSONProtocol等,因此,在使用Thrift框架时,客户端程序与服务器端程序所使用的通信协议一定要一致,否则便无法正常通信。
上代码
package com.demo.thrift.test;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
import org.junit.Test;
import com.demo.thrift.Hello;
import com.demo.thrift.HelloImpl;
public class HelloServerTest {
@Test
public void demo() throws TTransportException {
System.out.println("服务端开启....");
TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloImpl());
// 简单的单线程服务模型
TServerSocket serverTransport = new TServerSocket(9898);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
server.serve();
}
}
TProcessor tprocessor = new Hello.Processor<Hello.Iface>(new HelloImpl());
TServerSocket serverTransport = new TServerSocket(9898);
TServer.Args tArgs = new TServer.Args(serverTransport);
tArgs.processor(tprocessor);
tArgs.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TSimpleServer(tArgs);
TServer server = new TSimpleServer(tArgs);
server.serve();
package com.demo.thrift.test;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.junit.Test;
import com.demo.thrift.Hello;
public class HelloClientTest {
@Test
public void demo() {
System.out.println("客户端启动....");
TTransport transport = null;
try {
transport = new TSocket("127.0.0.1", 9898);
// 协议要和服务端一致
TProtocol protocol = new TBinaryProtocol(transport);
Hello.Client client = new Hello.Client(protocol);
transport.open();
String result = client.helloString("哈哈");
System.out.println(result);
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
} finally {
if (null != transport) {
transport.close();
}
}
}
TTransport transport = null;
transport = new TSocket("127.0.0.1", 9898);
TProtocolprotocol =new TBinaryProtocol(m_transport);
Hello.Client client = new Hello.Client(protocol);
transport.open();
String result = client.helloString("哈哈");
transport.close();
需要注意的问题
Thrift的服务器端和客户端使用的通信方式要一样,否则便无法进行正常通信;
Thrift的服务器端的种模式所使用的通信方式并不一样,因此,服务器端使用哪种通信方式,客户端程序也要使用这种方式,否则就无法进行正常通信了。服务器端使用的工作模式为TNonblockingServer,在该工作模式下需要采用的传输方式为TFramedTransport,也就是在通信过程中会将tcp的字节流封装成一个个的帧,此时就需要客户端程序也这么做,否则便会通信失败。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ThriftInMaven2artifactId>
<groupId>com.NEUgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>maven-thrift-serverartifactId>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.thriftgroupId>
<artifactId>libthriftartifactId>
<version>0.14.1version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-pluginartifactId>
<version>3.5.1version>
<configuration>
<source>1.7source>
<target>1.7target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-source-pluginartifactId>
<version>3.0.1version>
<executions>
<execution>
<id>attach-sourcesid>
<goals>
<goal>jargoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>