新接觸的項目這邊使用了Thrift,于我而言是一個完全陌生的東西,因此在此記錄一下自己的學(xué)習(xí)過程和一點體會。
1.簡介
遠(yuǎn)程調(diào)用接口我們當(dāng)然可以使用HTTP協(xié)議,但伴隨著服務(wù)越來越多,業(yè)務(wù)越來越復(fù)雜,效率就變成了一個很大的問題,于是基于網(wǎng)絡(luò)傳輸層的RPC應(yīng)運而生。Thrift就是一個跨語言的RPC框架,支持很多常用的語言,大大方便了我們的開發(fā)。
2.環(huán)境介紹
- 操作系統(tǒng):macOS
- Thrift version:0.11.0
- IDE:IntelliJ IDEA
- JDK:1.8
3.安裝Thrift
mac推薦使用brew來安裝,命令如下
brew install thrift
如果還沒有安裝brew工具,可以使用如下命令安裝
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
安裝完成之后我們執(zhí)行thrift -version,看到輸出Thrift version 0.11.0就OK了。
4.編寫thrift文件并生成配置
我們使用Thrift IDL(interface definition language)來編寫thrift文件。具體的語法和規(guī)則可以參見官網(wǎng)的介紹TYPES和IDL,在這里我們舉一個簡單的例子,新建user.thrift文件,內(nèi)容如下
namespace java com.atticus //聲明命名空間
enum Gender{ //枚舉
MALE = 1,
FEMALE = 2
}
struct User{ //定義一個結(jié)構(gòu)
1: required i32 id,
2: required string name,
3: required Gender gender,
4: optional bool married
}
service UserService{ //定義service
User getUser(1:i32 id);
}
然后執(zhí)行thrift --gen java user.thrift命令,可以在當(dāng)前目錄下看到一個名為gen-java的目錄,這就是生成的文件了。

5.創(chuàng)建工程
我們在IDEA中創(chuàng)建一個maven工程,在pom.xml文件中加入對Thrift的依賴
<!-- 注意和自己brew安裝的thrift版本一致 -->
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.11.0</version>
</dependency>
隨后我們把上面生成的gen-java目錄下的所有文件都拷貝到我們工程下的src/main/java目錄下,整個項目結(jié)構(gòu)如下

6.服務(wù)端實現(xiàn)
首先我們來建一個UserServiceImpl類作為服務(wù)端的具體實現(xiàn)類
package com.atticus;
import org.apache.thrift.TException;
public class UserServiceImpl implements UserService.Iface{
@Override
public User getUser(int id) throws TException {
System.out.println("request come in... " + id);
User user = new User();
user.setId(id);
user.setName("atticus");
user.setGender(Gender.MALE);
user.setMarried(false);
return user;
}
}
接下來創(chuàng)建啟動類Server類
package com.atticus;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TTransportException;
public class Server {
public static final int PORT = 8098;
public void start(){
try {
TServerSocket serverSocket = new TServerSocket(PORT);
UserService.Processor processor = new UserService.Processor(new UserServiceImpl());
//
TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverSocket);
args.processor(processor);//具體的處理實現(xiàn)
args.protocolFactory(new TBinaryProtocol.Factory());//二進(jìn)制
new TThreadPoolServer(args).serve();
} catch (TTransportException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server().start();
}
}
7.客戶端實現(xiàn)
客戶端的實現(xiàn)比較簡單,我們創(chuàng)建一個Client類
package com.atticus;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
public class Client {
public static final String HOST = "localhost";
public static final int PORT = 8098;
public void start(){
try {
TTransport tTransport = new TSocket(HOST, PORT);
UserService.Client client = new UserService.Client(new TBinaryProtocol(tTransport));
tTransport.open();
User user = client.getUser(1);
System.out.println(user.toString());
tTransport.close();
} catch (TTransportException e) {
e.printStackTrace();
} catch (TException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Client().start();
}
}
8.驗證
首先啟動服務(wù)端,再啟動客戶端,可以看到服務(wù)端輸出如下

客戶端輸出如下

證明我們的服務(wù)是正常的。
9.簡單分析
接下來我們簡單分析一下其中的原理,首先來看客戶端,UserService.Client類實現(xiàn)了我們定義的public User getUser(int id){}方法,具體實現(xiàn)可以追蹤到他的父類TServiceClient中的sendBase和receiveBase方法
//發(fā)送請求
private void sendBase(String methodName, TBase<?,?> args, byte type) throws TException {
//寫入方法名(例如getUser),類型(TMessageType.CALL)和一個序列id
oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_));
//序列化
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}
//接收
protected void receiveBase(TBase<?,?> result, String methodName) throws TException {
TMessage msg = iprot_.readMessageBegin();
if (msg.type == TMessageType.EXCEPTION) {//處理異常
TApplicationException x = new TApplicationException();
x.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
if (msg.seqid != seqid_) {//驗證序列號
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID,
String.format("%s failed: out of sequence response: expected %d but got %d", methodName, seqid_, msg.seqid));
}
result.read(iprot_);//反序列化
iprot_.readMessageEnd();
}
可以看到在方法調(diào)用時用一個seqid來保證請求和響應(yīng)能夠?qū)?yīng)上。
再看服務(wù)端的UserService.Processor類,我們可以發(fā)現(xiàn)這樣一行
processMap.put("getUser", new getUser());
也就是說getUser方法在這里被當(dāng)做一個類處理,
public static class getUser<I extends Iface> extends org.apache.thrift.ProcessFunction<I, getUser_args> {
public getUser() {
super("getUser");
}
public getUser_args getEmptyArgsInstance() {
return new getUser_args();
}
protected boolean isOneway() {
return false;
}
@Override
protected boolean handleRuntimeExceptions() {
return false;
}
//真正的處理方法
public getUser_result getResult(I iface, getUser_args args) throws org.apache.thrift.TException {
getUser_result result = new getUser_result();
result.success = iface.getUser(args.id);//直接調(diào)用
return result;
}
}
同時有一個map在processor初始化的時候存儲方法名和類,在請求到達(dá)的時候可以從map中取出相應(yīng)的類,調(diào)用getResult方法。
public boolean process(TProtocol in, TProtocol out) throws TException {
TMessage msg = in.readMessageBegin();
ProcessFunction fn = processMap.get(msg.name);//取出方法名對應(yīng)的類
if (fn == null) {
...
}
fn.process(msg.seqid, in, out, iface);//最終會執(zhí)行g(shù)etResult
return true;
}
10.總結(jié)
有時間再試試不同語言直接的通信吧。