2015/03/04

Recent entries from same category

  1. Zenn で Twitter bot 作成入門を書いた。
  2. プログラマーのための新しい情報共有コミュニティ Zenn で本を書いてみた。
  3. Windows ユーザは cmd.exe で生きるべき 2020年版
  4. Let's Encrypt を簡単操作できる CLI、Lego が MyDNS に対応した。
  5. golang でメモ専用コマンド「memo」作った。

先日、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(nameARGV[0], ageARGV[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 plus)
伊藤 直也, 片山 暁雄, 平山 毅, 舟崎 健治, 吉荒 祐一, 今井 雄太, 八木橋 徹平, 安川 健太, 宮下 剛輔, 田中 慎司, 久保 達彦, 道井 俊介, 飯田 祐基, 桑野 章弘, 松浦 隼人, 中村 俊之, 福永 亘, 杉山 仁則, WEB+DB PRESS編集部
技術評論社 大型本 / ¥13 (2014年10月30日)
 
発送可能時間:

WEB+DB PRESS Vol.85 WEB+DB PRESS Vol.85
菅原 元気, 磯辺 和彦, 山口 与力, 澤登 亨彦, 濱田 章吾, 宮田 淳平, 松本 亮介, 海野 弘成, 佐藤 歩, 泉水 翔吾, 佐藤 太一, hide_o_55, 青木 良樹, 武本 将英, 道井 俊介, 伊藤 直也, 橋本 翔, 渡邊 恵太, 舘野 祐一, 中島 聡, はまちや2, 竹原, 牧 大輔, 工藤 春奈, WEB+DB PRESS編集部
技術評論社 大型本 / ¥1,628 (2015年02月24日)
 
発送可能時間:

Posted at by | Edit