introduce

I suspect that most long-time Java developers will have less exposure to gRPC, given the Dubbo/SpringClound service frameworks used in the Java community.

I was introduced to gRPC recently when I had the opportunity to rebuild my business from scratch. There were several reasons why I chose gRPC:

  • Develop deployment projects based on the idea of cloud native, while in cloud nativegRPCIt’s almost standard communication protocol.
  • The development language is Go, in the Go circlegRPCObviously the better choice.
  • Part of the company’s internal business usesPythonDevelopment, on multilingual compatibilitygRPCThe support is very good.

After more than a year of smooth operation, it can be seen that gRPC is very stable and efficient; The core points of RPC framework are:

  • serialization
  • Communication protocol
  • IDL (Interface Description Language)

These correspond to the following in gRPC:

  • Based on theProtocol BufferSerialization protocol, high performance.
  • Based on theHTTP/2Standard protocol development, ownstream, multiplexing and other features; Also, because it is a standard protocol, third-party tools are more compatible (such as load balancing, monitoring, etc.)
  • Write a.protoInterface file, you can generate common language code.

HTTP/2

Before learning gRPC, we must first know what protocol it communicates through. HTTP/1.1 is the most common protocol we are exposed to in daily development or application.

Because HTTP/1.1 is a text protocol, it is very human-friendly, whereas machine performance is relatively low.

The need to repeatedly parse text is naturally inefficient; To be machine-friendly, you have to go binary, and HTTP/2 naturally does.

There are other advantages:

  • Multiplexing: Messages that can be sent and received in parallel without affecting each other
  • HPACKsaveheaderSpace, avoidHTTP1.1For the sameheaderSend repeatedly.

Protocol

GRPC uses Protocol serialization, which was released earlier than gRPC, so it is not only used for gRPC, but can be used in any scenario that requires serialization of IO operations.

It will be more space saving, high performance; Previously in development github.com/crossoverJi… Is used to do data interaction.

package order.v1; service OrderService{ rpc Create(OrderApiCreate) returns (Order) {} rpc Close(CloseApiCreate) returns (Order) {} // RPC ServerStream(stream OrderApiCreate) returns (stream Order) {} Returns (Order) {} RPC BdStream(stream OrderApiCreate) returns (stream Order) {}} message OrderApiCreate{int64  order_id = 1; repeated int64 user_id = 2; string remark = 3; repeated int32 reason_id = 4; }Copy the code

It is also very simple to use, just define your own.proto file and use the command line tool to generate the SDK for the corresponding language.

For details, see grpc. IO /docs/ Langua…

call

	protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    test.proto
Copy the code

Once you’ve generated the code, writing the server side is pretty straightforward; you just need to implement the generated interface.

func (o *Order) Create(ctx context.Context, in *v1.OrderApiCreate) (*v1.Order, error) {
	/ / get the metadata
	md, ok := metadata.FromIncomingContext(ctx)
	if! ok {return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
	}
	fmt.Println(md)
	fmt.Println(in.OrderId)
	return &v1.Order{
		OrderId: in.OrderId,
		Reason:  nil,},nil
}
Copy the code

The client side is also very simple, just rely on the server side code, create a connection and then just call the local method.

This is a classic unary(unary) call, similar to the HTTP request-response pattern, with one response per request.

Server stream

GRPC supports server push in addition to regular unary calls, which can be useful in some specific scenarios.

func (o *Order) ServerStream(in *v1.OrderApiCreate, rs v1.OrderService_ServerStreamServer) error {
	for i := 0; i < 5; i++ {
		rs.Send(&v1.Order{
			OrderId: in.OrderId,
			Reason:  nil})},return nil
}
Copy the code

As shown above, the server push can be pushed to the client by calling the Send function.

	for {
		msg, err := rpc.RecvMsg()
		if err == io.EOF {
			marshalIndent, _ := json.MarshalIndent(msgs, ""."\t")
			fmt.Println(msg)
			return}}Copy the code

The client obtains the server message through a loop to determine whether the currently received packet has expired.

In order to show this process more intuitively, we optimized a gRPC client developed before, which can intuitively debug stream calls.

An example of server push is shown above.

Client Stream

In addition to server push support, client support is also available.

The client keeps sending data to the server in the same connection, and the server can process the messages in parallel.


// Server code
func (o *Order) ClientStream(rs v1.OrderService_ClientStreamServer) error {
	var value []int64
	for {
		recv, err := rs.Recv()
		if err == io.EOF {
			rs.SendAndClose(&v1.Order{
				OrderId: 100,
				Reason:  nil,
			})
			log.Println(value)
			return nil
		}
		value = append(value, recv.OrderId)
		log.Printf("ClientStream receiv msg %v", recv.OrderId)
	}
	log.Println("ClientStream finish")
	return nil
}

	// Client code
	for i := 0; i < 5; i++ {
		messages, _ := GetMsg(data)
		rpc.SendMsg(messages[0])
	}
	receive, err := rpc.CloseAndReceive()
Copy the code

The code is similar to server-side push, but the roles are reversed.

Bidirectional Stream

The same is true when both the client and the server are sending messages at the same time.

/ / the server
func (o *Order) BdStream(rs v1.OrderService_BdStreamServer) error {
	var value []int64
	for {
		recv, err := rs.Recv()
		if err == io.EOF {
			log.Println(value)
			return nil
		}
		iferr ! =nil {
			panic(err)
		}
		value = append(value, recv.OrderId)
		log.Printf("BdStream receiv msg %v", recv.OrderId)
		rs.SendMsg(&v1.Order{
			OrderId: recv.OrderId,
			Reason:  nil})},return nil
}
/ / the client
	for i := 0; i < 5; i++ {
		messages, _ := GetMsg(data)
		// Send a message
		rpc.SendMsg(messages[0])
		// Receive the message
		receive, _ := rpc.RecvMsg()
		marshalIndent, _ := json.MarshalIndent(receive, ""."\t")
		fmt.Println(string(marshalIndent))
	}
	rpc.CloseSend()
Copy the code

It’s a combination of the two.

It’s easy to understand by calling the sample.

metadata

GRPC also supports metadata transfer, similar to headers in HTTP.

	// Write to the client
	metaStr := `{"lang":"zh"}`
	var m map[string]string
	err := json.Unmarshal([]byte(metaStr), &m)
	md := metadata.New(m)
	// Call CTX
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	
	// The server receives
	md, ok := metadata.FromIncomingContext(ctx)
	if! ok {return nil, status.Errorf(codes.DataLoss, "failed to get metadata")
	}
	fmt.Println(md)	
Copy the code

gRPC gateway

Although gRPC is powerful and easy to use, it is not as widely supported as REST for browsers and APPS (browsers also support, but very few applications).

The community created github.com/grpc-ecosys… Project to expose gRPC services as RESTFUL apis.

In order to let the test can be used to postman interface testing, we also proxy gRPC service out, more convenient for testing.

Reflection calls

As an RPC framework, generalization call must be supported, which can facilitate the development of supporting tools. GRPC is supported by reflection, by getting the service name, PB file for reflection call.

Github.com/jhump/proto… This library encapsulates common reflection operations.

The visual Stream calls seen in the figure above are also implemented through this library.

Load balancing

Since gRPC is implemented based on HTTP/2, the client and server maintain a long connection. Load balancing is not as simple as HTTP.

With gRPC, we want to achieve the same effect as HTTP, requiring load balancing of requests rather than connections.

There are two ways to do this:

  • Client load balancing
  • Server load balancing

Client load balancing is widely used in RPC calls, such as Dubbo.

GRPC also provides related interfaces. For details, see official Demo.

Github.com/grpc/grpc-g…

Client-side load balancing is relatively flexible for developers (they can customize their own policies), but they also need to maintain this piece of logic themselves, and multiple copies if there are multiple languages.

Therefore, in the context of cloud native, server-side load balancing is recommended.

The options are as follows:

  • istio
  • envoy
  • apix

We’re also looking at this, most likely using envoy/ Istio.

conclusion

GRPC content is still very much, this article is only as an introduction to the hope that can let do not understand gRPC can have a basic understanding; This is a necessary skill in the cloud native era.

Friends interested in the gRPC client in the article can refer to the source code here:

Github.com/crossoverJi…