18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

gRPCがどんな動きをするのかhelloworld試しても分からなかったのでコードを読解してみた

Last updated at Posted at 2019-01-22

Why I thouth it

  • 業務の一環でAPIを構築することになったが、アーキテクトから組むのは初体験
  • しかもざっくりとしかRESTも理解してなかった
  • なので、再度勉強し直す意味も踏まえて昨今の時勢を調べてみた
  • 「REST」に加え、昨今は「gRPC」などが一般的な代替手段となっているとのこと
  • gRPCについては全くわからなかったので、調べながらとりあえずhelloworldを試してみた
  • が、やってみてもgRPCがどんなことしてるのかサッパリわからない状態だったので、コード読んで見ることにした

[参考文献]
https://docs.microsoft.com/ja-jp/azure/architecture/microservices/api-design

Whats' gRPC?

  • Googleが開発したOSSのRPC framework
  • 「ロードバランシング / トレーシング / ヘルスチェック / 認証」のためのプラグができるサポートがあるため
  • データセンターを跨いでの効率的なサービスインができる
  • とのこと (※1 参照)

RESTとRPCの差異の一部を↓にまとめた
文章は参考文献(※2, ※3, ※4, ※5)から引用

名称 REST RPC
正式名称 Representational State Transfer remote procedure call
ざっくり概要 SOAPプロトコルのようなMEPベースの特別な抽象化をしないもののことを、大まかに意味する用語 PGから別のアドレス空間(同一NWの別host)にあるPGを実行することを可能にする技術
対象 HTTP 動詞に基づいて統一インターフェイスを定義 操作やコマンドを中心にインターフェイス化
特徴 HTTPリクエストの1行目を見ればクライアントの目的を基本的に理解することができる(残りはその詳細に過ぎない) 遠隔相互作用の詳細を明示的にコーディングする必要がない
アプローチ1 スコープ情報の様々な値に対して異なるURIを返す 「ドキュメントプロセッサ」ごとにURIを公開する
アプローチ2 リソース(名詞)の多様性を重視 プロトコル操作(動詞)の多様性を重視

[参考]
※1 https://grpc.io/about/
※2 https://docs.microsoft.com/ja-jp/azure/architecture/microservices/api-design
※3 https://restful-api-guidelines-ja.netlify.com
※4 https://ja.wikipedia.org/wiki/Representational_State_Transfer
※5 RESTful Webサービス [ レオナルド・リチャードソン ]

How to gRPC's "HelloWorld"?

以下の作業が必要となるが、ここではさっくりとしか紹介しないので
詳細は是非参考にさせていただいたサイトを見ていただきたい

  • gRPCを利用するための環境準備

     -- gRPCはprotocコマンドを使ってコンパイルするので↓が必要
     $ brew install protobuf
    
     -- golangで使うgRPCライブラリをDL
     $ go get -u google.golang.org/grpc
     $ go get -u github.com/golang/protobuf/protoc-gen-go
    
     -- PATHを通す(通さないとprotocでコンパイルするときにエラーになる)
     $ export PATH=$PATH:$(go env GOPATH)/bin
     $ export PATH=$PATH:$GOPATH/bin
    
  • サンプルコードの準備

     -- helloworldのコードはインストールしたgrpcのディレクトリに存在する
     $ ls $GOPATH/src/google.golang.org/grpc/examples/helloworld/
     $ tree $GOPATH/src/google.golang.org/grpc/examples/helloworld/
     	.
     	├── greeter_client
     	│   └── main.go
     	├── greeter_server
     	│   └── main.go
     	├── helloworld
     	│   ├── helloworld.pb.go
     	│   └── helloworld.proto
     	└── mock_helloworld
     	    ├── hw_mock.go
     		└── hw_mock_test.go
    
     -- 作業用にcpする
     $ cp -r $GOPATH/src/google.golang.org/grpc/examples/helloworld .
     $ cd helloworld
    
  • 動かして動作確認

     -- Server側を起動する
     $ go run greeter_server/main.go
    
     -- 別タブでClient側を実行して動作検証
     $ go run greeter_client/main.go
    

[参考]
https://budougumi0617.github.io/2018/01/01/hello-grpc-go/

helloworldがどうやって動いているかを読み解いていく

↓のGitHubのコードやリンクを見ながら紐解いていく
https://github.com/grpc/grpc-go/tree/master/examples/helloworld

< Side greeter_server >

■ greeter_server/main.go でやっていることを理解する

greeter_server/main.go

func main() {
	lis, err := net.Listen("tcp", port)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterGreeterServer(s, &server{})
	// Register reflection service on gRPC server.
	reflection.Register(s)
	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

→ 上から順に紐解いていく。

lis, err := net.Listen("tcp", port)

引数に渡している 「port」は「:50051」が入っている↓

	const (
		port = ":50051"
	)

s := grpc.NewServer()

このメソッドの定義があったのは grpc-go/server.go
↓詳細

// NewServer creates a gRPC server which has no service registered and has not
// started to accept requests yet.
func NewServer(opt ...ServerOption) *Server {
	opts := defaultServerOptions
	for _, o := range opt {
		o(&opts)
	}
	s := &Server{
		lis:    make(map[net.Listener]bool),
		opts:   opts,
		conns:  make(map[io.Closer]bool),
		m:      make(map[string]*service),
		quit:   make(chan struct{}),
		done:   make(chan struct{}),
		czData: new(channelzData),
	}
	s.cv = sync.NewCond(&s.mu)
	if EnableTracing {
		_, file, line, _ := runtime.Caller(1)
		s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line))
	}

	if channelz.IsOn() {
		s.channelzID = channelz.RegisterServer(&channelzServer{s}, "")
	}
	return s
}
  • 「opts := defaultServerOptions」 = ServerOption options structs の 一部フィールドに 値を代入

     var defaultServerOptions = options{
     	maxReceiveMessageSize: defaultServerMaxReceiveMessageSize,
     	maxSendMessageSize:    defaultServerMaxSendMessageSize,
     	connectionTimeout:     120 * time.Second,
     	writeBufferSize:       defaultWriteBufSize,
     	readBufferSize:        defaultReadBufSize,
     }
    
  • (あれば)ServerOption型( options structs )の引数で受け取った設定を、opts(defaultServerOptions)に代入する

     o(&opts)
    
     --------
    
     // A ServerOption sets options such as credentials, codec and keepalive parameters, etc.
     type ServerOption func(*options)
    
     --------
    
     type options struct {
     	creds                 credentials.TransportCredentials
     	codec                 baseCodec
     	cp                    Compressor
     	dc                    Decompressor
     	unaryInt              UnaryServerInterceptor
     	streamInt             StreamServerInterceptor
     	inTapHandle           tap.ServerInHandle
     	statsHandler          stats.Handler
     	maxConcurrentStreams  uint32
     	maxReceiveMessageSize int
     	maxSendMessageSize    int
     	unknownStreamDesc     *StreamDesc
     	keepaliveParams       keepalive.ServerParameters
     	keepalivePolicy       keepalive.EnforcementPolicy
     	initialWindowSize     int32
     	initialConnWindowSize int32
     	writeBufferSize       int
     	readBufferSize        int
     	connectionTimeout     time.Duration
     	maxHeaderListSize     *uint32
     }
    
  • Server structs に各種値を入れる

     // Server is a gRPC server to serve RPC requests.
     type Server struct {
     	opts options
    
    
     	mu     sync.Mutex // guards following
     	lis    map[net.Listener]bool
     	conns  map[io.Closer]bool
     	serve  bool
     	drain  bool
     	cv     *sync.Cond          // signaled when connections close for GracefulStop
     	m      map[string]*service // service name -> service info
     	events trace.EventLog
    
    
     	quit               chan struct{}
     	done               chan struct{}
     	quitOnce           sync.Once
     	doneOnce           sync.Once
     	channelzRemoveOnce sync.Once
     	serveWG            sync.WaitGroup // counts active Serve goroutines for GracefulStop
    
    
     	channelzID int64 // channelz unique identification number
     	czData     *channelzData
     }
    
  • 「s.cv = sync.NewCond(&s.mu)」 = r/wのlock管理を設定する

  • NewCond = read/writeのlockとunlockを管理する 用のメソッドらしい

     ```
     func NewCond(l Locker) *Cond
    
     -----
    
     type Locker interface {
             Lock()
             Unlock()
     }
    
     -----
    
     > func (rw *RWMutex) RLocker() Locker
     RLocker returns a Locker interface that implements the Lock and Unlock methods by calling rw.RLock and rw.RUnlock.
     ```
    
  • RPCsが利用できる状態かどうかをチェック

  • channelz(gRPCの中で異なったレベルで接続しているランタイム情報を提供するツール) がonになっているか確認

  • Server structsを返す

pb.RegisterGreeterServer(s, &server{})

「pb = helloworld」と定義しているので、helloworldディレクトリで定義した RegisterGreeterServer() である

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
	s.RegisterService(&_Greeter_serviceDesc, srv)
}

↑で使われている RegisterService()の詳細↓

func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {
	ht := reflect.TypeOf(sd.HandlerType).Elem()
	st := reflect.TypeOf(ss)
	if !st.Implements(ht) {
		grpclog.Fatalf("grpc: Server.RegisterService found the handler of type %v that does not satisfy %v", st, ht)
	}
	s.register(sd, ss)
}

→ 引数として渡されてきた &_Greeter_serviceDesc の型チェック & Serverに helloworldのMethodsを登録

var _Greeter_serviceDesc = grpc.ServiceDesc{
	ServiceName: "helloworld.Greeter",
	HandlerType: (*GreeterServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "SayHello",
			Handler:    _Greeter_SayHello_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "helloworld.proto",
}

------------------------------------------------------------------------------------------------------------------

func (s *Server) register(sd *ServiceDesc, ss interface{}) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.printf("RegisterService(%q)", sd.ServiceName)
	if s.serve {
		grpclog.Fatalf("grpc: Server.RegisterService after Server.Serve for %q", sd.ServiceName)
	}
	if _, ok := s.m[sd.ServiceName]; ok {
		grpclog.Fatalf("grpc: Server.RegisterService found duplicate service registration for %q", sd.ServiceName)
	}
	srv := &service{
		server: ss,
		md:     make(map[string]*MethodDesc),
		sd:     make(map[string]*StreamDesc),
		mdata:  sd.Metadata,
	}
	for i := range sd.Methods {
		d := &sd.Methods[i]
		srv.md[d.MethodName] = d
	}
	for i := range sd.Streams {
		d := &sd.Streams[i]
		srv.sd[d.StreamName] = d
	}
	s.m[sd.ServiceName] = srv
}

reflection.Register(s)

grpc-go/reflectionの serverreflection.go に定義ある

// Register registers the server reflection service on the given gRPC server.
func Register(s *grpc.Server) {
	rpb.RegisterServerReflectionServer(s, &serverReflectionServer{
		s: s,
	})
}

→ rpb = "google.golang.org/grpc/reflection/grpc_reflection_v1alpha" に処理を中継

func RegisterServerReflectionServer(s *grpc.Server, srv ServerReflectionServer) {
	s.RegisterService(&_ServerReflection_serviceDesc, srv)
}

→ _ServerReflection_serviceDesc をServerに登録

var _ServerReflection_serviceDesc = grpc.ServiceDesc{
	ServiceName: "grpc.reflection.v1alpha.ServerReflection",
	HandlerType: (*ServerReflectionServer)(nil),
	Methods:     []grpc.MethodDesc{},
	Streams: []grpc.StreamDesc{
		{
			StreamName:    "ServerReflectionInfo",
			Handler:       _ServerReflection_ServerReflectionInfo_Handler,
			ServerStreams: true,
			ClientStreams: true,
		},
	},
	Metadata: "grpc_reflection_v1alpha/reflection.proto",
}

< Side greeter_client >

■ greeter_client/main.go でやっていることを理解する

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)


	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)
}

conn, err := grpc.Dial(address, grpc.WithInsecure())

client系処理は grpc-go/clientconn.go に記載されている

// Dial creates a client connection to the given target.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
	return DialContext(context.Background(), target, opts...)
}

→ DialContext という他に比べると長いメソッドを返している
  この処理の詳細はコメントに丁寧にまとまっているので、理解するのはここを見るのが一番早かった

// DialContext creates a client connection to the given target. By default, it's
// a non-blocking dial (the function won't wait for connections to be
// established, and connecting happens in the background). To make it a blocking
// dial, use WithBlock() dial option.
//
// In the non-blocking case, the ctx does not act against the connection. It
// only controls the setup steps.
//
// In the blocking case, ctx can be used to cancel or expire the pending
// connection. Once this function returns, the cancellation and expiration of
// ctx will be noop. Users should call ClientConn.Close to terminate all the
// pending operations after this function returns.
//
// The target name syntax is defined in
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
// e.g. to use dns resolver, a "dns:///" prefix should be applied to the target.

↑の処理でやったことを要約すると...

  • まず渡してるものをおさらい

    • context.Background() = nil以外の空のContextを返す

    • target = address = :50051

    • opts ...DialOption = grpc.WithInsecure() = newFuncDialOption() = funcDialOption structs = dialOptions structs へ代入した変数

      func WithInsecure() DialOption {
      return newFuncDialOption(func(o *dialOptions) {
      o.insecure = true
      })
      }


      func newFuncDialOption(f func(*dialOptions)) *funcDialOption {
      return &funcDialOption{
      f: f,
      }
      }


      // funcDialOption wraps a function that modifies dialOptions into an
      // implementation of the DialOption interface.
      type funcDialOption struct {
      f func(*dialOptions)
      }


      // dialOptions configure a Dial call. dialOptions are set by the DialOption
      // values passed to Dial.
      type dialOptions struct {
      unaryInt UnaryClientInterceptor
      streamInt StreamClientInterceptor
      cp Compressor
      dc Decompressor
      bs backoff.Strategy
      block bool
      insecure bool
      timeout time.Duration
      scChan <-chan ServiceConfig
      authority string
      copts transport.ConnectOptions
      callOptions []CallOption
      // This is used by v1 balancer dial option WithBalancer to support v1
      // balancer, and also by WithBalancerName dial option.
      balancerBuilder balancer.Builder
      // This is to support grpclb.
      resolverBuilder resolver.Builder
      reqHandshake envconfig.RequireHandshakeSetting
      channelzParentID int64
      disableServiceConfig bool
      disableRetry bool
      disableHealthCheck bool
      healthCheckFunc internal.HealthChecker
      }

  • 指定したtargetでclient接続を作成する

  • デフォルトはブロックされてないからのcontext

c := pb.NewGreeterClient(conn)

server側と同じく、helloworldディレクトリで作成した NewGreeterClient()

func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
	return &greeterClient{cc}
}

→ GreeterClient interfacegrpc.Dial(address, grpc.WithInsecure()) で作成した context を代入している

// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
	// Sends a greeting
	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

→ 代入したContextにSayHello()を返す

残りの処理

	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)
  • defaultNamegreeter_client/main.go で記述されている
  • 引数が合った場合、nameを上書き
  • contextのタイムアウト時間を設定する
  • clientに登録したcontextに紐づけたSayHello()の実行
  • 結果を標準出力

まとめ

■ protoファイル でやっていることまとめ

  • 「server ー client」で呼び出すメソッドの定義

■ greeter_server/main.go でやっていることまとめ

  • 受付portの登録
  • serverとしての処理を起動するために設定を作成
  • 起動させるserver定義にprotoで定義したメソッドの実体を登録
  • gRPC serverとして↑で定義したserverを登録
  • ↑起動

■ greeter_client/main.go でやっていることまとめ

  • 空のcontextの作成
  • portの登録
  • 指定したtargetでclient接続を作成する
  • 作成したclient接続にprotoで定義したメソッドを登録
  • contextのタイムアウト時間を設定する
  • clientに登録したcontextに紐づけたSayHello()の実行
  • 結果を標準出力
18
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?