什麼樣的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);
}
伺服器端程式碼
include // 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
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(¶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::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]**
相關文章
- 什麼是rpc?RPC
- RPC是什麼RPC
- RPC是什麼?RPC
- 到底什麼樣的 REST 才是最佳 REST?REST
- RPC的概述RPC
- 什麼是RPC ? 用PHP如何實現?RPCPHP
- Spark RPC 到底是個什麼鬼?SparkRPC
- gRPC(一)入門:什麼是RPC?RPC
- 為什麼有了 HTTP 還要 RPCHTTPRPC
- 瞭解一下RPC,為何誕生RPC,和HTTP有什麼不同?RPCHTTP
- 你說說RPC的一個請求的流程是怎麼樣的?RPC
- golang RPC 應用(1) :net/rpc的應用GolangRPC
- 怎樣才是好用的企業管理軟體?
- 如何給女朋友解釋什麼是RPCRPC
- 什麼樣的CRM系統更好用?
- RPCRPC
- [RPC]RPC
- go語言實現自己的RPC:go rpc codecGoRPC
- net/rpc包的使用RPC
- 漫談Roguelike——什麼樣的遊戲才是好玩的肉鴿遊戲遊戲
- 碼農深耕 - 什麼樣的程式碼才是好程式碼?
- Go RpcGoRPC
- PoS RPCRPC
- 生成 rpcRPC
- 什麼樣的雲管平臺才是企業需要的?他們的真正訴求是什麼?
- rpc的正確開啟方式|讀懂Go原生net/rpc包RPCGo
- 你真的明白RPC 嗎?一起來探究 RPC 的實質RPC
- Java 簡單的rpc 一JavaRPC
- goalng中net/rpc的使用GoRPC
- 一個RPC的demo(good)RPCGo
- 基於RPC原理的dubboRPC
- 手寫簡單的RPCRPC
- .NET輕量級RPC框架:Rabbit.RpcRPC框架
- 擁抱.NET Core,跨平臺的輕量級RPC:Rabbit.RpcRPC
- 螞蟻 RPC 框架 SOFA-RPC 初體驗RPC框架
- RPC詳解RPC
- RPC簡述RPC
- Rpc Call ProxyRPC