RPC-thrift总览

RPC

rpc全称Remote Procedure Call,也就是远程过程调用,通俗的说,就是在本地调用一个远程的方法,这个调用过程是透明的。

对于rpc,我首先想到的是进程间通讯,其中Socket就是完成远程进程间通讯。

从网络的角度看,运输层抽象的Socket做到了ip的分用及复用,也就是通过端口,完成了进程间通讯。

所以rpc可以看作是一种特殊的网络通讯。

网络栈
在这里插入图片描述

所以,rpc就需要有网络连接(传输层)、RPC协议(应用层),还需要序列化来完成对象的传输,最后为了使用的便捷、透明,使用者无感知,还需要进行一个调用透明化的处理。

透明调用

我的理解,这部分主要是动态生成代码,拦截client的方法调用,使真正的调用通过rpc的形式进行。

在Java中可以使用反射(动态代理)、字节码编辑等技术完成。

thrift使用IDL(接口描述语言)来定义结构体,并转化成对应语言的代码,这本身就是动态生成代码的过程。

序列化 Protocol

序列化是将对象转化为可在网络上传输的格式的过程,反之将网络上可传输的数据转化为对象的过程则为反序列化。

可在网络上传输的格式,总的来说,就是二进制和文本两种。二进制传输效率高,性能有优势,文本在可读性有优势。

在WebSocket协议头中的opcode(4位),就说明了传输的数据格式,分为ping\pong、closed、binary、text四种

thrift提供了四种序列化方式,前两种是二进制的方式,后两种是文本方式。

序列化

说明

TBinaryProtocol

二进制编码

TCompactProtocol

高效、密集的二进制编码

TJSONProtocol

json编码

TSimpleJSONProtocol

只提供json只写,适用脚本语言解析

RPC协议 Processor

序列化完成后,数据已经可以在网络传输了,接下来就是网络传输部分要进行的处理。

网上有人说,Processor组件是函数名和对象的映射。我觉得这种说法不够准确。

我认为Processor正是应用层RPC协议的映射。

在Tomcat中,也有一个组件叫Processor,他就是对应用层HTTP的映射,将HTTP请求转换成Tomcat Request。

网络 Transport

在Linux中网络模型有 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O 五种。其中异步IO在Linux中实现不成熟,Java中不支持信号驱动IO。

在Java中常用的IO模式只有两种,BIO和NIO。

thrift中对应两种网络模型是TSocket和TNonblockingSocket,分别对应Java的BIO和NIO。服务端则对应TServerSocket和TNonblockingServerSocket。

NIO使用Reactor编程模式,分为单线程Reactor、多线程Reactor、主从Reactor。这部分在Server中详细说。

Bootstrap Server

Server组件(服务端)负责将其他组件组合起来,对外提供服务。

类型

Server

说明

阻塞

TSimpleServer

单线程

TThreadPoolServer

one-thread-per-connection

非阻塞

TNonblockingServer

单线程Reactor 模式

THsHaServer

多线程Reactor 模式

TThreadedSelectorServer

主从Reactor 模式

举一个栗子

下面我们就写一个简单的例子来看看整个调用过程。这个例子是传入数据库的表名,参数名,查询数据库中的数据,以String List的形式返回。

thrift IDL

struct Request {
1:required string tableName;
2:required list fieldList
}

struct Response {
1:required list result
}

service Query {
Response queryFrom(1:required Request request);
}

Server初始化

server

public void startServer() {
    try {
        System.out.println("TSimpleServer start ....");

        TProcessor tprocessor = new Query.Processor<Query.Iface>(queryServiceImpl);

        TServerSocket serverTransport = new TServerSocket(SERVER_PORT);

        TServer.Args tArgs = new TServer.Args(serverTransport)
                .processor(tprocessor)
                .protocolFactory(new TBinaryProtocol.Factory());
        TServer server = new TSimpleServer(tArgs);
        server.serve();

    } catch (Exception e) {
        System.out.println("Server start error!!!");
        e.printStackTrace();
    }
}

首先创建Processor,创建getProcessMap,保存方法名(String)和ProcessFunction的映射。绑定接口实现类。

初始化网络,创建ServerSocket,执行bind函数绑定端口。

Server绑定Processor、TServerSocket,设置Protocol,调用server()启动服务。

	public Processor(I iface) {
  super(iface, getProcessMap(new HashMap<String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
}

protected Processor(I iface, Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
  super(iface, getProcessMap(processMap));
}

private static <I extends Iface> Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> getProcessMap(Map<String,  org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
  processMap.put("queryFrom", new queryFrom());
  return processMap;
}

// -------------------------------------------------------------------------------------------------------
public TServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws TTransportException {
this.serverSocket_ = null;
this.clientTimeout_ = 0;
this.clientTimeout_ = clientTimeout;

    try {
        this.serverSocket_ = new ServerSocket();
        this.serverSocket_.setReuseAddress(true);
        this.serverSocket_.bind(bindAddr);
    } catch (IOException var4) {
        this.serverSocket_ = null;
        throw new TTransportException("Could not create ServerSocket on address " + bindAddr.toString() + ".");
    }
}

serve()的实现和具体的Server类型有关,下面我们看一下TSimpleServer的实现。

核心逻辑就是

调用Transport的listen方法,底层就是使用ServerSocket,Java网络编程实现。

之后调用accept接收请求,收到请求后,交给Processor处理。

	public void serve() {
    this.stopped_ = false;

    try {
        this.serverTransport_.listen();
    } catch (TTransportException var8) {
        LOGGER.error("Error occurred during listening.", var8);
        return;
    }

    this.setServing(true);

    while(!this.stopped_) {
        TTransport client = null;
        TProcessor processor = null;
        TTransport inputTransport = null;
        TTransport outputTransport = null;
        TProtocol inputProtocol = null;
        TProtocol outputProtocol = null;

        try {
            client = this.serverTransport_.accept();
            if (client != null) {
                processor = this.processorFactory_.getProcessor(client);
                inputTransport = this.inputTransportFactory_.getTransport(client);
                outputTransport = this.outputTransportFactory_.getTransport(client);
                inputProtocol = this.inputProtocolFactory_.getProtocol(inputTransport);
                outputProtocol = this.outputProtocolFactory_.getProtocol(outputTransport);

                while(true) {
                    if (processor.process(inputProtocol, outputProtocol)) {
                        continue;
                    }
                }
            }
        } catch (TTransportException var9) {
        } catch (TException var10) {
            if (!this.stopped_) {
                LOGGER.error("Thrift error occurred during processing of message.", var10);
            }
        } catch (Exception var11) {
            if (!this.stopped_) {
                LOGGER.error("Error occurred during processing of message.", var11);
            }
        }

        if (inputTransport != null) {
            inputTransport.close();
        }

        if (outputTransport != null) {
            outputTransport.close();
        }
    }
    this.setServing(false);
}

Client初始化

client

	public static void main(String[] args) {
    try {
        transport = new TSocket("127.0.0.1", SERVER_PORT, TIMEOUT);
        transport.open();
        TProtocol protocol = new TBinaryProtocol(transport);
        client = new Query.Client(protocol);
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // 准备参数
    String tableName = "test.country";
    // fieldList {"country_name", "country_code"} 
    ArrayList<String> fieldList = new ArrayList<>();
    fieldList.add("country_name");
    fieldList.add("country_code");

    try {
        Request request = new Request();
        request.tableName = tableName;
        request.fieldList = fieldList;

        Response response = client.queryFrom(request);
        System.out.println(response.toString());

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

    if (null != transport) {
        transport.close();
    }
}

client初始化相对比较简单,设置Transport,开启连接,设置Protocol,创建client,就可以调用了。

RPC协议和序列化

Client发送请求
在这里插入图片描述

client调用接口之后,会调用sendBase方法执行核心发送请求逻辑

	protected void sendBase(String methodName, TBase args) throws TException {
    this.oprot_.writeMessageBegin(new TMessage(methodName, (byte)1, ++this.seqid_));
    args.write(this.oprot_);
    this.oprot_.writeMessageEnd();
    this.oprot_.getTransport().flush();
}

RPC协议

thrift的应用层协议比较简单,实现代码如下。

	public void writeMessageBegin(TMessage message) throws TException {
    if (this.strictWrite_) {
        int version = -2147418112 | message.type;
        this.writeI32(version);
        this.writeString(message.name);
        this.writeI32(message.seqid);
    } else {
        this.writeString(message.name);
        this.writeByte(message.type);
        this.writeI32(message.seqid);
    }
}

其中name是要调用的方法名,type有四种情况,seqid保证请求与回复对应(在Server接收请求也能看到他)。

public final class TMessageType {
public static final byte CALL = 1;
public static final byte REPLY = 2;
public static final byte EXCEPTION = 3;
public static final byte ONEWAY = 4;
}

这里特别说一下version

-2147418112 转换成二进制是 7f ff 00 00(16进制方便看)。type是一个byte,-2147418112 | message.type;操作将type保存在7f ff 00 00的后两位,达到了节省空间的作用。

这个version主要是为了保证client和server的thrift描述是一致的。

对应的,在readMessageBegin方法中,也有相应的处理。

	public TMessage readMessageBegin() throws TException {
    int size = this.readI32();
    if (size < 0) {
        int version = size & -65536;
        if (version != -2147418112) {
            throw new TProtocolException(4, "Bad version in readMessageBegin");
        } else {
            return new TMessage(this.readString(), (byte)(size & 255), this.readI32());
        }
    } else if (this.strictRead_) {
        throw new TProtocolException(4, "Missing version in readMessageBegin, old client?");
    } else {
        return new TMessage(this.readStringBody(size), this.readByte(), this.readI32());
    }
}

size & -65536 将后16位清零

size & 255 只保留后8位

使用位运算不但提升计算效率,还能有效节省空间。

序列化

	public void write(org.apache.thrift.protocol.TProtocol oprot, queryFrom_args struct) throws org.apache.thrift.TException {
    struct.validate();

    oprot.writeStructBegin(STRUCT_DESC);
    if (struct.request != null) {
      oprot.writeFieldBegin(REQUEST_FIELD_DESC);
      struct.request.write(oprot);
      oprot.writeFieldEnd();
    }
    oprot.writeFieldStop();
    oprot.writeStructEnd();
  }

}

序列化的核心方法就是write,首先是writeStructBegin,他在TBinaryProtocol中是空的实现。(STRUCT_DESC => 方法名_args 猜测在文本序列化方式中有用到)

之后就是writeFieldBegin,可以看到,这里只序列化了type和id,所以我们在修改thrift描述时,只要不修改id与type,就可以向前兼容,对应数据结构如下。
在这里插入图片描述

public class TField {
public final String name;
public final byte type;
public final short id;
}

public final class TType {
public static final byte STOP = 0;
public static final byte VOID = 1;
public static final byte BOOL = 2;
public static final byte BYTE = 3;
public static final byte DOUBLE = 4;
public static final byte I16 = 6;
public static final byte I32 = 8;
public static final byte I64 = 10;
public static final byte STRING = 11;
public static final byte STRUCT = 12;
public static final byte MAP = 13;
public static final byte SET = 14;
public static final byte LIST = 15;
public static final byte ENUM = 16;
}

之后,这里调用了struct.request的write方法,struct.request是之前我们在thrift描述里面定义的request对象,这里很明显就是进行一个递归的write。

最后,完成序列化进行flush,刷新缓冲区,发送数据。

Server接收请求

server接收到请求后,会调用Processor的process方法进行处理。

	public boolean process(TProtocol in, TProtocol out) throws TException {
    TMessage msg = in.readMessageBegin();
    ProcessFunction fn = (ProcessFunction)this.processMap.get(msg.name);
    if (fn == null) {
        TProtocolUtil.skip(in, (byte)12);
        in.readMessageEnd();
        TApplicationException x = new TApplicationException(1, "Invalid method name: '" + msg.name + "'");
        out.writeMessageBegin(new TMessage(msg.name, (byte)3, msg.seqid));
        x.write(out);
        out.writeMessageEnd();
        out.getTransport().flush();
        return true;
    } else {
        fn.process(msg.seqid, in, out, this.iface);
        return true;
    }
}

首先调用readMessageBegin,解析RPC协议,代码如下。

这里我们刚刚在发送请求时已经分析过了,这里不再重复。

	public TMessage readMessageBegin() throws TException {
    int size = this.readI32();
    if (size < 0) {
        int version = size & -65536;
        if (version != -2147418112) {
            throw new TProtocolException(4, "Bad version in readMessageBegin");
        } else {
            return new TMessage(this.readString(), (byte)(size & 255), this.readI32());
        }
    } else if (this.strictRead_) {
        throw new TProtocolException(4, "Missing version in readMessageBegin, old client?");
    } else {
        return new TMessage(this.readStringBody(size), this.readByte(), this.readI32());
    }
}

之后根据初始化时生成的processMap,通过message.name获取方法对应的ProcessFunction,调用其process方法。

	public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException {
    TBase args = this.getEmptyArgsInstance();

    try {
        args.read(iprot);
    } catch (TProtocolException var8) {
        iprot.readMessageEnd();
        TApplicationException x = new TApplicationException(7, var8.getMessage());
        oprot.writeMessageBegin(new TMessage(this.getMethodName(), (byte)3, seqid));
        x.write(oprot);
        oprot.writeMessageEnd();
        oprot.getTransport().flush();
        return;
    }

    iprot.readMessageEnd();
    TBase result = this.getResult(iface, args);
    oprot.writeMessageBegin(new TMessage(this.getMethodName(), (byte)2, seqid));
    result.write(oprot);
    oprot.writeMessageEnd();
    oprot.getTransport().flush();
}

这个主要分三个部分,首先反序列化得到请求参数,其次调用方法获取结果,最后将结果序列化写回。

getResult方法实际调用了我们在初始化时传入的接口实现。

	protected queryFrom_result getResult(I iface, queryFrom_args args) throws org.apache.thrift.TException {
    queryFrom_result result = new queryFrom_result();
    result.success = iface.queryFrom(args.request);
    return result;
}

Client接收响应

client接收响应核心是receiveBase方法。首先解析RPC协议,查看type是否为3(表示异常),查看seqid是否匹配(确保请求和响应对应)。

没有问题之后进行反序列化,获取结果。

	protected void receiveBase(TBase result, String methodName) throws TException {
    TMessage msg = this.iprot_.readMessageBegin();
    if (msg.type == 3) {
        TApplicationException x = TApplicationException.read(this.iprot_);
        this.iprot_.readMessageEnd();
        throw x;
    } else if (msg.seqid != this.seqid_) {
        throw new TApplicationException(4, methodName + " failed: out of sequence response");
    } else {
        result.read(this.iprot_);
        this.iprot_.readMessageEnd();
    }
}

下面是client的调用总过程。发送请求,获取结果。

	public Response queryFrom(Request request) throws org.apache.thrift.TException
{
  send_queryFrom(request);
  return recv_queryFrom();
}

public void send_queryFrom(Request request) throws org.apache.thrift.TException
{
  queryFrom_args args = new queryFrom_args();
  args.setRequest(request);
  sendBase("queryFrom", args);
}

public Response recv_queryFrom() throws org.apache.thrift.TException
{
  queryFrom_result result = new queryFrom_result();
  receiveBase(result, "queryFrom");
  if (result.isSetSuccess()) {
    return result.success;
  }
  throw new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.MISSING_RESULT, "queryFrom failed: unknown result");
}

协议和序列化总结

最后,针对TBinaryProtocol序列化,我绘制了示意图。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值