代码改变世界

Stub学习

2014-05-07 10:36  付哲  阅读(1776)  评论(0编辑  收藏  举报

RPC

RPC(Remote Procedure Call)就是某台主机A(一般为client)像调用本地的过程一样去调用另一台主机B(一般为server)上的某个过程。RPC代码可能长成这个样子:

//client
void SampleFunc() {
    Request req;
    req.set_key("client");
    vector<string> values = {"v1", "v2"};
    req.set_values(values);
    Response response = remoteHost.RemoteFunc(req);
    cout << response.status() << endl;
}
//server
Response RemoteFunc(Request req) {
    //...
}

在client调用remoteHost.RemoteFunc()时,其实已经发生了一次网络传输。参数被封包后传递给了server,传递方式可能是HTTP请求,也可能直接用TCP。server接收到参数后调用相应的RemoteFunc函数,并在离开函数时再将结果封包后传回给client。整个过程被称为一次RPC调用。

RPC调用可能像上面代码一样是同步的,但也可以是异步的,client在发起请求后需要主动去查询response。

由此我们可以看出RPC需要做的事有:

  1. 在client和server端各自准备好用于网络通信的函数,也就是上面代码中的RemoteFunc;
  2. 在client和server端各自准备好用于网络通信的数据类型,也就是上面代码中的Request和Response;
  3. 把请求分发给相应的函数。

这里面有两个问题:

  1. 如何保证client调用的函数和server端准备的函数是一致的(也就是可调用的);
  2. 如何保证Request和Response类型在client和server端是一致的(也就是可被正确解析的)。

stub就是用于解决这两个问题的代码。

Stub

wiki里的解释是:分布式计算中的stub是在RPC调用中用于转换参数的代码。进行RPC调用的两端都要装相应的RPC函数库,而stub就是RPC函数库里用于保证数据在两端一致的代码。

数据在两端为什么会不一致呢?可能有以下原因:

  1. 编程语言不同,那么使用的数据类型肯定不同;
  2. 位宽不同,一边是32位,一边是64位,整数长度不一致;
  3. 大小端不同,一边是大端,一边是小端;
  4. 结构体的内存布局不同,这个和整个编译环境有关;
  5. 字符串编码不同,一边是GBK,一边是UTF-8。

stub就是要选取一种中间形式,让两端的数据都可以正确可逆的与中间数据进行转换。常用的中间数据类型包括:纯字符串、XML、JSON、protobuf。

stub的代码可以手动去写,优点是可以处理非常复杂的数据类型,缺点是比较繁琐,如果数据类型需要修改,工作量很大。

一般来说stub都是通过现成的函数库自动生成的。这种方法需要用指定的语言(interface description language, IDL)完成一个描述文档,这个文档同时应用于client和server端。在使用时两端的stub编译器会将IDL文档翻译成指定的编程语言供两端的程序使用。如前面程序中使用的两种数据类型可以这样描述:

message Request{
    required bytes key = 1;
    repeated bytes values = 2;
}
message Response {
    required int32 status = 1;
    optional bytes error_code = 2;
}

当数据类型要进行修改的时候,只要改一下IDL文档,再用stub编译器进行编译就OK了。

除了数据,RPC过程也可以通过IDL进行定义:

service DefaultService {
    rpc RemoteFunc(Request) returns(Response)
}