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文件。具体的语法和规则可以参见官网的介绍TYPES和IDL,在这里我们举一个简单的例子,新建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的依赖



    org.apache.thrift
    libthrift
    0.11.0

随后我们把上面生成的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 extends org.apache.thrift.ProcessFunction {
      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.总结

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

你可能感兴趣的:(Thrift我之初见)