Apache thrift 之使用示例
最近看到一些招聘說明有關(guān)于thrift的要求:

作為一個(gè)面向簡歷編程的程序員,多年來使用的都是springmvc下的http調(diào)用,不免對這個(gè)thrift有了一絲好奇,于是thrift的學(xué)習(xí)就提上了日程。
Thrift 基本概念
百度百科的定義如下:
Thrift是一種接口描述語言和二進(jìn)制通訊協(xié)議,它被用來定義和創(chuàng)建跨語言的服務(wù)。它被當(dāng)作一個(gè)遠(yuǎn)程過程調(diào)用(RPC)框架來使用,是由Facebook為“大規(guī)??缯Z言服務(wù)開發(fā)”而開發(fā)的。
關(guān)于那些大而泛的概念,本文就不多介紹了,直接進(jìn)入thfrit的學(xué)習(xí)吧。
Thrift的數(shù)據(jù)類型
Thrift 腳本可定義的數(shù)據(jù)類型包括以下幾種類型:
基本類型:
bool: 布爾值 byte: 8位有符號整數(shù) i16: 16位有符號整數(shù) i32: 32位有符號整數(shù) i64: 64位有符號整數(shù) double: 64位浮點(diǎn)數(shù) string: UTF-8編碼的字符串 binary: 二進(jìn)制串 結(jié)構(gòu)體類型:
struct: 定義的結(jié)構(gòu)體對象 容器類型:
list: 有序元素列表 set: 無序無重復(fù)元素集合 map: 有序的key/value集合 異常類型:
exception: 異常類型 服務(wù)類型:
service: 具體對應(yīng)服務(wù)的類
Thrift的協(xié)議
Thrift可以讓用戶選擇客戶端與服務(wù)端之間傳輸通信協(xié)議的類別,在傳輸協(xié)議上總體劃分為**文本(text)「和」二進(jìn)制(binary)**傳輸協(xié)議。為節(jié)約帶寬,提高傳輸效率,一般情況下使用二進(jìn)制類型的傳輸協(xié)議為多數(shù),有時(shí)還會使用基于文本類型的協(xié)議,這需要根據(jù)項(xiàng)目/產(chǎn)品中的實(shí)際需求。常用協(xié)議有以下幾種:
TBinaryProtocol:二進(jìn)制編碼格式進(jìn)行數(shù)據(jù)傳輸TCompactProtocol:高效率的、密集的二進(jìn)制編碼格式進(jìn)行數(shù)據(jù)傳輸TJSONProtocol: 使用JSON文本的數(shù)據(jù)編碼協(xié)議進(jìn)行數(shù)據(jù)傳輸TSimpleJSONProtocol:只提供JSON只寫的協(xié)議,適用于通過腳本語言解析
Thrift的傳輸層
常用的傳輸層有以下幾種:
TSocket:使用阻塞式I/O進(jìn)行傳輸,是最常見的模式TNonblockingTransport:使用非阻塞方式,用于構(gòu)建異步客戶端TFramedTransport:使用非阻塞方式,按塊的大小進(jìn)行傳輸,類似于Java中的NIO
Thrift的服務(wù)端類型
TSimpleServer:單線程服務(wù)器端,使用標(biāo)準(zhǔn)的阻塞式I/OTThreadPoolServer:多線程服務(wù)器端,使用標(biāo)準(zhǔn)的阻塞式I/OTNonblockingServer:單線程服務(wù)器端,使用非阻塞式I/OTHsHaServer:半同步半異步服務(wù)器端,基于非阻塞式IO讀寫和多線程工作任務(wù)處理TThreadedSelectorServer:多線程選擇器服務(wù)器端,對THsHaServer在異步IO模型上進(jìn)行增強(qiáng)
簡單了解完上面的概念后,接下來我們將通過一個(gè)實(shí)例來演示thrift的使用。
安裝
使用thrift前,需要安裝thrift命令行工具,該工具用來將編寫的thfirt文件編譯成指定的源碼文件。
在 Mac 上用如下命令安裝:
$ brew install thrift
安裝成功后,查看版本:
$ thrift -v
Thrift version 0.14.1
其他操作系統(tǒng)的安裝過程可自行百度。
編寫 thrift 文件
這里我們編寫兩個(gè)服務(wù)。
HelloService.thrift
namespace java com.attempt.thrift02.gen.service
service HelloService {
string hello(1: string text);
}
這里定義了一個(gè)HelloService,里面僅有一個(gè)hello(String)方法
QueryResult.thrift
namespace java com.attempt.thrift02.gen.vo
struct QueryResult {
1: required i32 code; // 請求的code 必選
2: optional string msg; // 請求返回信息,可選
}
這里定義了一個(gè)對象實(shí)體,里面有兩個(gè)屬性:code與msg,用來接收返回參數(shù)。
QueryService.thrift
namespace java com.attempt.thrift02.gen.service
// 引入另一文件
include "QueryResult.thrift"
service QueryService {
// QueryResult在另一個(gè)文件中,使用方式為 文件名.對象名
QueryResult.QueryResult query(1: i32 code);
}
這是另一個(gè)Service:QueryService,該service中只有一個(gè)方法:query(...),該方法返回對象為QueryResult,由于該對象在QueryResult.thrift文件中,因此需要使用include命令引入,并且在引用時(shí),需要使用文件名.對象名的方式。
生成
在命令行中執(zhí)行如下命令(指定文件的源碼文件為java):
$ thrift -r --gen java QueryService.thrift
$ thrift -r --gen java HelloService.thrift
在QueryService.thrift中引用了QueryResult.thrift文件,因此只需生成QueryService.thrift,QueryResult.thrift就可自動生成。
生成的文件如下:

實(shí)現(xiàn)
我們將上一步生成的java代碼復(fù)制到src/main/java下,目錄結(jié)構(gòu)如下:

引入 thrift jar 包
除了復(fù)制生成的文件外,還需要在項(xiàng)目中引入thrift 的 jar 包,jar包的GAV如下:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.14.1</version>
</dependency>
HelloService的實(shí)現(xiàn):HelloServiceImpl
package com.attempt.thrift02.serviceImpl;
import com.attempt.thrift02.gen.service.HelloService;
import org.apache.thrift.TException;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:50 上午
*/
public class HelloServiceImpl implements HelloService.Iface {
@Override
public String hello(String text) throws TException {
return "hello, " + text + " !";
}
}
HelloServiceImpl是我們自己編寫的類,它實(shí)現(xiàn)了HelloService.Iface,在對應(yīng)的方法中編寫我們自己的業(yè)務(wù)邏輯,而HelloService.Iface是由thrift生成的類。
QueryService的實(shí)現(xiàn):QueryServiceImpl
package com.attempt.thrift02.serviceImpl;
import com.attempt.thrift02.gen.service.QueryService;
import com.attempt.thrift02.gen.vo.QueryResult;
import org.apache.thrift.TException;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:51 上午
*/
public class QueryServiceImpl implements QueryService.Iface {
@Override
public QueryResult query(int code) throws TException {
QueryResult result = new QueryResult();
if (code == 1) {
result.code = 1;
result.msg = "success";
} else {
result.code = 0;
result.msg = "fail";
}
return result;
}
}
同樣地,QueryServiceImpl是我們自己編寫的類,它實(shí)現(xiàn)了QueryService.Iface,在對應(yīng)的方法中編寫我們自己的業(yè)務(wù)邏輯,而QueryService.Iface是由thrift生成的類。
客戶端與服務(wù)端
接下來就是實(shí)現(xiàn)各服務(wù)對應(yīng)的客戶端了。
HelloService
server:
package com.attempt.thrift02.server;
import com.attempt.thrift02.gen.service.HelloService;
import com.attempt.thrift02.serviceImpl.HelloServiceImpl;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:53 上午
*/
public class HelloServer {
private static final int SERVER_PORT = 8090;
public static void main(String[] args) {
try {
HelloService.Processor processor = new HelloService.Processor<>(
new HelloServiceImpl());
TServerTransport transport = new TServerSocket(SERVER_PORT);
TServer server = new TSimpleServer(new TServer.Args(transport)
.processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
client:
package com.attempt.thrift02.client;
import com.attempt.thrift02.gen.service.HelloService;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:52 上午
*/
public class HelloClient {
private static final int SERVER_PORT = 8090;
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("localhost", SERVER_PORT);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
HelloService.Client client = new HelloService.Client(protocol);
String result = client.hello("thrift world");
System.out.println("result=" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != transport) {
transport.close();
}
}
}
}
啟動 server:

運(yùn)行 client:

QueryService
server:
package com.attempt.thrift02.server;
import com.attempt.thrift02.gen.service.QueryService;
import com.attempt.thrift02.serviceImpl.QueryServiceImpl;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:53 上午
*/
public class QueryServer {
private static final int SERVER_PORT = 8091;
public static void main(String[] args) {
try {
QueryService.Processor processor = new QueryService.Processor<>(
new QueryServiceImpl());
TServerTransport transport = new TServerSocket(SERVER_PORT);
TServer server = new TSimpleServer(new TServer.Args(transport)
.processor(processor));
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
client:
package com.attempt.thrift02.client;
import com.attempt.thrift02.gen.service.QueryService;
import com.attempt.thrift02.gen.vo.QueryResult;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 11:53 上午
*/
public class QueryClient {
private static final int SERVER_PORT = 8091;
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("localhost", SERVER_PORT);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
QueryService.Client client = new QueryService.Client(protocol);
QueryResult result = client.query(1);
System.out.println("query result=" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(null != transport) {
transport.close();
}
}
}
}
啟動 server:

運(yùn)行 client:

復(fù)合服務(wù)
以上同一個(gè)項(xiàng)目中有兩個(gè)thrift服務(wù),我們是開兩個(gè)端口對外提供服務(wù),在實(shí)際生產(chǎn)環(huán)境,thrift服務(wù)可能會更多,這種時(shí)候我們能不能只開一個(gè)jvm進(jìn)程來處理呢?thrift 提供了TMultiplexedProcessor來解決這個(gè)問題,使用方法如下:
server:
package com.attempt.thrift02.server;
import com.attempt.thrift02.gen.service.HelloService;
import com.attempt.thrift02.gen.service.QueryService;
import com.attempt.thrift02.serviceImpl.HelloServiceImpl;
import com.attempt.thrift02.serviceImpl.QueryServiceImpl;
import org.apache.thrift.TMultiplexedProcessor;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TNonblockingServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 12:45 下午
*/
public class MultipleServer {
private static final int SERVER_PORT = 8093;
public static void main(String[] args) {
try {
TMultiplexedProcessor processor = new TMultiplexedProcessor();
// 注冊 helloService
processor.registerProcessor("helloService",
new HelloService.Processor<>(new HelloServiceImpl()));
// 注冊 queryService
processor.registerProcessor("queryService",
new QueryService.Processor<>(new QueryServiceImpl()));
TServer server = getSimpleServer(SERVER_PORT, processor);
System.out.println("Starting the simple server...");
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
/** 簡單的單線程服務(wù)模型,一般用于測試 */
public static TServer getSimpleServer(int port, TProcessor processor)
throws TTransportException {
TServerTransport transport = new TServerSocket(port);
TServer server = new TSimpleServer(new TServer.Args(transport)
.processor(processor));
return server;
}
}
client:
服務(wù)端使用了TMultiplexedProcessor,客戶端也需要使用:
package com.attempt.thrift02.client;
import com.attempt.thrift02.gen.service.HelloService;
import com.attempt.thrift02.gen.service.QueryService;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TMultiplexedProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
/**
* {這里添加描述}
*
* @author chengyan
* @date 2021-05-06 12:46 下午
*/
public class MultipleClient {
private static final int SERVER_PORT = 8093;
public static void main(String[] args) {
TTransport transport = null;
try {
transport = new TSocket("localhost", SERVER_PORT);
transport.open();
TProtocol protocol = new TBinaryProtocol(transport);
// helloService
TMultiplexedProtocol helloService = new TMultiplexedProtocol(
protocol, "helloService");
HelloService.Client client = new HelloService.Client(helloService);
System.out.println(client.hello("thrift world"));
// queryService
TMultiplexedProtocol helloProtocol = new TMultiplexedProtocol(
protocol, "queryService");
QueryService.Client queryClient = new QueryService.Client(helloProtocol);
System.out.println(queryClient.query(1));
} catch (TException e) {
e.printStackTrace();
} finally {
transport.close();
}
}
}
啟動 server:

啟動 client:

關(guān)于 thrift 的使用介紹就到這里了,下篇我們來分析源碼的實(shí)現(xiàn)。
參考:
Apache Thrift系列詳解(一) - 概述與入門:https://juejin.cn/post/6844903622380093447
限于作者個(gè)人水平,文中難免有錯(cuò)誤之處,歡迎指正!原創(chuàng)不易,商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
本文首發(fā)于微信公眾號 「Java技術(shù)探秘」,如果您喜歡本文,歡迎關(guān)注該公眾號,讓我們一起在技術(shù)的世界里探秘吧!
