什么样的 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 {
}
服務器端代碼include // sofa-pbrpc頭文件
include "echo_service.pb.h" // service接口定義頭文件
class EchoServerImpl : public sofa::pbrpc::test::EchoServer
{
public:
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的使用并沒有像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:
};
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;
include
class myserver : public msgpack::rpc::server::base {
public:
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(¶ms);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
include
int main(void)
{
}
評價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]**總結
以上是生活随笔為你收集整理的什么样的 RPC 才是好用的 RPC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Debian GNU/Linux 9 将
- 下一篇: AngularJS 的自定义指令