Thrift我之初見

新接觸的項目這邊使用了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)的介紹TYPESIDL,在這里我們舉一個簡單的例子,新建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)如下

項目結(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ù)端輸出

客戶端輸出如下


客戶端輸出

證明我們的服務(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é)

有時間再試試不同語言直接的通信吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容