现在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框架,并提供了多种客户端。 



协议定义 


Java代码 

1. 先定义一个.proto的文件,例如  
2.   
3. // Obtains the feature at a given position.
4.     rpc GetFeature(Point) returns (Feature) {}  
5. 定义了一个服务接口,接收客户端传过来的Point,返回一个Feature,接下来定义protocol buffer的消息类型,用于序列化/反序列化  
6.   
7.     message Point {  
8. 1;  
9. 2;  
10.     }


服务器代码 


Java代码 

1. class RouteGuideImpl final : public
2. const
3.           feature->set_name(GetFeatureName(*point, feature_list_));  
4.           feature->mutable_location()->CopyFrom(*point);  
5. return
6.     }  
7. }  
8.   
9. void RunServer(const
10. "0.0.0.0:50051");  
11.   RouteGuideImpl service(db_path);  
12.   
13.   ServerBuilder builder;  
14.   builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());  
15.   builder.RegisterService(&service);  
16.   std::unique_ptr<Server> server(builder.BuildAndStart());  
17. "Server listening on "
18.   server->Wait();  
19. }


客户端代码 


Java代码 

1. bool GetOneFeature(const
2.     ClientContext context;  
3.     Status status = stub_->GetFeature(&context, point, feature);  
4. if
5. "GetFeature rpc failed."
6. return false;  
7.     }  
8. if
9. "Server returns incomplete feature."
10. return false;  
11.     }  
12.   
13. return true;  
14. }



评价 



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的,需要定义协议。 



协议定义 


Java代码 

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


Java代码 


1. // 定义回应消息
2. message EchoResponse {  
3. 1;  
4. }  
5.   
6. // 定义RPC服务,可包含多个方法(这里只列出一个)
7. service EchoServer {  
8.     rpc Echo(EchoRequest) returns(EchoResponse);  
9. }


服务器端代码 


Java代码 

1. #include <sofa/pbrpc/pbrpc.h>  // sofa-pbrpc头文件
2. #include "echo_service.pb.h"   // service接口定义头文件
3. class EchoServerImpl : public
4. {  
5. public:  
6.     EchoServerImpl() {}  
7.     virtual ~EchoServerImpl() {}  
8.   
9. private:  
10. void
11. const
12.                       sofa::pbrpc::test::EchoResponse* response,  
13.                       google::protobuf::Closure* done)  
14.     {  
15.         sofa::pbrpc::RpcController* cntl =  
16.             static_cast<sofa::pbrpc::RpcController*>(controller);  
17. "Echo(): request message from %s: %s",  
18.             cntl->RemoteAddress().c_str(), request->message().c_str());  
19. "echo message: "
20.         done->Run();  
21.     }  
22. };  
23. 注意:  
24.   
25. 服务完成后必须调用done->Run(),通知RPC系统服务完成,触发发送Response;  
26. 在调了done->Run()之后,Echo的所有四个参数都不再能访问;  
27. done-Run()可以分派到其他线程中执行,以实现了真正的异步处理;


客户端代码 


Java代码 


1. int
2. {  
3.     SOFA_PBRPC_SET_LOG_LEVEL(NOTICE);  
4.   
5. // 定义RpcClient对象,管理RPC的所有资源
6. // 通常来说,一个client程序只需要一个RpcClient实例
7. // 可以通过RpcClientOptions指定一些配置参数,譬如线程数、流控等
8.     sofa::pbrpc::RpcClientOptions client_options;  
9. 8;  
10.     sofa::pbrpc::RpcClient rpc_client(client_options);  
11.   
12. // 定义RpcChannel对象,代表一个消息通道,需传入Server端服务地址
13. "127.0.0.1:12321");  
14.   
15. // 定义EchoServer服务的桩对象EchoServer_Stub,使用上面定义的消息通道传输数据
16.     sofa::pbrpc::test::EchoServer_Stub stub(&rpc_channel);  
17.   
18. // 定义和填充调用方法的请求消息
19.     sofa::pbrpc::test::EchoRequest request;  
20. "Hello world!");  
21. // 可以通过RpcClientOptions指定一些配置参数,譬如线程数、流控等
22.     sofa::pbrpc::RpcClientOptions client_options;  
23. 8;  
24.     sofa::pbrpc::RpcClient rpc_client(client_options);  
25.   
26. // 定义RpcChannel对象,代表一个消息通道,需传入Server端服务地址
27. "127.0.0.1:12321");  
28.   
29. // 定义EchoServer服务的桩对象EchoServer_Stub,使用上面定义的消息通道传输数据
30.     sofa::pbrpc::test::EchoServer_Stub stub(&rpc_channel);  
31.   
32. // 定义和填充调用方法的请求消息
33.     sofa::pbrpc::test::EchoRequest request;  
34. "Hello world!");  
35.   
36. // 定义方法的回应消息,会在调用返回后被填充
37.     sofa::pbrpc::test::EchoResponse response;  
38.   
39. // 定义RpcController对象,用于控制本次调用
40. // 可以设置超时时间、压缩方式等;默认超时时间为10秒,默认压缩方式为无压缩
41.     sofa::pbrpc::RpcController controller;  
42. 3000);  
43. // 发起调用,最后一个参数为NULL表示为同步调用
44.     stub.Echo(&controller, &request, &response, NULL);  
45.   
46. // 调用完成后,检查是否失败
47. if
48. // 调用失败后的错误处理,譬如可以进行重试
49. "request failed: %s", controller.ErrorText().c_str());  
50.     }  
51.   
52. return
53. }


评价 



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



综合评价:62分。 



腾讯Pebble 



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



协议定义 


Java代码 


1. struct HeartBeatInfo {  
2. 1: i64 id,  
3. 2: i32 version = 1,  
4. 3: string address,  
5. 4: optional string comment,  
6. }  
7.   
8. service BaseService {  
9.   
10. 1:i64 id, 2:HeartBeatInfo data),  
11.   
12. void log(1: string content)  
13.   
14. }


服务器端代码 


Java代码 


1. class BaseServiceHandler : public
2. public:  
3.   
4. void log(const
5. "receive request : log(" << content << ")"
6.     }  
7. };  
8.   
9. int main(int argc, char* argv[]) {  
10. // 初始化RPC
11.     pebble::rpc::Rpc* rpc = pebble::rpc::Rpc::Instance();  
12. "", 0, "");  
13.   
14. // 注册服务
15.     BaseServiceHandler base_service;  
16.     rpc->RegisterService(&base_service);  
17.   
18. // 配置服务监听地址
19. "tcp://127.0.0.1:");  
20. if (argc > 1) {  
21. 1]);  
22. else
23. "8200");  
24.     }  
25. // 添加服务监听地址
26.     rpc->AddServiceManner(listen_addr, pebble::rpc::PROTOCOL_BINARY);  
27.   
28. // 启动server
29.     rpc->Serve();  
30.   
31. return 0;  
32. }


客户端代码 


Java代码 


1. // 初始化RPC
2. pebble::rpc::Rpc* rpc = pebble::rpc::Rpc::Instance();  
3. rpc->Init("", -1, "");  
4.   
5. // 创建rpc client stub
6. BaseServiceClient client(service_url, pebble::rpc::PROTOCOL_BINARY);  
7.   
8. // 同步调用
9. int ret = client.log("pebble simple test : log");  
10. std::cout << "sync call, ret = "


评价 



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



综合评价:75分。 



apache msgpack-RPC 



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



服务器端代码 


Java代码 

1. #include <jubatus/msgpack/rpc/server.h>  
2.   
3. class myserver : public
4. public:  
5. void add(msgpack::rpc::request req, int a1, int
6.     {  
7.         req.result(a1 + a2);  
8.     }  
9.   
10. public:  
11. void
12. try
13.         std::string method;  
14.         req.method().convert(&method);  
15.   
16. if(method == "add") {  
17. int, int> params;  
18.             req.params().convert(¶ms);  
19. 0>(), params.get<1>());  
20.   
21. else
22.             req.error(msgpack::rpc::NO_METHOD_ERROR);  
23.         }  
24.   
25. catch
26.         req.error(msgpack::rpc::ARGUMENT_ERROR);  
27. return;  
28. } catch
29.         req.error(std::string(e.what()));  
30. return;  
31.     }  
32. };


客户端代码 


Java代码 


1. #include <jubatus/msgpack/rpc/client.h>  
2. #include <iostream>  
3.   
4. int main(void)  
5. {  
6. "127.0.0.1", 9090);  
7. int result = c.call("add", 1, 2).get<int>();  
8.     std::cout << result << std::endl;  
9. }


评价 



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



综合评价:80分。 



总结 



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