Thrift是由Facebook开发的
Thrift的简单使用
1.环境准备和依赖引入
无论使用何种语言,首先要准备Thrift编译环境,可以去官网下载相应的Thrift执行文件,下文均以Windows为例。下载后可以选择性的配置环境变量,最终在shell中可执行Thrift。
在项目中,预先准备好libthrift依赖,maven写法:
<dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId> <version>0.14.1</version> </dependency>
2. IDL文件的定义
例如:
定义一个testService.thrift(idl文件名不重要),一般都会定义在resources的thrift文件夹下:
/** * 定义了命名空间 语言 其中命名空间就是java中package thrift是大小写敏感的 */ namespace java com.example.demo.thrift /** * 这里的Service name(TestThriftService)便是后面生成的类名 */ service TestThriftService { /** * 可添加注释,最好在变量前标上数字 */ string getStr(1:string srcStr1, 2:string srcStr2), i32 getInt(1:i32 val1,2:i32 val2) }
这里定义了两个方法,分别返回字符串和int类型,在thrift的idl中,对于变量的定义如下:
string, 字符串类型,注意是全部小写形式;例如:string aString;
i16, 16位整形类型,例如:i16 aI16Val;
i32,32位整形类型,对应C/C++/java中的int类型;例如: I32 aIntVal;
i64,64位整形,对应C/C++/java中的long类型;例如:I64 aLongVal;
byte,8位的字符类型,对应C/C++中的char,java中的byte类型;例如:byte aByteVal;
bool, 布尔类型,对应C/C++中的bool,java中的boolean类型; 例如:bool aBoolVal;
double,双精度浮点类型,对应C/C++/java中的double类型;例如:double aDoubleVal;
void,空类型,对应C/C++/java中的void类型;该类型主要用作函数的返回值,例如:void testVoid;
3.通过 IDL生成类文件
当我们完成第一步环境准备后,就可以直接使用thrift执行下面的命令生成Java类:
thrift --gen <language> <Thrift filename>
例如,将上面的thrift转为.java:
thrift --gen java testService.thrift
便会自动在当前目录下生成目录和文件 com/example/demo/thrift/TestThriftService.java。
3.2通过 IDL生成类文件-使用maven插件
通过shell命令生成java文件未免过于麻烦,尤其当idl很多时,执行略为繁琐,这时可以使用mavan插件maven-thrift-plugin完成这一操作,注意即使使用插件也需要满足Thrift环境,因为本质上依旧是通过命令行转换 。在pom.xml中配置:
<build> <plugins> <plugin> <groupId>org.apache.thrift.tools</groupId> <artifactId>maven-thrift-plugin</artifactId> <version>0.1.11</version> <configuration> <!--指定Thrift编译文件的目录和位置,设定环境变量便可不用指定--> <thriftExecutable>D://thrift</thriftExecutable> <!--指定待编译的IDL文件目录,默认为src/main/thrift--> <thriftSourceRoot>src/main/resources/thrift</thriftSourceRoot> <!--在0.1.10版本后的plugin需要添加的参数--> <generator>java</generator> <!--指定编译输出目录--> <outputDirectory>src/main/java</outputDirectory> </configuration> </plugin> </plugins> </build>
然后通过执行plugin 的compile指令即可将文件直接编译转化为java类,注意有些版本需要添加<generator>java</generator>,否则可能会报错:[ERROR] thrift failed error: [FAILURE:generation:1] Error: unknown option java:hashcode。同时,如果我们像上面一样指定了编译输出目录为项目目录,会覆盖原有目录下的文件,所以可以保持默认配置,输出至target目录下,然后复制到我们想要的package下。
4.编写Thrift接口的实现类
通过上面那步,我们已经获得了Thrift生成的Service类,如下图:
接下来,我们写一个他的实现类,需要实现<原类名>.Iface:
public class TestThriftServiceImpl implements TestThriftService.Iface { @Override public String getStr(String srcStr1, String srcStr2) { System.out.println("str1:"+srcStr1+",str2:"+srcStr2); return srcStr1+srcStr2; } @Override public int getInt(int val1, int val2) { System.out.println("val1:"+val1+",val2:"+val2); return val1+val2; } }
5.编写Thrift服务端(被调用方)并启动
我们来参考一下Thrift官方文档的simple:
public class JavaServer { public static TestThriftServiceImpl testThriftServiceImpl; public static TestThriftService.Processor processor; public static void main(String [] args) { try { testThriftServiceImpl = new TestThriftServiceImpl(); processor = new TestThriftService.Processor(testThriftServiceImpl); Runnable simple = new Runnable() { public void run() { simple(processor); } }; //可选的使用SSL证书运行服务,其实一般RPC都是内部服务调用,可能SSL并不是标配 Runnable secure = new Runnable() { public void run() { secure(processor); } }; new Thread(simple).start(); //new Thread(secure).start(); } catch (Exception x) { x.printStackTrace(); } } public static void simple(TestThriftService.Processor processor) { try { //指定端口与Server类型 TServerTransport serverTransport = new TServerSocket(9090); TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); System.out.println("Starting the simple server..."); server.serve(); } catch (Exception e) { e.printStackTrace(); } } /** * 使用SSL协议运行 * */ public static void secure(TestThriftService.Processor processor) { try { TSSLTransportParameters params = new TSSLTransportParameters(); //需要指定一个证书文件 params.setKeyStore("../../lib/java/test/.keystore", "thrift", null, null); TServerTransport serverTransport = TSSLTransportFactory.getServerSocket(9091, 0, null, params); TServer server = new TSimpleServer(new Args(serverTransport).processor(processor)); System.out.println("Starting the secure server..."); server.serve(); } catch (Exception e) { e.printStackTrace(); } } }
在这里启动了一个监听9090端口的Thrift服务,只需要通过impl指定一个professor,然后通过professor指定一个server并启动服务即可。这里选用了最普遍的simpleServer,有关server的分类,我们下面再提。
6.编写Thrift客户端(调用发起方)并执行
public class Client { public static void main(String [] args) { if (args.length != 1) { System.out.println("Please enter 'simple' or 'secure'"); System.exit(0); } try { TTransport transport; if (args[0].contains("simple")) { transport = new TSocket("localhost", 9090); transport.open(); } else { TSSLTransportFactory.TSSLTransportParameters params = new TSSLTransportFactory.TSSLTransportParameters(); params.setTrustStore("../../lib/java/test/.truststore", "thrift", "SunX509", "JKS"); transport = TSSLTransportFactory.getClientSocket("localhost", 9091, 0, params); } TProtocol protocol = new TBinaryProtocol(transport); TestThriftService.Client client = new TestThriftService.Client(protocol); //这里其实已经是远程调用,可以跟服务端分别启动并检查日志 int result = client.getInt(1,2); System.out.println(result); String result2 = client.getStr("hello,","world"); System.out.println(result2); transport.close(); } catch (TException x) { x.printStackTrace(); } } }
这里使用相同的ThriftService类,可以通过代码复用或是引入依赖,只要简单的指定host、端口和协议即可,此处使用了二进制的TBinaryProtocol,注意此处的协议需要与服务端匹配一致才可以,具体额匹配关系下面再提。
分别先后启动并执行server和client我们可以分别观察到他们打印的日志:
clent:
server:
到这里,一个简单的Thrift demo便完成了。
Thrift的数据编解码协议、传输层模式和工作模式
我们注意到上面在客户端中指定了通信协议TbinaryProtocol,这也是默认的协议。在服务端使用的是TSimpleServer,这也是最简单的服务器类型,实际不适用于大多情境。在服务端,我们可以使用不同种的服务器;在服务端和客户端通信中也可以使用不同的协议类型。
服务端工作模式分类
服务器主要根据不同的线程模型进行分类,主要分为以下几类:
TSimpleServer,普通的BIO web服务器,单线程,基本不会使用;
TNonBlockingServer,全异步的NIO服务器,单线程使用IO多路复用,并发效率高于SimpleServer,但因为是单线程也不好用;
THsHaServer,半同步的服务器,socket读取使用NIO,执行业务代码则是交由线程池,同样资源下相对吞吐较高;
TThreadPoolServer,类似
Tomcat默认每连接一线程的线程模型,使用线程池,不易阻塞但相对资源占用较高; TThreadSelectorServer,标准的NIO模式,负责监听连接的和处理连接请求数据的均为线程池,综合比较下来,此工作模式一般为最佳。
大致可分为阻塞和非阻塞两类服务端,上面已经展示了阻塞同步服务端的创建,以下是非阻塞服务端的创建代码:
//…… public static TestThriftServiceImpl testThriftServiceImpl; public static TestThriftService.Processor processor; testThriftServiceImpl = new TestThriftServiceImpl(); processor = new TestThriftService.Processor(testThriftServiceImpl); Runnable secure = new Runnable() { public void run() { threadSelector(processor); } }; //…… public static void threadSelector(TestThriftService.Processor processor) { try { //这里使用了threadSelector模式 TNonblockingServerTransport serverSocket=new TNonblockingServerSocket(9090);//传输层协议 TThreadedSelectorServer.Args serverParams=new TThreadedSelectorServer.Args(serverSocket); serverParams.protocolFactory(new TBinaryProtocol.Factory()); //编码协议 serverParams.processor(processor); TServer server=new TThreadedSelectorServer(serverParams); //TThreadedSelectorServer System.out.println("Starting the threadSelector server..."); server.serve(); } catch (Exception e) { e.printStackTrace(); } }
客户端工作模式
客户端调用也可以简单的分为同步和异步callback方式,以下是一个异步客户端的例子:
try { TNonblockingSocket transport=null; transport = new TNonblockingSocket("localhost", 9090); //传输层协议 TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory(); //编码协议 TAsyncClientManager clientManager = new TAsyncClientManager(); TProtocol protocol = new TBinaryProtocol(transport); TestThriftService.AsyncClient client = new TestThriftService.AsyncClient(factory,clientManager,transport); AsyncMethodCallback asyncMethodCallback = new AsyncMethodCallback() { //异步callback @Override public void onComplete(Object response) { System.out.println("finished! return:"+response); } @Override public void onError(Exception exception) { System.out.println("error! ex:"+exception); } }; //守护线程,注意主线程 client.getInt(1,2,asyncMethodCallback); } catch (TException | IOException x) { x.printStackTrace(); }
传输层分类
Thrift有不同的传输层协议,该协议需要在服务端和客户端中保持一致,主要分为以下几种:
TNonblockingTransport: 非阻塞的 Transport 的抽象类,底层使用 NIO
TNonblockingSocket: TNonblockingTransport 的实现类,基于 SocketChannel 的 Transport,是非阻塞的
TIOStreamTransport: 基于 IO 流的 Transport
TSocket: TIOStreamTransport 的子类,底层使用 Socket
TSimpleFileTransport:基于文件的 Transport,会将流写入文件或者从文件读取流
TFileTransport: 基于文件的 Transport,会将流写入文件或者从文件读取流
THttpClient:基于 HttpClient 或 Http
URLConnection,会通过 HTTP 的方式发送请求,通常用于 TServlet 的服务端 ByteBuffer: 基于 ByteBuffer 的 Transport
TMemoryInputTransport:基于内存数组的 Transport,会从底层的数组读取,用于测试场景
TMemoryBuffer:使用内存数组作为缓冲区的 Transport,用于测试场景
TZlibTransport: 压缩的 Transport,会将流压缩后再发送
AutoExpandingBufferReadTransport: 可扩展读缓冲区的 Transport,使用可变数组作为缓冲区
AutoExpandingBufferWriteTransport: 可扩展写缓冲区的 Transport,使用可变数组作为缓冲区
TSaslTransport:支持 SASL(Simple Authentication and Security Layer) 认证的 Transport,有两个实现类,用于客户端的TSaslClientTransport 和用于服务端的 TSaslServerTransport
TFramedTransport:缓冲的 Transport,通过在前面带有4字节帧大小的消息来确保每次都完全读取消息
TFastFramedTransport: 复用并扩展了读写缓冲区的 Transport,避免每次都创建新的 byte 数组