Googleが作ったRPCフレームワークgRPCを使ってみた

(2016-07-29)

A high performance, open source, general RPC framework that puts mobile and HTTP/2 first.

What is gRPC?

gRPCを使うと、クライアントアプリケーションは直接、ローカルのオブジェクトのように、他のマシンのサーバーアプリケーションのメソッドを呼ぶことができ、 分散したアプリケーションやサービスを簡単に作ることができる。 多くのRPCシステムと同様に、gRPCはサービスを定義し、リモートから呼べるメソッドと、そのパラメーターおよび返り値の型を記述するようになっている。 サーバーサイドではインタフェースを実装し、クライアントからの呼び出しをハンドリングするgRPCサーバーを実行する。 クライアントサイドでは、サーバーと同じメソッドを提供するスタブを持っている。

gRPCクライアントとサーバーは様々な環境同士でやり取りすることができ、いくつもの言語でサポートされている。 そのため、例えば、gRPCサーバーをJavaで、クライアントをGoやPython、Rubyで作るのも簡単だ。 加えて、最新のGoodle APIにはgRPCのインタフェースが存在するので、これらをアプリケーションに組み込むのも容易にできる。

Protobuf

デフォルトではgRPCはprotobuf(protocol buffers)を使う。 protobufというのは、 Googleによるオープンソースの、構造化されたデータをシリアライズするメカニズムだ。

今回作るのは、同じ文字列を返すだけのEchoサーバーで、コードはここにある。 以下のprotoファイルでは、EchoというサービスはRetEchoというメソッドを含み、 これは文字列sayを含むEchoRequestに対して、文字列retを含むEchoReplyを返すということを表している。

syntax = "proto3";

option java_package = "net.sambaiz.trygrpc.protos";

package protos;

service Echo {
  rpc RetEcho (EchoRequest) returns (EchoReply) {}
}

message EchoRequest {
  string say = 1;
}

message EchoReply {
  string ret = 1;
}

これをprotocでコンパイルするとecho.pb.goのようなコードが生成される。

$ brew install --devel protobuf # install Install Protocol Compiler v3.0.0-beta-2
$ go get -u github.com/golang/protobuf/protoc-gen-go # Install Go Protobuf Runtime Installation
$ protoc --go_out=plugins=grpc:protos/. protos/*.proto

サーバー

$ go get google.golang.org/grpc

protoファイルに書いた、RetEchoを実装し、サーバーを立ち上げる。

package main

import (
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"net"
	"log"
	pb "github.com/sambaiz/try-gRPC/protos"
)

const (
	port = ":50051"
)

type server struct{}

func (s *server) RetEcho(ctx context.Context, in *pb.EchoRequest) (*pb.EchoReply, error) {
	return &pb.EchoReply{Ret: in.Say}, nil
}

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterEchoServer(s, &server{})
	log.Printf("server start localhost%s", port)
	s.Serve(lis)
}

クライアント

サーバーに接続すると、他のメソッドと同じようにサーバー側のRetEchoメソッドを呼び出すことができるようになる。

package main

import (
	"log"
	"google.golang.org/grpc"
	"golang.org/x/net/context"
	pb "github.com/sambaiz/try-gRPC/protos"

)

const (
	address = "localhost:50051"
)

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewEchoClient(conn)

	r, err := c.RetEcho(context.Background(), &pb.EchoRequest{Say: "hello"})
	if err != nil {
		log.Fatalf("Error: %v", err)
	}
	log.Printf("Return: %s", r.Ret)
}

最新のprotoファイルを共有すれば、どんな言語で書いても自分で型を定義したり、呼び出すロジックを書く必要がないし、 インタフェースが変わったときにコードレベルでエラーに気づけるのは通常のAPIリクエストと比較しても良い点だと思う。 また、同じ理由でロジックが複数のサーバーに渡る場合の負担を最小限にできるため、サービスを小さく作る、 マイクロサービスアーキテクチャで使われる。もちろん性能や管理のしやすさなどの面でもメリット/デメリットはあるだろうし、 オライリー本を読んで勉強しようと思う。(読んだ)