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序列化,我绘制了示意图。