Thrift我之初见

新接触的项目这边使用了Thrift,于我而言是一个完全陌生的东西,因此在此记录一下自己的学习过程和一点体会。

1.简介

远程调用接口我们当然可以使用HTTP协议,但伴随着服务越来越多,业务越来越复杂,效率就变成了一个很大的问题,于是基于网络传输层的RPC应运而生。Thrift就是一个跨语言的RPC框架,支持很多常用的语言,大大方便了我们的开发。

2.环境介绍

  • 操作系统: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)"

安装完成之后我们执行thrift -version,看到输出Thrift version 0.11.0就OK了。

4.编写thrift文件并生成配置

我们使用Thrift IDL(interface definition language)来编写thrift文件。具体的语法和规则可以参见官网的介绍TYPESIDL,在这里我们举一个简单的例子,新建user.thrift文件,内容如下

namespace java com.atticus //声明命名空间

enum Gender{    //枚举
    MALE = 1,
    FEMALE = 2
}

struct User{  //定义一个结构
    1: required i32 id,  
    2: required string name,  
    3: required Gender gender,  
    4: optional bool married    
}

service UserService{    //定义service
    User getUser(1:i32 id);
}

然后执行thrift --gen java user.thrift命令,可以在当前目录下看到一个名为gen-java的目录,这就是生成的文件了。

生成的文件

5.创建工程

我们在IDEA中创建一个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目录下,整个项目结构如下

项目结构

6.服务端实现

首先我们来建一个UserServiceImpl类作为服务端的具体实现类

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;
    }
}

接下来创建启动类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);//具体的处理实现
            args.protocolFactory(new TBinaryProtocol.Factory());//二进制
            new TThreadPoolServer(args).serve();
        } catch (TTransportException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Server().start();
    }
}

7.客户端实现

客户端的实现比较简单,我们创建一个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.验证

首先启动服务端,再启动客户端,可以看到服务端输出如下


服务端输出

客户端输出如下


客户端输出

证明我们的服务是正常的。

9.简单分析

接下来我们简单分析一下其中的原理,首先来看客户端,UserService.Client类实现了我们定义的public User getUser(int id){}方法,具体实现可以追踪到他的父类TServiceClient中的sendBase和receiveBase方法

    //发送请求
    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();
    }

可以看到在方法调用时用一个seqid来保证请求和响应能够对应上。
再看服务端的UserService.Processor类,我们可以发现这样一行
processMap.put("getUser", new getUser());
也就是说getUser方法在这里被当做一个类处理,

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);//直接调用
        return result;
      }
    }

同时有一个map在processor初始化的时候存储方法名和类,在请求到达的时候可以从map中取出相应的类,调用getResult方法。

  public boolean process(TProtocol in, TProtocol out) throws TException {
    TMessage msg = in.readMessageBegin();
    ProcessFunction fn = processMap.get(msg.name);//取出方法名对应的类
    if (fn == null) {
        ...
    }
    fn.process(msg.seqid, in, out, iface);//最终会执行getResult
    return true;
  }

10.总结

有时间再试试不同语言直接的通信吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容