thrift 入門(2/2)

上接:thrift 入門(1/2)
PS:我也不想拆,但是放一起文章太長無法發(fā)布仗嗦。惭笑。侣姆。


四、thrift 入門

4.1 小試牛刀

首先沉噩,我們還是先用 thrift 實(shí)現(xiàn)一下前文中的需求。

按如下步驟操作:

  1. 安裝 thrift(安裝方式請自行百度柱蟀,最新版本或稍老的版本都可以川蒙,我用的 0.9.3 )。
  2. 編寫 demo.thrift 文件长已,內(nèi)容如下:
// demo.thrift
namespace java com.ann.javas.frameworks.thrifts.demo.beans


enum Gender {
    BOY = 1,
    GIRL = 2,
}


struct UserInfo {
    1: required string name,
    3: required Gender gender,
    6: required i32 weight,
}

service DemoService {

    string sayHi(1:required UserInfo userInfo);

    string locate();

    string adsRecommendation();
}
  1. 打開終端畜眨,cd 到 demo.thrift 所在文件目錄執(zhí)行 thrift -r --gen java demo.thrift昼牛,thrift 會(huì)編譯 demo.thrift 生成三個(gè) java 源文件(分別是:DemoService.java、UserInfo.java康聂、Gender.java贰健,源碼太多不在這兒貼了)到 ./gen-java 目錄下。
  2. maven 引入 libthrift 包恬汁,版本須與前面安裝的 thrift 版本保持一致伶椿。
  3. 把該文件拷貝到工程目錄。
  4. 然后創(chuàng)建 3 個(gè) java 源文件:
  • DemoServiceImpl.java氓侧,實(shí)現(xiàn) thrift 定義的接口脊另。
  • Demo1Server.java,服務(wù)端约巷。
  • Demo1Client.java偎痛,客戶端。
// DemoServiceImpl.java
package com.ann.javas.frameworks.thrifts.demo.impl;

import com.ann.javas.frameworks.thrifts.demo.beans.DemoService;
import com.ann.javas.frameworks.thrifts.demo.beans.Gender;
import com.ann.javas.frameworks.thrifts.demo.beans.UserInfo;

import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * Created by liyanling on 2018/9/9.
 */
public class DemoServiceImpl implements DemoService.Iface{
  private static Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);

  private final static String SAY_HI_TEMPLATE = "Hi,%s%s!%s";

  private final static List<String> CITIES = Arrays.asList("上海", "北京", "廣州", "成都", "內(nèi)蒙古", "香港", "河北", "云南");

  private final static List<String> ADS = Arrays.asList("坐紅旗車独郎,走中國路", "要想皮膚好踩麦,早晚用大寶", "喝匯源果汁,走健康之路", "送禮就送腦白金",
                                                        "飄柔氓癌,就是這么自信");


  @Override
  public String sayHi(UserInfo userInfo) throws TException {
    String name = userInfo.getName();
    String nameSuffix = Gender.BOY == userInfo.getGender() ? "先生" : "女士";
    String weightNotice = userInfo.getWeight() >= 200 ? "你該減肥啦~" : "";
    return String.format(SAY_HI_TEMPLATE, name, nameSuffix, weightNotice);
  }

  @Override
  public String locate() throws TException {
    int citySize = CITIES.size();
    int randomIndex = Math.abs((new Random(System.currentTimeMillis())).nextInt()) % citySize;
    return CITIES.get(randomIndex);
  }

  @Override
  public String adsRecommendation() throws TException {
//    logger.info("sleep..........");
//    try {
//      Thread.sleep(10000);
//    } catch (InterruptedException e) {
//      e.printStackTrace();
//    }
//    logger.info("awake..........");
    int adsSize = ADS.size();
    int randomIndex = Math.abs((new Random(System.currentTimeMillis())).nextInt()) % adsSize;
    return ADS.get(randomIndex);
  }
}

// Demo1Server.java
package com.ann.javas.frameworks.thrifts.demo.impl;


import com.ann.javas.frameworks.thrifts.demo.beans.DemoService;

import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by liyanling on 2018/9/9.
 */
public class Demo1Server {
  private static Logger logger = LoggerFactory.getLogger(Demo1Server.class);

  public static final int PORT = 9081;

  public static final int CLIENT_TIMEOUT = 100000;


  public static void main(String[] args) {
    Demo1Server server = new Demo1Server();
    server.startServer();
  }

  /**
   * 單線程服務(wù)模型:TSimpleServer + TServerSocket
   */
  private void startServer() {

    try {

      int count = 1;

      logger.info("{}:new 一個(gè) TServerSocket 實(shí)例谓谦,指定端口號(hào)為:{} 超時(shí)時(shí)間:{}", count++, PORT, CLIENT_TIMEOUT);
      TServerTransport tServerTransport = new TServerSocket(PORT, CLIENT_TIMEOUT);

      TSimpleServer.Args args = new TSimpleServer.Args(tServerTransport);
      logger.info("{}:服務(wù)端初始化 {} 參數(shù)...", count++, "TSimpleServer.Args");

      TBinaryProtocol.Factory proFactory = new TBinaryProtocol.Factory(true, true);
      args.protocolFactory(proFactory);
      logger.info("{}:設(shè)置協(xié)議工廠為 TBinaryProtocol.Factory,即:使用 TBinaryProtocol 協(xié)議", count++);

      TProcessor processor = new DemoService.Processor(new DemoServiceImpl());
      args.processor(processor);
      logger.info("{}:設(shè)置 processor 為 {} 實(shí)例", count++, "UserServiceImpl");

      TServer server = new TSimpleServer(args);
      logger.info("{}:{} 實(shí)例顽铸,服務(wù)啟動(dòng)", count++, "TSimpleServer");
      server.serve();

    } catch (TTransportException e) {
      e.printStackTrace();
    }
  }
}

// Demo1Client.java
package com.ann.javas.frameworks.thrifts.demo.impl;

import com.ann.javas.frameworks.thrifts.demo.beans.DemoService;
import com.ann.javas.frameworks.thrifts.demo.beans.Gender;
import com.ann.javas.frameworks.thrifts.demo.beans.UserInfo;

import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Scanner;

/**
 * Created by liyanling on 2018/9/9.
 */
public class Demo1Client {
  private static Logger logger = LoggerFactory.getLogger(Demo1Client.class);

  public static String LOCAL_HOST = "127.0.0.1";
  public static Scanner scanner;


  public static void main(String[] args){
    scanner = new Scanner(System.in);
    callServer(LOCAL_HOST, Demo1Server.PORT);
    scanner.close();
  }


  /**
   * TSocket
   */
  public static void callServer(String IP, int PORT) {

    try {
      int count = 1;

      logger.info("{}:客戶端茁计,創(chuàng)建 TSocket 實(shí)例 (IP:{},PORT:{})...", count++, IP, PORT);
      TTransport transport = new TSocket(LOCAL_HOST, PORT);
      transport.open();
      logger.info("{}:打開 socket 連接", count++);

      TProtocol protocol = new TBinaryProtocol(transport);
      logger.info("{}:創(chuàng)建 TBinaryProtocol 實(shí)例", count++);

      DemoService.Iface client = new DemoService.Client(protocol);
      logger.info("{}:創(chuàng)建 Demo1Service.Client 實(shí)例", count++);

      // 測試 locate
      String locateResult = client.locate();
      logger.info("{}:locate 返回值 {}",count++,locateResult);

      // 測試 adsRecommendation
      String adsRecommendationResult = client.adsRecommendation();
      logger.info("{}:adsRecommendation 返回值 {}",count++,adsRecommendationResult);

      // 測試 sayHi
      logger.info("{}:請輸入姓名:", count++);
      String name = scanner.nextLine();
      logger.info("{}:請輸入性別(GIRL / BOY):", count++);
      String gender = scanner.nextLine();
      logger.info("{}:請輸入體重(單位:斤):", count++);
      Integer weight = Integer.valueOf(scanner.nextLine());

      UserInfo userInfo = new UserInfo();
      userInfo.setName(name);
      userInfo.setGender(Gender.valueOf(gender));
      userInfo.setWeight(weight);

      String sayHiResult = client.sayHi(userInfo);
      logger.info("{}:sayHi 返回值 {}",count++,sayHiResult);


      transport.close();
      logger.info("{}:關(guān)閉 socket 連接", count++);

    } catch (Throwable t) {
      t.printStackTrace();
    }

  }
}

分別測試 sayHi谓松、locate星压、adsRecommendation 功能,輸出日志如下:

// Demo1Server 日志
00:35:50.229 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Server - 1:new 一個(gè) TServerSocket 實(shí)例鬼譬,指定端口號(hào)為:9081 超時(shí)時(shí)間:100000
00:35:50.249 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Server - 2:服務(wù)端初始化 TSimpleServer.Args 參數(shù)...
00:35:50.249 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Server - 3:設(shè)置協(xié)議工廠為 TBinaryProtocol.Factory娜膘,即:使用 TBinaryProtocol 協(xié)議
00:35:50.259 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Server - 4:設(shè)置 processor 為 UserServiceImpl 實(shí)例
00:35:50.260 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Server - 5:TSimpleServer 實(shí)例,服務(wù)啟動(dòng)

// Demo1Client 日志
00:35:53.312 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 1:客戶端优质,創(chuàng)建 TSocket 實(shí)例 (IP:127.0.0.1竣贪,PORT:9081)...
00:35:53.325 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 2:打開 socket 連接
00:35:53.326 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 3:創(chuàng)建 TBinaryProtocol 實(shí)例
00:35:53.328 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 4:創(chuàng)建 Demo1Service.Client 實(shí)例
00:35:53.351 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 5:locate 返回值 香港
00:35:53.363 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 6:adsRecommendation 返回值 喝匯源果汁,走健康之路
00:35:53.363 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 7:請輸入姓名:
超人
00:36:21.899 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 8:請輸入性別(GIRL / BOY):
BOY
00:36:24.783 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 9:請輸入體重(單位:斤):
230
00:36:27.376 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 10:sayHi 返回值 Hi,超人先生!你該減肥啦~
00:36:27.376 [main] INFO  com.ann.javas.frameworks.thrifts.demo.impl.Demo1Client - 11:關(guān)閉 socket 連接

這個(gè)例子主要看 client 端日志巩螃,server 端的不重要演怎,一般都是 deamon 啟動(dòng)的。

不需要了解任何原理避乏,光看這個(gè)用法你就會(huì)發(fā)現(xiàn)爷耀,thrift 通過某種方式屏蔽了用戶對“序列化與反序列化”這個(gè)動(dòng)作的感知,用戶只需要:

  1. 按照某種規(guī)則拍皮、使用 xxx.thrift 文件定義 RPC 接口和數(shù)據(jù)結(jié)構(gòu)歹叮。
  2. 執(zhí)行命令生成對應(yīng)的 java 代碼跑杭。
  3. 然后在此基礎(chǔ)上,實(shí)現(xiàn)你的業(yè)務(wù)邏輯就可以了咆耿。

4.2 序列化分析

關(guān)于 thrift 序列化的具體實(shí)現(xiàn)德谅,可以在前面 demo 的基礎(chǔ)上,通過串調(diào)用鏈路的方式進(jìn)行了解萨螺。

需要進(jìn)行序列化和反序列化操作的無非就這么 4 個(gè)場景:

  1. client->server窄做,client 序列化參數(shù)。
  2. client->server屑迂,server 反序列化參數(shù)浸策,處理請求。
  3. server->client惹盼,server 序列化返回值庸汗。
  4. server->client,client 反序列化返回值手报。

以 “client 序列化參數(shù)” 為例進(jìn)行簡單分析蚯舱,過程如下:

  1. 以 sayHi() 為例,入口在 Demo1Client.java 客戶端代碼:
// Demo1Client.java
...
public class Demo1Client {
  ...
  public static void callServer(String IP, int PORT) {
      ...
       TProtocol protocol = new TBinaryProtocol(transport);
      ...
      DemoService.Iface client = new DemoService.Client(protocol);
      ...
      String sayHiResult = client.sayHi(userInfo);
      ...
  }
}
  1. Command+B 點(diǎn) sayHi 進(jìn)到 DemoService.java 中的 Iface 接口定義掩蛤,這是編譯器根據(jù) IDL 自動(dòng)生成的代碼:
// DemoService.java
// Autogenerated by Thrift Compiler
public class DemoService {
  public interface Iface {
    public String sayHi(UserInfo userInfo) throws org.apache.thrift.TException;
    ...
  }
  ...
}
  1. 如果你用的 IDE 是 idea 枉昏,在左側(cè)能看到一個(gè)綠色的小圓圈,鼠標(biāo)懸浮在上面揍鸟,會(huì)看到 Iface 接口定義的 sayHi() 方法有兩個(gè)實(shí)現(xiàn)類:
  • ...beans.DemoService.Client兄裂,客戶端實(shí)現(xiàn),其實(shí)是 DemoService 的內(nèi)部類阳藻,編譯器自動(dòng)生成的晰奖。
  • ...impl.DemoServiceImpl,服務(wù)端實(shí)現(xiàn)腥泥,繼承自 DemoService.Iface匾南,用于實(shí)現(xiàn)服務(wù)端業(yè)務(wù)邏輯。


    Iface.sayHi 的兩個(gè)實(shí)現(xiàn)
  1. 點(diǎn)進(jìn)客戶端實(shí)現(xiàn)蛔外,也就是 DemoService 的內(nèi)部類 DemoService.Client蛆楞,然后你會(huì)看到下面這段代碼:
// DemoService.java
// Autogenerated by Thrift Compiler
...
public class DemoService {
  ...
  public static class Client extends org.apache.thrift.TServiceClient implements Iface {
    ...
    public String sayHi(UserInfo userInfo) throws org.apache.thrift.TException
    {
      send_sayHi(userInfo);
      return recv_sayHi();
    }
    ...
  }
  ...
}

很明顯,send_sayHi(userInfo) 是發(fā)送請求到服務(wù)端夹厌,return recv_sayHi() 是從服務(wù)端獲取返回值豹爹。

  1. 本例需關(guān)注 send_sayHi(userInfo) 實(shí)現(xiàn):
  • sayHi_args 又是個(gè)內(nèi)部類,定義了 sayHi() 方法參數(shù)的數(shù)據(jù)結(jié)構(gòu)矛纹,其唯一成員變量就是 UserInfo 類實(shí)例帅戒,對應(yīng)的是 demo.thrift 中定義的 string sayHi(1:required UserInfo userInfo);
  • 先初始化一個(gè) sayHi_args 類實(shí)例崖技,準(zhǔn)備好參數(shù)逻住。
  • sendBase(...) 是 TServiceClient 提供的方法,調(diào)用時(shí)指明方法名和方法參數(shù)實(shí)例迎献。
  • 在本例中瞎访,方法名即:"sayHi",參數(shù)即:sayHi_args 實(shí)例吁恍。
// DemoService.java
// Autogenerated by Thrift Compiler
...
public class DemoService {
  ...
  public static class Client extends org.apache.thrift.TServiceClient implements Iface {
    ...
    public String sayHi(UserInfo userInfo) throws org.apache.thrift.TException
    {
      send_sayHi(userInfo);
      return recv_sayHi();
    }
    public void send_sayHi(UserInfo userInfo) throws org.apache.thrift.TException
    {
      sayHi_args args = new sayHi_args();
      args.setUserInfo(userInfo);
      sendBase("sayHi", args);
    }
    ...
  }
  ...
}
  1. 進(jìn)到 TServiceClient 看 sendBase(...) 具體實(shí)現(xiàn):
...
public abstract class TServiceClient {
  ...
  protected void sendBase(String methodName, TBase<?, ?> args) throws TException {
    this.sendBase(methodName, args, (byte)1);
  }
  ...
  private void sendBase(String methodName, TBase<?, ?> args, byte type) throws TException {
    this.oprot_.writeMessageBegin(new TMessage(methodName, type, ++this.seqid_));
    args.write(this.oprot_);
    this.oprot_.writeMessageEnd();
    this.oprot_.getTransport().flush();
  }
  ...
}

可以看到 sendBase(...) 是按照:MessageBegin扒秸、MessageBody、MessageEnd 的順序?qū)⑾懭?outputStream 冀瓦。

  • writeMessageBegin 和 writeMessageEnd 操作伴奥,不同協(xié)議(TProtocol 子類)的實(shí)現(xiàn)各有不同。
  • writeMessageBody 則由方法的參數(shù)結(jié)構(gòu)體自己實(shí)現(xiàn)翼闽,也就是 args.write(this.oprot_); 這一行拾徙。
  1. 跟進(jìn) args.write(this.oprot_); 里面,其實(shí)還是編譯器根據(jù) IDL 定義自動(dòng)生成的代碼:
// DemoService.java
// Autogenerated by Thrift Compiler
...
public class DemoService {
  ...
    private static class sayHi_argsStandardScheme extends StandardScheme<sayHi_args> {
      ...
      public void write(org.apache.thrift.protocol.TProtocol oprot, sayHi_args struct) throws org.apache.thrift.TException {
        struct.validate();
        oprot.writeStructBegin(STRUCT_DESC);
        if (struct.userInfo != null) {
          oprot.writeFieldBegin(USER_INFO_FIELD_DESC);
          struct.userInfo.write(oprot);
          oprot.writeFieldEnd();
        }
        oprot.writeFieldStop();
        oprot.writeStructEnd();
      }
    }
  ...
}
  • 在 write 操作前感局,有一個(gè) validate 校驗(yàn)尼啡,主要是校驗(yàn)參數(shù)格式(required 字段非null)。
  • 剩下的應(yīng)該不用多介紹了询微,差不多的規(guī)則和實(shí)現(xiàn)崖瞭,到這兒往下跟就比較簡單了,不再貼代碼過來(有點(diǎn)多)撑毛。
  1. 也就是說书聚,一個(gè)結(jié)構(gòu)體大致會(huì)按照這個(gè)模板進(jìn)行序列化:
- ${Message Begin}
  - Struct.validate()
  - ${struct Begin}
    - ${Field1 Begin}
    - ${Feild1 Body}
    - ${Field1 End}
    - ${Field2 Begin}
    - ${Feild2 Body}
    - ${Field2 End}
    - ......
    - ${Field Stop}
  - ${struct End}
- ${Message End}

PS:Struct 和 Feild 是可以嵌套的。
例如:sayHi_args 的 Feild1 是 userInfo藻雌,則 sayHi_args 的 Feild1Body 實(shí)際上是包含了一個(gè) UserInfo Struct 內(nèi)容雌续。
emm......有點(diǎn)繞,追幾遍調(diào)用鏈就能明白了蹦疑。

反序列化的流程不在這兒貼分析流程了西雀,感興趣的同學(xué)可以自己跟一下。

4.3 序列化方案初探

現(xiàn)在對我們前面的分析做個(gè)總結(jié)歉摧,thrift 序列化方案有3個(gè)關(guān)鍵點(diǎn):

  1. libthrift 包
  2. thrift IDL 語法規(guī)則
  3. thrift 編譯器
序列化方案關(guān)鍵點(diǎn)

序列化和反序列化在 thrift 中是通過 “協(xié)議” 來體現(xiàn)的艇肴。

libthrift

  • 首先,libthrift 中有一個(gè)抽象類 TProtocol 叁温,定義了一系列 readXXX 和 writeXXX 的方法再悼,包括前面提到過的 Message、Struct膝但、Feild 和 String冲九、Double、Map、List莺奸、I32丑孩、I64、Binary 等數(shù)據(jù)結(jié)構(gòu)的讀寫方法灭贷。
  • TProtocol 有多個(gè)實(shí)現(xiàn)温学,如:TBinaryProtocol 二進(jìn)制格式、TCompactProtocol 壓縮格式甚疟、TJSONProtocol JSON 格式 等仗岖。
  • 通常為節(jié)約帶寬,提高傳輸效率览妖,一般使用二進(jìn)制類型的傳輸協(xié)議(TBinaryProtocol)轧拄;但有時(shí)會(huì)還是會(huì)使用基于文本類型的協(xié)議,需根據(jù)項(xiàng)目/產(chǎn)品中的實(shí)際需求來確定讽膏。
  • 對 TProtocol 協(xié)議的具體實(shí)現(xiàn)感興趣的同學(xué)檩电,可以通過跟蹤調(diào)用鏈的方式進(jìn)行分析,然后使用 Wireshark 等工具抓包數(shù)據(jù)進(jìn)行驗(yàn)證桅打。

若 thrift 提供的協(xié)議不能滿足要求是嗜,用戶還可以基于 TProtocol 來實(shí)現(xiàn)定制化協(xié)議。

IDL

  • thrift 定義了一套 IDL 語法規(guī)則挺尾,可參見官方文檔:Thrift interface description language鹅搪。
  • 用戶需嚴(yán)格按照 thrift IDL 語法規(guī)則來定義 RPC 的接口和數(shù)據(jù)結(jié)構(gòu)。

編譯器

  • 指定需要編譯的文件及生成代碼的語言遭铺,執(zhí)行 thrift 編譯命令丽柿,thrift 編譯器會(huì)對 IDL 文件進(jìn)行編譯并輸出指定語言的源碼文件。
  • 例如:
    1. IDL 定義的 service 會(huì)生成一個(gè)單獨(dú)的 Service 類魂挂,Service 類內(nèi)部的 Iface 接口包含了 IDL service 定義的所有方法甫题。
    2. IDL service 定義的每一個(gè)方法,其入?yún)⒑头祷刂刀紩?huì)自動(dòng)生成單獨(dú)的 Service 內(nèi)部類(function_args 和 function_result)涂召,并提供序列化/反序列化方法(read坠非、write)。
    3. IDL 中的 struct果正、enum炎码、exception 等定義,也會(huì)生成相應(yīng)的類秋泳,并提供 read潦闲、write 方法。

這三個(gè)要點(diǎn)結(jié)合起來迫皱,就構(gòu)成了 thrift 序列化和反序列化的整套機(jī)制歉闰。


五、thrift 服務(wù)框架

至此,thrift 的兩大關(guān)鍵點(diǎn)已經(jīng)搞定一個(gè):
1和敬、協(xié)議(序列化和反序列化)
2凹炸、服務(wù)框架(網(wǎng)絡(luò)通信)

協(xié)議(即:序列化和反序列化)反映的是“數(shù)據(jù)傳輸格式”,而對于一個(gè) RPC 框架來說概龄,再上層就是數(shù)據(jù)傳輸方式和服務(wù)模型了还惠。

5.1 demo 分析

在前面的例子中,哪一段代碼和“服務(wù)模型”或“數(shù)據(jù)傳輸方式”有關(guān)呢私杜?

...
public class Demo1Server {
  ...
  private void startServer() {
    ...
    TServerTransport tServerTransport = new TServerSocket(PORT, CLIENT_TIMEOUT);
    TSimpleServer.Args args = new TSimpleServer.Args(tServerTransport);
    ...
    TServer server = new TSimpleServer(args);
    server.serve();
    ...
  }
  ...
}
  • 先看哪一句是用來啟動(dòng)服務(wù)的,顯然是 server.serve(); 救欧。
  • 再往上 TServer server = new TSimpleServer(args); 定義了 server 衰粹,是一個(gè) TSimpleServer 實(shí)例狰腌,這里選定的服務(wù)模型就是 TSimpleServer锐峭。
  • 而初始化 server 實(shí)例時(shí)用到的 TServerTransport 就是其傳輸方式朱巨。

這次我們從上往下看适滓,先講服務(wù)模型暂刘。

5.2 TServer 服務(wù)模型

thrift 提供的服務(wù)模型可分為:單線程遥金、多線程逼裆、事件驅(qū)動(dòng) 3類蓝仲。從另一個(gè)角度也可以劃分為:阻塞服務(wù)模型办成、非阻塞服務(wù)模型 2類泡态。

thrift 的服務(wù)模型可以拿來對標(biāo)我們學(xué)習(xí)網(wǎng)絡(luò)編程時(shí)學(xué)到的網(wǎng)絡(luò)通信模型(IO模型)。

常見的有:

  • TSimpleServer - 簡單的單線程服務(wù)模型迂卢,常用于測試某弦。
  • TThreadPoolServer - 多線程、阻塞 IO 服務(wù)模型而克。
  • TNonblockingServer - 多線程靶壮、非阻塞 IO 服務(wù)模型。
  • 以及 TThreadedSelectorServer 和 THsHaServer员萍。

TSimpleServer

前例中腾降,Server 端使用的服務(wù)模型是 TSimpleServer 簡單的單線程服務(wù)模型,其特點(diǎn)是:

  • 只有一個(gè)工作線程碎绎,循環(huán)監(jiān)聽新請求的到來螃壤,并完成請求的處理。
  • 這種服務(wù)模型的優(yōu)點(diǎn)是使用方法簡單混卵、便于理解映穗。
  • 但是一次只能接收和處理一個(gè) socket 連接,效率比較低幕随。
  • 主要用于測試或演示 thrift 的工作過程蚁滋,在實(shí)際開發(fā)中很少會(huì)用到。

可以按照下面的步驟簡單做個(gè)小測試:

  1. 在 DemoServiceImpl 的某個(gè)方法里,加一段 Thread.sleep(xxxx) 辕录。
  2. 啟動(dòng)服務(wù)并建立一個(gè) clientA 連接并發(fā)起請求睦霎,當(dāng)前請求會(huì)夯在 sleep 的地方,不給客戶端返回結(jié)果走诞。
  3. 然后再新建一個(gè) clientB 連接并發(fā)起請求副女。
  4. 你會(huì)發(fā)現(xiàn),若 clientA 的請求不處理完蚣旱,server 端不會(huì)與 clientB 建立連接碑幅,clientB 需要一直等著,直到 clientA 的請求被處理完并返回結(jié)果塞绿。
TSimpleServer 示意圖

TThreadPoolServer

TThreadPoolServer 靠引入 “工作線程池” 解決了 TSimpleServer 不支持并發(fā)和多連接的問題沟涨,其特點(diǎn)是:

  • 使用一個(gè)專有線程負(fù)責(zé)監(jiān)聽端口、接受連接异吻,具體的業(yè)務(wù)處理則由一個(gè)工作線程池中的子線程來完成裹赴。
  • 優(yōu)點(diǎn)是將監(jiān)聽請求和業(yè)務(wù)處理兩項(xiàng)工作做了拆分,在并發(fā)量較大時(shí)新連接也能夠被及時(shí)接受诀浪。
  • 缺點(diǎn)是服務(wù)處理能力受限于線程池的工作能力棋返,當(dāng)并發(fā)請求大于線程池中的線程數(shù)時(shí),新請求還是要排隊(duì)等待雷猪。
TThreadPoolServer 示意圖

TThreadServer 實(shí)現(xiàn)上還有一些細(xì)節(jié)沒畫在圖上睛竣,比如:
線程池滿如何處理?
有無異常重試機(jī)制春宣?
有無優(yōu)雅停服機(jī)制酵颁?
......
感興趣的同學(xué)可以讀源碼了解一下。

TNonblockingServer
TNonblockingServer 使用非阻塞 I/O 解決了 TSimpleServer 一個(gè)客戶端阻塞其他所有客戶端的問題月帝,其特點(diǎn)是:

  • 服務(wù)啟動(dòng)時(shí)開一個(gè) SelectAcceptThread 線程躏惋,該線程內(nèi)部通過引入 java.nio.channels.Selector 選擇器(Java NIO 核心組件之一)實(shí)現(xiàn)單線程管理多個(gè)網(wǎng)絡(luò)連接的功能。
  • 優(yōu)點(diǎn)是服務(wù)端可以同時(shí)處理多個(gè)客戶端連接請求嚷辅,而不需要像 TSimpleServer 那樣排隊(duì)等待簿姨。
  • 缺點(diǎn)是業(yè)務(wù)處理還是單線程順序執(zhí)行,在業(yè)務(wù)處理比較復(fù)雜簸搞、耗時(shí)較長的場景下扁位,執(zhí)行效率也很難提升。

我先去補(bǔ)補(bǔ) Selector 的課再回來補(bǔ)這里的圖......

THsHaServer
THsHaServer 是 TNonblockingServer 的子類趁俊,可以簡單粗暴的理解成 TNonblockingServer 和 TThreadPoolServer 的結(jié)合版本:

  • THsHaServer 在 TNonblockingServer 的基礎(chǔ)上 域仇,引入了 TThreadPoolServer 的工作線程池,用于進(jìn)行業(yè)務(wù)處理寺擂。
  • 優(yōu)點(diǎn)是將業(yè)務(wù)處理過程扔到工作線程池處理暇务,主線程可以直接返回進(jìn)行下一次循環(huán)操作泼掠,效率大大提升。
  • 缺點(diǎn)是垦细,由于主線程需要完成所有 socekt 的監(jiān)聽及數(shù)據(jù)讀寫工作择镇,當(dāng)并發(fā)請求數(shù)較大,且發(fā)送數(shù)據(jù)量較多時(shí)括改,監(jiān)聽 socket 上的新連接請求不能被及時(shí)接受腻豌。(TNonblockingServer 也有這個(gè)問題)

TThreadedSelectorServer
TThreadedSelectorServer 可以簡單看做是 THsHaServer 的擴(kuò)展(實(shí)際上并不是),其特點(diǎn)是:

  • 在 THsHaServer 的基礎(chǔ)上嘱能,再引入一個(gè) SelectorThread 線程池吝梅,以分散網(wǎng)絡(luò)IO。
  • 并提供一個(gè) SelectorThreadLoadBalancer 用作 SelectorThread 分發(fā)焰檩。

最后附上一張 TServer 精“剪”類圖:


TServer 精“剪”類圖

5.3 TTransport 傳輸方式

傳輸方式這部分憔涉,首先請同學(xué)們區(qū)分 TServerTransport 和 TTransport:

  • TServerTransport 定義的是“以何種方式監(jiān)聽和處理請求”;初始化 Server 服務(wù)端實(shí)例的時(shí)候需要明確指定析苫;提供 listen()、accept()穿扳、interrupt() 等功能衩侥。
  • TTransport 定義的是“以何種形式在網(wǎng)絡(luò)上傳輸數(shù)據(jù)”,底層實(shí)現(xiàn)是對 ServerSocket 做了封裝矛物;初始化 Client 客戶端實(shí)例的時(shí)候需要明確指定茫死;提供 open()/close()、read()/write()履羞、peek()峦萎、flush() 等功能。

TTransport 和 TServerTransport 需要與 TServer 搭配使用:

  • 服務(wù)端為阻塞式服務(wù)時(shí)忆首,使用 TServerSocket 爱榔;客戶端使用 TSocket 配合。
  • 服務(wù)端為非阻塞式服務(wù)時(shí)糙及,使用 TNonblockingServerSocket 详幽;客戶端使用 TFramedTransport 封裝 TSocket 配合。

這里 TTransport 就是我們說的“傳輸方式”浸锨,即:how is transmitted唇聘?

  • TSocket,阻塞 IO 傳輸柱搜。
  • TFramedTransport迟郎,非阻塞 I/O,按幀/塊傳輸(支持定長數(shù)據(jù)發(fā)送和接收)聪蘸。
  • TMemoryTransport宪肖,內(nèi)存IO表制。
  • TFileTransport,文件格式傳輸匈庭,不提供java的實(shí)現(xiàn)夫凸。
  • TZlibTransport,zlib壓縮傳輸阱持,不提供java的實(shí)現(xiàn)夭拌。
  • TNonblockingTransport,非阻塞式I/O衷咽,用于構(gòu)建異步客戶端鸽扁。
    ......

不同語言對上述 Transport 的支持是不一樣的。
我個(gè)人只用過 TSocket 和 TFramedTransport镶骗,有點(diǎn)心虛桶现,慫...

TServerTransport 類圖
TTransport 類圖

5.4 小結(jié)

最后總結(jié)一下老生常談的 thrift 分層:

  • Transport 傳輸層:定義了網(wǎng)絡(luò)數(shù)據(jù)的傳輸方式,負(fù)責(zé)完成數(shù)據(jù)的網(wǎng)絡(luò)傳輸鼎姊。
  • TProtocol 協(xié)議層:定義了網(wǎng)絡(luò)傳輸數(shù)據(jù)的格式骡和,負(fù)責(zé)完成“應(yīng)用數(shù)據(jù)-網(wǎng)絡(luò)可傳輸?shù)臄?shù)據(jù)”的組裝和拆解(序列化和反序列化)。
  • TProcessor:不重要相寇,自動(dòng)生成的處理器慰于。
  • Server 服務(wù)層:把 Transport、Protocol唤衫、TProcessor 組合在一起婆赠,將服務(wù)運(yùn)行起來,在指定端口監(jiān)聽并處理客戶端請求佳励。
thrift 層次圖

六休里、思考

曾經(jīng)在開發(fā)過程中遇到的問題,列在這里赃承,供感興趣的同學(xué)思考:

  • 若返回值里有 map妙黍,而 map 里寫了個(gè) map.put("key",null),就會(huì)拋異常楣导,為什么废境?
  • 若 Server 端和 Client 端使用的 thrift 版本不一致,可能會(huì)出現(xiàn)什么問題筒繁?
  • IDL 中的 struct 如何擴(kuò)展升級(jí)噩凹?

thrift 入門的內(nèi)容差不多就到這里了,寫的心好累毡咏。

如果文中內(nèi)容有錯(cuò)誤的地方驮宴,歡迎各位大佬指出,感恩呕缭。


七堵泽、參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末修己,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子迎罗,更是在濱河造成了極大的恐慌睬愤,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纹安,死亡現(xiàn)場離奇詭異尤辱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)厢岂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門光督,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人塔粒,你說我怎么就攤上這事结借。” “怎么了卒茬?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵船老,是天一觀的道長。 經(jīng)常有香客問我圃酵,道長努隙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任辜昵,我火速辦了婚禮,結(jié)果婚禮上咽斧,老公的妹妹穿的比我還像新娘堪置。我一直安慰自己,他們只是感情好张惹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布舀锨。 她就那樣靜靜地躺著,像睡著了一般宛逗。 火紅的嫁衣襯著肌膚如雪坎匿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天雷激,我揣著相機(jī)與錄音替蔬,去河邊找鬼。 笑死屎暇,一個(gè)胖子當(dāng)著我的面吹牛承桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播根悼,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼凶异,長吁一口氣:“原來是場噩夢啊……” “哼蜀撑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起剩彬,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤酷麦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后喉恋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沃饶,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年瀑晒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绍坝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苔悦,死狀恐怖轩褐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情玖详,我是刑警寧澤把介,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站蟋座,受9級(jí)特大地震影響拗踢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜向臀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一巢墅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧券膀,春花似錦君纫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舒帮,卻和暖如春会喝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玩郊。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工肢执, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓦宜。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓蔚万,卻偏偏與公主長得像,于是被迫代替她去往敵國和親临庇。 傳聞我的和親對象是個(gè)殘疾皇子反璃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志閱讀 24,784評論 2 38
  • 一. 與 Thrift 的初識(shí) 也許大多數(shù)人接觸 Thrift 是從序列化開始的昵慌。每次搜索 “java序列化” +...
    java菜閱讀 1,305評論 0 6
  • Thrift是什么? Thrift是Facebook于2007年開發(fā)的跨語言的rpc服框架淮蜈,提供多語言的編譯功能斋攀,...
    jiangmo閱讀 9,401評論 0 6
  • 步步驚心麗主題曲 EXO 為了你 伯賢 CHEN XIUMIN
    卟尼與鹿閱讀 461評論 0 1
  • 結(jié)果,都要努力追求梧田,怎不精疲力盡淳蔼?經(jīng)過,需要用心體會(huì)裁眯,才會(huì)回味無窮鹉梨!人生,的經(jīng)過穿稳,我們都要用心去經(jīng)過存皂!結(jié)果,該來就...
    再湊熱鬧閱讀 91評論 0 0