牛丼一筋の心意気で学ぶ、Go言語とgRPCによる高速RPC通信
一言でいうと、「Go言語でサクッとマイクロサービスを構築するための、うまい、早い、安いRPCフレームワーク」です。
RPC(Remote Procedure Call)とは、ネットワーク越しに別々のプログラムの関数を呼び出す仕組みのこと。簡単に言えば、サーバーの機能(関数)を、クライアントからまるで自分のプログラム内の関数のように呼び出せるようにしてくれる技術です。
このRPCを、Googleが開発した新しい技術基盤であるgRPCを使って実現するのが、この grpc-go です。HTTP/2という最新の通信プロトコルをベースにしているため、従来のRPCよりも、高速かつ効率的な通信が可能です。
牛丼の三拍子に例えて、そのメリットを解説します。
型安全なAPI設計
gRPCでは、Protocol Buffers (通称Protobuf) という仕組みを使って、サーバーとクライアント間でやり取りするデータの型や、呼び出せる関数を厳密に定義します。
これにより、「この関数はどんな引数を必要とするのか」「戻り値の型は何か」が明確になり、APIの設計ミスや、プログラムのバグを減らすことができます。
まるで、お品書きがしっかりした牛丼屋さんに入ったような安心感ですね。
多言語対応
Protobufは、Go言語だけでなく、Java、Python、C++など、多くの言語に対応しています。
そのため、クライアントとサーバーで異なる言語を使って開発する、といったことが簡単にできます。
例えば、「WebフロントエンドはJavaScript、バックエンドはGo言語」という構成でも、スムーズに連携できます。
HTTP/2による高速通信
gRPCはHTTP/2をベースにしています。これによって、一つのTCP接続で複数のリクエストを同時に送受信したり、ヘッダーを圧縮したりすることができます。
従来のREST API (HTTP/1.1) に比べて、特に通信量が多いマイクロサービス間の通信で、圧倒的なパフォーマンスを発揮します。
まるで、注文したら瞬時に牛丼が出てくるような感覚です。
自動生成されるコード
Protobufで定義した内容をもとに、Go言語のクライアント・サーバーコードを自動生成してくれます。
自分でゴリゴリと通信処理を書く必要がないので、開発スピードが格段に上がります。
面倒なルーチンワークは機械に任せて、本質的なビジネスロジックの開発に集中できます。
軽量な通信プロトコル
Protobufは、JSONやXMLに比べて、送受信するデータサイズが非常に小さいです。
これは、特にクラウド環境など、通信量に応じて費用が発生するような環境で、コスト削減に繋がります。
ネットワーク帯域の節約にもなるので、サーバーやインフラのコストも抑えることができます。
では、実際に簡単な「Greeter」サービスのサンプルを一緒に作ってみましょう。
まず、protoc (Protocol Buffersコンパイラ) と、protoc-gen-go (Go言語用プラグイン) をインストールします。
# プロトコルバッファコンパイラのインストール(お使いのOSに合わせて)
# macOSの場合
brew install protobuf
# Go言語用プラグインのインストール
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
protoというディレクトリを作成し、その中に greeter.proto というファイルを作成します。ここで、サービスとデータの型を定義します。
// proto/greeter.proto
syntax = "proto3";
option go_package = "./;greeter";
package greeter;
// サービスを定義
service Greeter {
// RPCメソッドを定義
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// リクエストのメッセージを定義
message HelloRequest {
string name = 1;
}
// レスポンスのメッセージを定義
message HelloReply {
string message = 1;
}
service Greeter で、Greeter というサービスを定義しています。
rpc SayHello で、SayHello というRPCメソッドを定義しています。
HelloRequest と HelloReply で、それぞれリクエストとレスポンスのデータの型を定義しています。
greeter.proto をもとに、Go言語のコードを自動生成します。
protoc --go_out=./ --go_opt=paths=source_relative \
--go-grpc_out=./ --go-grpc_opt=paths=source_relative \
proto/greeter.proto
このコマンドを実行すると、greeter.pb.go と greeter_grpc.pb.go という2つのファイルが生成されます。これらは、自分で書く必要のない、Go言語でgRPCを扱うためのひな形コードです。
次に、生成されたコードを使ってサーバーを実装します。
// server/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "your-module-name/greeter" // ここはご自身のモジュール名に合わせて変更してください
)
// serverはGreeterサービスを実装する構造体
type server struct {
pb.UnimplementedGreeterServer
}
// SayHelloはgRPCのメソッドとして定義
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
最後に、サーバーに接続してRPCを呼び出すクライアントを作成します。
// client/main.go
package main
import (
"context"
"log"
"os"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "your-module-name/greeter" // ここはご自身のモジュール名に合わせて変更してください
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Goopher"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
grpc-go は、Go言語を使ってマイクロサービスを構築する上で、うまい、早い、安いという三拍子が揃った非常に強力なツールです。
うまい
Protobufによる厳密な型定義で、設計の美しさとメンテナンス性を向上。
早い
HTTP/2とコード自動生成で、パフォーマンスと開発スピードを両立。
安い
軽量なプロトコルで、通信コストを削減。