什么样的 RPC 才是好用的 RPC

现在RPC框架很多,但是真正好用的RPC却是少之又少。那么什么是好用的RPC,什么是不好用的RPC呢,有一个评判标准吗?下面是我列举出来的衡量RPC好用与否的几条标准:

真的像本地函数一样调用

使用简单,用户只需要关注业务即可

灵活,RPC调用的序列化方式可以自由定制,比如支持json,支持msgpack等方式

下面来分别解释这几条标准。

标准1:真的像本地函数一样调用

RPC的本质是为了屏蔽网络的细节和复杂性,提供易用的api,让用户就像调用本地函数一样实现远程调用,所以RPC最重要的就是“像调用本地函数一样”实现远程调用,完全不让用户感知到底层的网络。真正好用的RPC接口,他的调用形式是和本地函数无差别的,但是本地函数调用是灵活多变的。服务器如果提供和客户端完全一致的调用形式将是非常好用的,这也是RPC框架的一个巨大挑战

标准2:使用简单,用户只需要关注业务即可

RPC的使用简单直接,非常自然,就是和调用本地函数一样,不需要写一大堆额外代码,用户只用写业务逻辑代码,而不用关注框架的细节,其他的事情都由RPC框架完成。

标准3:灵活,RPC调用的序列化方式可以自由定制

RPC调用的数据格式支持多种编解码方式,比如一些通用的json格式、msgpack格式或者boost.serialization等格式,甚至支持用户自己定义的格式,这样使用起来才会更灵活。

RPC框架评估

下面根据这几个标准来评估一些国内外知名大公司的RPC框架,这些框架的用法在github的wiki中都有使用示例,使用示例代码均来自官方提供的例子。

谷歌 gRPC

gRPC最近发布了1.0版本,他是谷歌公司用c++开发的一个RPC框架,并提供了多种客户端。

协议定义

先定义一个.proto的文件,例如

// Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}

定义了一个服务接口,接收客户端传过来的Point,返回一个Feature,接下来定义protocol buffer的消息类型,用于序列化/反序列化

message Point {
      int32 latitude = 1;
      int32 longitude = 2;
    }

服务器代码

class RouteGuideImpl final : public RouteGuide::Service {
    Status GetFeature(ServerContext context, const Point point, Feature* feature) override {
          feature->set_name(GetFeatureName(*point, feature_list_));
          feature->mutable_location()->CopyFrom(*point);
          return Status::OK;
    }
}

void RunServer(const std::string& db_path) {
  std::string server_address("0.0.0.0:50051");
  RouteGuideImpl service(db_path);

  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
}

客户端代码

bool GetOneFeature(const Point& point, Feature* feature) {
    ClientContext context;
    Status status = stub_->GetFeature(&context, point, feature);
    if (!status.ok()) {
      std::cout << "GetFeature rpc failed." << std::endl;
      return false;
    }
    if (!feature->has_location()) {
      std::cout << "Server returns incomplete feature." << std::endl;
      return false;
    }

    return true;
}

评价

gRPC调用的序列化用的是protocal buffer,RPC服务接口需要在.proto文件中定义,使用稍显繁琐。根据标准1,gRPC并没有完全实现像本地调用一样,虽然很接近了,但做不到,原因是RPC接口中必须带一个Context的参数,并且返回类型必须是Status,这些限制导致gRPC无法做到像本地接口一样调用。
根据标准2,gRPC的使用不算简单,需要关注诸多细节,比如Context和Status等框架的细节。根据标准3,gRPC只支持pb协议,无法扩展支持其他协议。

综合评价:70分。

百度sofa-pbRPC

sofa-pbRPC是百度用c++开发的一个RPC框架,和gRPC有点类似,也是基于protocal buffer的,需要定义协议。

协议定义

// 定义请求消息
message EchoRequest {
required string message = 1;
}

// 定义回应消息
message EchoResponse {
    required string message = 1;
}

/```javascript
/ 定义RPC服务,可包含多个方法(这里只列出一个)
service EchoServer {
rpc Echo(EchoRequest) returns(EchoResponse);
}

服务器端代码

```javascript
#include <sofa/pbrpc/pbrpc.h>  // sofa-pbrpc头文件
#include "echo_service.pb.h"   // service接口定义头文件
class EchoServerImpl : public sofa::pbrpc::test::EchoServer
{
public:
    EchoServerImpl() {}
    virtual ~EchoServerImpl() {}

private:
    virtual void Echo(google::protobuf::RpcController* controller,
                      const sofa::pbrpc::test::EchoRequest* request,
                      sofa::pbrpc::test::EchoResponse* response,
                      google::protobuf::Closure* done)
    {
        sofa::pbrpc::RpcController* cntl =
            static_cast<sofa::pbrpc::RpcController*>(controller);
        SLOG(NOTICE, "Echo(): request message from %s: %s",
            cntl->RemoteAddress().c_str(), request->message().c_str());
        response->set_message("echo message: " + request->message());
        done->Run();
    }
};

注意:

服务完成后必须调用done->Run(),通知RPC系统服务完成,触发发送Response;
在调了done->Run()之后,Echo的所有四个参数都不再能访问;
done-Run()可以分派到其他线程中执行,以实现了真正的异步处理;
客户端代码

int main()
{
    SOFA_PBRPC_SET_LOG_LEVEL(NOTICE);

    // 定义RpcClient对象,管理RPC的所有资源
    // 通常来说,一个client程序只需要一个RpcClient实例
    // 可以通过RpcClientOptions指定一些配置参数,譬如线程数、流控等
    sofa::pbrpc::RpcClientOptions client_options;
    client_options.work_thread_num = 8;
    sofa::pbrpc::RpcClient rpc_client(client_options);

    // 定义RpcChannel对象,代表一个消息通道,需传入Server端服务地址
    sofa::pbrpc::RpcChannel rpc_channel(&rpc_client, "127.0.0.1:12321");

    // 定义EchoServer服务的桩对象EchoServer_Stub,使用上面定义的消息通道传输数据
    sofa::pbrpc::test::EchoServer_Stub stub(&rpc_channel);

    // 定义和填充调用方法的请求消息
    sofa::pbrpc::test::EchoRequest request;
    request.set_message("Hello world!");

    // 定义方法的回应消息,会在调用返回后被填充
    sofa::pbrpc::test::EchoResponse response;

    // 定义RpcController对象,用于控制本次调用
    // 可以设置超时时间、压缩方式等;默认超时时间为10秒,默认压缩方式为无压缩
    sofa::pbrpc::RpcController controller;
    controller.SetTimeout(3000);

    // 发起调用,最后一个参数为NULL表示为同步调用
    stub.Echo(&controller, &request, &response, NULL);

    // 调用完成后,检查是否失败
    if (controller.Failed()) {
        // 调用失败后的错误处理,譬如可以进行重试
        SLOG(ERROR, "request failed: %s", controller.ErrorText().c_str());
    }

    return EXIT_SUCCESS;
}

评价

sofa-pbRPC的使用并没有像sofa这个名字那样sofa,根据标准1,服务端的RPC接口比gRPC更加复杂,更加远离本地调用了。根据标准2,用户要做很多额外的事,需要关注框架的很多细节,比较难用。根据标准3,同样只支持pb协议,无法支持其他协议。

综合评价:62分。

腾讯Pebble

腾讯开源的Pebble也是基于protocal buffer的,不过他的用法比gRPC和sofaRPC更好用,思路都是类似的,先定义协议。

协议定义

struct HeartBeatInfo {
  1: i64 id,
  2: i32 version = 1,
  3: string address,
  4: optional string comment,
}

service BaseService {

   i64 heartbeat(1:i64 id, 2:HeartBeatInfo data),

   oneway void log(1: string content)

}

服务器端代码

class BaseServiceHandler : public BaseServiceCobSvIf {
public:

    void log(const std::string& content) {
        std::cout << "receive request : log(" << content << ")" << std::endl;
    }
};

int main(int argc, char* argv[]) {
    // 初始化RPC
    pebble::rpc::Rpc* rpc = pebble::rpc::Rpc::Instance();
    rpc->Init("", 0, "");

    // 注册服务
    BaseServiceHandler base_service;
    rpc->RegisterService(&base_service);

    // 配置服务监听地址
    std::string listen_addr("tcp://127.0.0.1:");
    if (argc > 1) {
        listen_addr.append(argv[1]);
    } else {
        listen_addr.append("8200");
    }

    // 添加服务监听地址
    rpc->AddServiceManner(listen_addr, pebble::rpc::PROTOCOL_BINARY);

    // 启动server
    rpc->Serve();

    return 0;
}
客户端代码

// 初始化RPC
pebble::rpc::Rpc* rpc = pebble::rpc::Rpc::Instance();
rpc->Init("", -1, "");

// 创建rpc client stub
BaseServiceClient client(service_url, pebble::rpc::PROTOCOL_BINARY);

// 同步调用
int ret = client.log("pebble simple test : log");
std::cout << "sync call, ret = " << ret << std::endl;

评价

Pebble比gRPC和sofa-pbrpc更好用,根据标准1,调用方式和本地调用一致了,接口中没有任何限制。根据标准2,除了定义协议稍显繁琐之外已经比较易用了,不过服务器在使用上还是有一些限制,比如注册服务的时候只能注册一个类对象的指针,不能支持lambda表达式,std::function或者普通的function。根据标准3,gRPC只支持pb协议,无法扩展支持其他协议。

综合评价:75分。

apache msgpack-RPC

msgpack-RPC是基于msgpack定义的RPC框架,不同于基于pb的RPC,他无需定义专门的协议。

服务器端代码

#include <jubatus/msgpack/rpc/server.h>

class myserver : public msgpack::rpc::server::base {
public:
    void add(msgpack::rpc::request req, int a1, int a2)
    {
        req.result(a1 + a2);
    }

public:
    void dispatch(msgpack::rpc::request req)
    try {
        std::string method;
        req.method().convert(&method);

        if(method == "add") {
            msgpack::type::tuple<int, int> params;
            req.params().convert(&params);
            add(req, params.get<0>(), params.get<1>());

        } else {
            req.error(msgpack::rpc::NO_METHOD_ERROR);
        }

    } catch (msgpack::type_error& e) {
        req.error(msgpack::rpc::ARGUMENT_ERROR);
        return;

    } catch (std::exception& e) {
        req.error(std::string(e.what()));
        return;
    }
};

客户端代码

#include <jubatus/msgpack/rpc/client.h>
#include <iostream>

int main(void)
{
    msgpack::rpc::client c("127.0.0.1", 9090);
    int result = c.call("add", 1, 2).get<int>();
    std::cout << result << std::endl;
}

评价

msgpack-RPC使用起来也很简单,不需要定义proto文件,根据标准1,客户端的调用和本地调用一致,不过,服务器的RPC接口有一个msgpack::rpc::request对象,并且也必须派生于base类,使用上有一定的限制。根据标准2,服务器端提供RPC服务的时候需要根据method的名字来dispatch,这种方式不符合开闭原则,使用起来有些不方便。根据标准3,msgpack-rpc只支持msgpack的序列化,不能支持其他的序列化方式。

综合评价:80分。

总结

目前虽然国内外各大公司都推出了自己的RPC框架,但是真正好用易用的RPC框架却是不多的,这里对各个厂商的RPC框架仅从好用的角度做一个评价,一家之言,仅供参考,希望可以为大家做RPC的技术选型的时候提供一些评判依据。

文章转载自 开源中国社区[http://www.oschina.net]

时间: 2024-10-30 17:47:27

什么样的 RPC 才是好用的 RPC的相关文章

一个轻量级分布式RPC框架--NettyRpc

1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个轻量级的分布式RPC框架.花了一些时间看了下他的代码,写的干净简单,写的RPC框架可以算是一个简易版的dubbo.这个RPC框架虽小,但是麻雀虽小,五脏俱全,有兴趣的可以学习一下. 本人在这个简易版的RPC上添加了如下特性: * 服务异步调用的支持,回调函数callback的支持 * 客户端使用长连接(在多次调用共享连接) *

NFS 与RPC

转自鸟哥的网站:http://vbird.dic.ksu.edu.tw/linux_server/0330nfs_1.php 第十三章.文件服务器之一:NFS 服务器 13.1 NFS 的由来与其功能 13.1.1 什么是 NFS ( Network FileSystem ) 13.1.2 什么是 RPC ( Remote Procedure Call ) 13.1.3 NFS 启动的 RPC daemons 13.1.4 NFS 的档案访问权限 13.2 NFS Server 端的设定 13.

Hadoop RPC通信Client客户端的流程分析

Hadoop的RPC的通信与其他系统的RPC通信不太一样,作者针对Hadoop的使用特点,专门的设计了一套RPC框架,这套框架个人感觉还是有点小复杂的.所以我打算分成Client客户端和Server服务端2个模块做分析.如果你对RPC的整套流程已经非常了解的前提下,对于Hadoop的RPC,你也一定可以非常迅速的了解的.OK,下面切入正题. Hadoop的RPC的相关代码都在org.apache.hadoop.ipc的包下,首先RPC的通信必须遵守许多的协议,其中最最基本的协议即使如下: /**

集群RPC通信

        RPC即远程过程调用,它的提出旨在消除通信细节.屏蔽繁杂且易错的底层网络通信操作,像调用本地服务一般地调用远程服务,让业务开发者更多关注业务开发而不必考虑网络.硬件.系统的异构复杂环境.         先看看集群中RPC的整个通信过程,假设从节点node1开始一个RPC调用, ①先将待传递的数据放到NIO集群通信框架(这里使用的是tribes框架)中: ②由于使用的是NIO模式,线程无需阻塞直接返回: ③由于与集群其他节点通信需要花销若干时间,为了提高CPU使用率当前线程应该放

Hadoop RPC远程过程调用源码解析及实例

什么是RPC? 1.RPC(Remote Procedure Call)远程过程调用,它允许一台计算机程序远程调用另外一台计算机的子程序,而不用去关心底层的网络通信细节,对我们来说是透明的.经常用于分布式网络通信中. 2.Hadoop的进程间交互都是通过RPC来进行的,比如Namenode与Datanode之间,Jobtracker与Tasktracker之间等. RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据.在OSI网络通信模型中, RPC跨越了传输层和应用层

基于zeromq的高性能分布式RPC框架Zerorpc 性能测试

Zeromq 是基于zeromq.gevent和 msgpack开发的分布式RPC框架zerorpc-python.这个框架简单.易用. 1. 安装zeromq yum -y install zeromq yum install gcc gcc-c++ libuuid-devel python-uuid uuid wget http://download.zeromq.org/zeromq-2.1.9.tar.gz ./configure make make install 2.安装gevent

IPC 和 RPC (呵呵,我感觉我应该要钻研到这个深度啦)

上次看到一个网页,知道牛人们都在作什么时, 我明显感觉到我的世界和这些世界的差异. 慢慢往前走吧.. 1.有不同的手机终端,如iphone,安卓,Symbian,不同的终端处理不一样,设计一种服务器和算法实现对不同的终端的处理. 2.设计一种内存管理算法.  3.A向B发邮件,B收到后读取并发送收到,但是中间可能丢失了该邮件,怎么设计一种最节省的方法,来处理丢失问题.  4.设计一种算法求出算法复杂度. ~~~~~~~~~~~~~ 所以,了解一下IPC和LPC,RPC,算是IT的份内之事吧. 远

RPC框架几行代码就够了

虽然以前也大概知道rpc的实现原理,也看过部分msgpack的实现,但是对于反射不是很了解. 现在看到一个简单完整的实现,也解决我的以前的另一个疑惑: http://topic.csdn.net/u/20111028/14/092f98d0-ecdc-48b2-bf8b-317d5071ab6f.html?seed=361547001&r=77648361#r_77648361 不过,还是不明白为什么msgpack只用函数名,没有结合参数列表来识别一个函数,为了和其它语言兼容? --------

大型分布式网站架构设计与实践 第一章《面向服务的体系架构(SOA)》1.1基于TCP协议的RPC

1.1基于TCP协议的RPC 1.1.1RPC名词理解 RPC的全称是Remote Process Call,即远程过程调用,它应用广泛,实现方式也很多,拥有RMI,WebService等诸多成熟的方案,在业界得到了广泛的应用.单台服务器的处理能力受硬件成本的限制,不可能无限制的提升,RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近乎无限制的提升,这是系统发展到一定阶段必然性的改革,也是实现分布式计算的基础. 如图1-2所示,RPC的实现包括客户端和服务端,