天天看點

RPC架構之Thrift—實作Go和Java遠端過程調用

官網:https://thrift.apache.org/

1 概覽

+-------------------------------------------+
| Server                                    |
| (single-threaded, event-driven etc)       |
+-------------------------------------------+
| Processor                                 |
| (compiler generated)                      |
+-------------------------------------------+
| Protocol                                  |
| (JSON, compact etc)                       |
+-------------------------------------------+
| Transport                                 |
| (raw TCP, HTTP etc)                       |
+-------------------------------------------+      

我們把他換成自己容易了解的方式:

RPC架構之Thrift—實作Go和Java遠端過程調用

2 分層解析

2.1 Transport(傳輸層)

傳輸層為網絡的讀寫提供了一個簡單的抽象。這使得Thrift能夠将底層傳輸與系統的其他部分解耦(例如,序列化/反序列化)。

接口支援的方法:

  • open
  • close
  • read
  • write
  • flush

可傳輸的底層協定:

  • 檔案傳輸(I/O)
  • HTTP

2.2 Protocol(協定層)

協定層抽象定義了一種機制來将記憶體中的資料結構映射到連線格式。換句話說,協定指定資料類型如何使用底層的Transport對自己進行編碼/解碼。是以,協定實作控制編碼方案并負責(反)序列化。這種意義上的協定的一些例子包括JSON、XML、純文字、壓縮二進制檔案等。

接口支援的方法:

寫(編碼)方法:

  • writeMessageBegin(name, type, seq)
  • writeMessageEnd()
  • writeStructBegin(name)
  • writeStructEnd()
  • writeFieldBegin(name, type, id)
  • writeFieldEnd()
  • writeFieldStop()
  • writeMapBegin(ktype, vtype, size)
  • writeMapEnd()
  • writeListBegin(etype, size)
  • writeListEnd()
  • writeSetBegin(etype, size)
  • writeSetEnd()
  • writeBool(bool)
  • writeByte(byte)
  • writeI16(i16)
  • writeI32(i32)
  • writeI64(i64)
  • writeDouble(double)
  • writeString(string)

讀(解碼)方法:

  • name, type, seq = readMessageBegin()

    readMessageEnd()

  • name = readStructBegin()

    readStructEnd()

  • name, type, id = readFieldBegin()

    readFieldEnd()

  • k, v, size = readMapBegin()

    readMapEnd()

  • etype, size = readListBegin()

    readListEnd()

  • etype, size = readSetBegin()

    readSetEnd()

  • bool = readBool()
  • byte = readByte()
  • i16 = readI16()
  • i32 = readI32()
  • i64 = readI64()
  • double = readDouble()
  • string = readString()

支援的編碼協定:

  • 二進制
  • compact
  • json

2.3 Processor(處理器層)

處理器封裝了從輸入流讀取資料和向輸出流寫入資料的能力。輸入和輸出流由Protocol對象表示。

接口方法樣例:

interface TProcessor {
    bool process(TProtocol in, TProtocol out) throws TException
}      

2.4 Server(應用服務層)

一個伺服器集合了上面描述的所有不同的特性:

  • 建立一個運輸
  • 為傳輸建立輸入/輸出協定
  • 建立基于輸入/輸出協定的處理器
  • 等待傳入的連接配接,并将它們交給處理器

3 thrift安裝

下載下傳頁:https://thrift.apache.org/download

下載下傳Windows版本:

RPC架構之Thrift—實作Go和Java遠端過程調用

下載下傳後可以直接使用,或者放在固定檔案設定環境變量

RPC架構之Thrift—實作Go和Java遠端過程調用

4 Go使用thrift

4.1 項目結構和依賴安裝

RPC架構之Thrift—實作Go和Java遠端過程調用

依賴安裝:

go get -u github.com/apache/thrift      

4.2 編寫thrift檔案并編譯

namespace go hello

struct HelloReq {
    1: string msg;
}

struct HelloResp {
    1: string msg;
}

service HelloService {
    //傳回值類型 方法名(參數序号:參數類型 參數名);
    HelloResp Hello(1: HelloReq req);
}      

編譯:

thrift -r --gen go hello.thrift      

4.3 服務端代碼

type HelloServiceImpl struct {
}

func (e *HelloServiceImpl) Hello(ctx context.Context, req *hello.HelloReq) (*hello.HelloResp, error) {
   fmt.Printf("message from client: %v\n", req.GetMsg())
   res := &hello.HelloResp{
      Msg: "Hi Client ~",
   }
   return res, nil
}


func main() {
   transport, err := thrift.NewTServerSocket(":9898")
   if err != nil {
      panic(err)
   }
   handler := &HelloServiceImpl{}
   processor := hello.NewHelloServiceProcessor(handler)
   //也可以選擇Framed傳輸協定,但是必須確定服務端和用戶端的傳輸協定一緻
   transportFactory := thrift.NewTBufferedTransportFactory(1024)
   //應對Java用戶端
   //factory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
   //可以選擇任意一種編碼協定,但是必須確定服務端和用戶端的編碼協定一緻
   protocolFactory := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}) //布爾參數strictRead, strictWrite,讀和寫時是否加入版本校驗。
   server := thrift.NewTSimpleServer4(
      processor,
      transport,
      transportFactory,
      //factory,
      protocolFactory,
   )
   if err := server.Serve(); err != nil {
      panic(err)
   }
}      

4.4 用戶端代碼

func main() {
    transportFactory := thrift.NewTBufferedTransportFactory(1024)
    //可以選擇任意一種通信協定,但是必須確定服務端和用戶端的通信協定一緻
    protocolFactory := thrift.NewTBinaryProtocolFactoryConf(&thrift.TConfiguration{}) //布爾參數strictRead, strictWrite,讀和寫時是否加入版本校驗。
    //Java服務端
    //factory := thrift.NewTCompactProtocolFactoryConf(&thrift.TConfiguration{})

    transport:= thrift.NewTSocketConf("127.0.0.1:9898",&thrift.TConfiguration{})
    useTransport, err := transportFactory.GetTransport(transport)
    client := hello.NewHelloServiceClientFactory(useTransport, protocolFactory)
    if err := transport.Open(); err != nil {
        fmt.Fprintln(os.Stderr, "Error opening socket to 127.0.0.1:9898", " ", err)
        os.Exit(1)
    }
    defer transport.Close()
    req := &hello.HelloReq{Msg: "Hello Server ~"}
    res, err := client.Hello(context.Background(), req)
    if err != nil {
        log.Println("Echo failed:", err)
        return
    }
    log.Println("response:", res.Msg)
}      

4.5 驗證

RPC架構之Thrift—實作Go和Java遠端過程調用

5 Java使用thrift

5.1 項目結構和依賴

RPC架構之Thrift—實作Go和Java遠端過程調用

依賴:

<dependency>
    <groupId>org.apache.thrift</groupId>
    <artifactId>libthrift</artifactId>
    <version>0.15.0</version>
</dependency>      

5.2 編寫thrift檔案并編譯

namespace java hello

struct HelloReq {
    1: string msg;
}

struct HelloResp {
    1: string msg;
}

service HelloService {
    HelloResp Hello(1: HelloReq req);
}      

編譯:

thrift -r --gen java hello.thrift      

​注意:編譯完成之後需要将生成的.java檔案放到項目結構中的對應位置。​

5.3 服務端代碼

public class ThriftServer {

    public static void main(String[] args) throws TTransportException {
        //設定伺服器端口  TNonblockingServerSocket-非堵塞服務模型
        TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
        //參數設定
        THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4);
        //處理器
        //PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl());
        HelloService.Processor<HelloServiceImpl> processor = new HelloService.Processor<>(new HelloServiceImpl());

        arg.protocolFactory(new TCompactProtocol.Factory());
        arg.transportFactory(new TFramedTransport.Factory());
        arg.processorFactory(new TProcessorFactory(processor));

        TServer server = new THsHaServer(arg);
        System.out.println("Thrift 服務端啟動成功");
        server.serve();
    }
}

//Handler
class HelloServiceImpl implements HelloService.Iface {
    @Override
    public HelloResp Hello(HelloReq req) throws TException {
        System.out.println(req.msg);
        return new HelloResp("Hello Client ~");
    }
}      

5.4 用戶端代碼

public class ThriftClient {

    public static void main(String[] args) throws TException {
        TTransport transport = new TFramedTransport(new TSocket("localhost", 8899), 600);
        TProtocol protocol = new TCompactProtocol(transport);
        HelloService.Client client = new HelloService.Client(protocol);
        transport.open();
        String msg = client.Hello(new HelloReq("Hi Server ~")).msg;
        System.out.println(msg);
        transport.close();
    }
}      

6 使用thrift進行Java和Go的遠端通信

  • 原則:用戶端與服務端的協定一緻即可

例如Java做用戶端:

public class ThriftClientGo {

    public static void main(String[] args) throws TTransportException {
        TTransport transport = new TFramedTransport(new TSocket("127.0.0.1", 9898), 8000);
        TBinaryProtocol binaryProtocol = new TBinaryProtocol(transport);
        HelloService.Client client = new HelloService.Client(binaryProtocol);
        try {
            transport.open();
            String msg = client.Hello(new HelloReq("Hi Server~")).msg;
            System.out.println(msg);
        }catch (Exception ex){
            throw new RuntimeException(ex.getMessage(),ex);
        }finally {
            transport.close();
        }
    }
}      
參考文章:

https://thrift.apache.org/docs/concepts.html

https://thrift.apache.org/docs/concepts.html