先日、Google が開発しているリモートプロシージャコール、gRPC を golang から使うチュートリアルを書きましたが
Big Sky :: Protocol Buffers を利用した RPC、gRPC を golang から試してみた。
http://mattn.kaoriya.net/software/lang/go/20150227144125.htm
今日は ruby と C++ から触ってみたいと思います。はじめに ruby の方ですが、Ruby 2.2.0 でビルドする事が出来ません。どうしても Ruby 2.2.0 から試したい人は、以下の PR にあるパッチを適用して下さい。
Support ruby 2.2.0 by mattn · Pull Request #894 - grpc/grpc - GitHub
https://github.com/grpc/grpc/pull/894
今回の検証は ruby 2.1.0 で行いました。まず proto ファイルから ruby のスタブを吐くには以下の様に実行します。
$ protoc --ruby_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_ruby_plugin customer_service.proto
GRPC_ROOT
は grpc を checkout したディレクトリですが、システムにインストールした人は適便書き換えて下さい。golang の時の様にスタブが吐かれるのでサーバであれば
#!/usr/bin/env ruby
require 'grpc'
require 'customer_service_services'
class MyServer < Proto::CustomerService::Service
def initialize()
@customers = []
end
def add_person(arg, call)
@customers << arg
Proto::ResponseType.new
end
def list_person(args, call)
@customers
end
end
def main
customers = []
server = GRPC::RpcServer.new
server.add_http2_port('0.0.0.0:11111')
server.handle(MyServer.new)
server.run
end
main
こんな感じ。クライアントであれば
#!/usr/bin/env ruby
require 'grpc'
require 'customer_service_services'
def main
stub = Proto::CustomerService::Stub.new('localhost:11111')
if ARGV.size == 2
stub.add_person(Proto::Person.new(name: ARGV[0], age: ARGV[1].to_i))
else
stub.list_person(Proto::RequestType.new).each do |x|
puts "name=#{x.name}, age=#{x.age}"
end
end
end
main
こんな感じに実装して下さい。今回は Sync サーバで実装しましたが、Async サーバで実装する場合は customers に排他を行うべきです。
C++ もやり方は変わりません。
protoc --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_ROOT)/bins/opt/grpc_cpp_plugin customer_service.proto
protoc でスタブを吐いてサーバであれば
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/server.h>
#include <grpc++/server_builder.h>
#include <grpc++/server_context.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"
using namespace proto;
using namespace grpc;
class CustomerServiceImpl final : public CustomerService::Service {
private:
std::vector<Person> customers;
public:
Status AddPerson(ServerContext* context, const Person* customer, ResponseType* response) {
std::cout << "AddPerson" << std::endl;
customers.push_back(*customer);
std::cout << "Done" << std::endl;
return Status::OK;
}
Status ListPerson(ServerContext* context, const RequestType* request, ServerWriter<Person>* writer) {
std::cout << "ListPerson" << std::endl;
for (const Person& p : customers) {
writer->Write(p);
}
std::cout << "Done" << std::endl;
return Status::OK;
}
};
int
main(int argc, char* argv[]) {
grpc_init();
std::string server_address("0.0.0.0:11111");
CustomerServiceImpl service;
ServerBuilder builder;
builder.AddPort(server_address);
builder.RegisterService(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wait();
grpc_shutdown();
return 0;
}
またクライアントであれば
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <grpc/grpc.h>
#include <grpc++/channel_arguments.h>
#include <grpc++/channel_interface.h>
#include <grpc++/client_context.h>
#include <grpc++/create_channel.h>
#include <grpc++/status.h>
#include <grpc++/stream.h>
#include "customer_service.pb.h"
using namespace proto;
using namespace grpc;
int
main(int argc, char** argv) {
grpc_init();
std::unique_ptr<CustomerService::Stub> client = CustomerService::NewStub(
grpc::CreateChannelDeprecated("127.0.0.1:11111", ChannelArguments()));
ClientContext context;
RequestType request;
ResponseType response;
Person person;
Status status;
if (argc == 3) {
person.set_name(argv[1]);
person.set_age(atoi(argv[2]));
status = client->AddPerson(&context, person, &response);
} else {
std::unique_ptr<ClientReader<Person>> reader = client->ListPerson(&context, request);
while (reader->Read(&person)) {
std::cout << "name=" << person.name() << ", age=" << person.age() << std::endl;
}
status = reader->Finish();
}
if (!status.IsOk()) {
std::cout << "ListFeatures rpc failed." << std::endl;
}
client.reset();
grpc_shutdown();
}
この様に実装します。同じ proto ファイルから生成したサーバやクライアントですので、ruby のサーバを起動して、golang のクライアントや C++ のクライアントからリクエストを送る事も出来ますし、C++ をサーバや golang をサーバにしても良いでしょう。
ただ触った感じですが、ruby のサーバは何故かレスポンスが悪かったので調査を兼ねてベンチマークを取ってみました。
シナリオは、ruby、C++、golang それぞれのサーバを起動して C++ のクライアントから要求します。計測は AddPerson の1000回呼び出し、とその1000件入った状態で ListPerson を100回呼び出しを計測しました。
ruby
mattn/grpc-example-rb - GitHub
AddPerson
$ time seq 1000 | xargs -n 1 ./client mattn
real 0m14.287s
user 0m6.667s
sys 0m4.607s
ListPerson
$ time seq 100 | xargs -n 1 ./client > /dev/null
real 0m12.385s
user 0m4.833s
sys 0m4.367s
cpp
mattn/grpc-example-cpp - GitHub
AddPerson
$ time seq 1000 | xargs -n 1 ./client mattn
real 0m12.326s
user 0m6.007s
sys 0m4.710s
ListPerson
$ time seq 100 | xargs -n 1 ./client > /dev/null
real 0m4.304s
user 0m2.430s
sys 0m1.490s
golang
mattn/grpc-example - GitHub
AddPerson
$ time seq 1000 | xargs -n 1 ./client mattn
real 0m13.067s
user 0m6.553s
sys 0m4.737s
ListPerson
$ time seq 100 | xargs -n 1 ./client > /dev/null
real 0m5.755s
user 0m2.767s
sys 0m1.983s
とまぁ、想定通りの結果が出ました。golang が案外頑張ってるなーという印象です。今後時間が出来たら Java や python も触っていく予定です。これまで3言語を同じ proto ファイルからスタブ生成して開発を行ってみましたが、各々扱い方が異なるのでちょっと混乱する可能性があります。自分の好きな言語で実装するのが良いと思います。
サーバ/インフラ徹底攻略 (WEB+DB PRESS plus)
伊藤 直也, 片山 暁雄, 平山 毅, 舟崎 健治, 吉荒 祐一, 今井 雄太, 八木橋 徹平, 安川 健太, 宮下 剛輔, 田中 慎司, 久保 達彦, 道井 俊介, 飯田 祐基, 桑野 章弘, 松浦 隼人, 中村 俊之, 福永 亘, 杉山 仁則, WEB+DB PRESS編集部
技術評論社 大型本 / ¥44 (2014年10月30日)
発送可能時間:
WEB+DB PRESS Vol.85
菅原 元気, 磯辺 和彦, 山口 与力, 澤登 亨彦, 濱田 章吾, 宮田 淳平, 松本 亮介, 海野 弘成, 佐藤 歩, 泉水 翔吾, 佐藤 太一, hide_o_55, 青木 良樹, 武本 将英, 道井 俊介, 伊藤 直也, 橋本 翔, 渡邊 恵太, 舘野 祐一, 中島 聡, はまちや2, 竹原, 牧 大輔, 工藤 春奈, WEB+DB PRESS編集部
技術評論社 大型本 / ¥1 (2015年02月24日)
発送可能時間: