先日、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
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 ファイルからスタブ生成して開発を行ってみましたが、各々扱い方が異なるのでちょっと混乱する可能性があります。自分の好きな言語で実装するのが良いと思います。