Brief introduction and detailed explanation of gRPC

What is gRPC

What is gRPC? It can be summarized in a sentence on the official website: a high performance, open source universal RPC framework.

The so-called RPC(remote procedure call) framework actually provides a set of mechanism, which enables communication between applications, and also complies with the server/client model. When using, the client invokes the interface provided by the server as if it were a local function. As shown in the figure below is a typical RPC structure diagram.

 

gRPC vs Restful API

Both gRPC and restful API provide a set of communication mechanism for server/client model communication, and both of them use HTTP as the underlying transport protocol (strictly speaking, gRPC uses http2.0, while restful API is not necessarily). However, gRPC has some unique advantages, as follows:

gRPC can define the interface through protobuf, so it can have more strict interface constraints. For protobuf, please refer to the next brief tutorial of protobuf. In addition, data can be serialized into binary code through protobuf, which will greatly reduce the amount of data to be transmitted, thus greatly improving performance.

gRPC can easily support streaming communication (in theory, streaming mode can be used through http2.0, but usually the restful api of Web services seems to be rarely used in this way. Generally, streaming data applications such as video streaming use special protocols such as HLS, RTMP, etc., which are not our usual web services, but have special server applications.)

 

Usage scenarios

  • For example, we provide a public service that many people, even people outside the company, can access. At this time, we want to have more strict constraints on the interface. We don't want the client to pass us any data, especially considering the security factor, we usually need to do more on the interface Strict constraints. At this time, gRPC can provide strict interface constraints through protobuf.

  • When there are higher requirements for performance. Sometimes our service needs to deliver a large amount of data, but we hope it doesn't affect our performance. At this time, we can also consider gRPC service, because through protobuf we can transform data compression coding into binary format, usually the amount of data transferred is much smaller, and through http2 we can realize asynchronous requests, thus greatly improving the communication efficiency.

However, we usually do not use gRPC alone, but use gRPC as a component. This is because in the production environment, we need to use the distributed system to deal with the situation of large concurrency, and gRPC does not provide some necessary components related to the distributed system. Moreover, the real online service also needs to provide necessary components including load balancing, current limiting and fusing, monitoring and alarming, service registration and discovery, etc. However, this is not the topic of this article. Let's continue to see how to use gRPC.

 

gRPC DEMO instance details

  • Define interface and data type through protobuf

  • Write gRPC server code

  • Write gRPC client-side code
    This article uses golang to implement demo, in which the installation of protobuf and grpc extensions is skipped.

New userrpc.proto

syntax = "proto3";
package user;
option go_package = "./grpc/user";

// The User service definition.
service User {   
  // Get all Users with id - A server-to-client streaming RPC.
  rpc GetUsers(UserFilter) returns (stream UserRequest) {}
  // Create a new User - A simple RPC 
  rpc CreateUser (UserRequest) returns (UserResponse) {}
}

// Request message for creating a new user
message UserRequest {
  int32 id = 1;  // Unique ID number for a User.
  string name = 2;
  string email = 3;
  string phone= 4;

  message Address {
    string province = 1;
    string city = 2;  
  }
  repeated Address addresses = 5;
}

message UserResponse {
  int32 id = 1;
  bool success = 2;
}
message UserFilter {
  int32 id = 1;
}

Compile. proto file


protoc  --go_out=plugins=grpc:. userrpc.proto

 

New server.go

package main

import (
    "log"
    "net"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "userrpc/grpc/user"
)

const (
    port = ":50051"
)

// server is used to implement user.UserServer.
type server struct {
    savedUsers []*pb.UserRequest
}

// CreateUser creates a new User
func (s *server) CreateUser(ctx context.Context, in *pb.UserRequest) (*pb.UserResponse, error) {

    s.savedUsers = append(s.savedUsers, in)
    return &pb.UserResponse{Id: in.Id, Success: true}, nil
}

// GetUsers returns all users by given id
func (s *server) GetUsers(filter *pb.UserFilter, stream pb.User_GetUsersServer) error {
    for _, user := range s.savedUsers {
        if filter.Id == 0 {
            continue
        }
        if err := stream.Send(user); err != nil {
            return err
        }
    }
    return nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    // Creates a new gRPC server
    s := grpc.NewServer()
    pb.RegisterUserServer(s, &server{})
    s.Serve(lis)
}

client.go

package main

import (
    "io"
    "log"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "userrpc/grpc/user"
)

const (
    address = "localhost:50051"
)

// createUser calls the RPC method CreateUser of UserServer
func createUser(client pb.UserClient, user *pb.UserRequest) {
    resp, err := client.CreateUser(context.Background(), user)
    if err != nil {
        log.Fatalf("Could not create User: %v", err)
    }
    if resp.Success {
        log.Printf("A new User has been added with id: %d", resp.Id)
    }
}

// getUsers calls the RPC method GetUsers of UserServer
func getUsers(client pb.UserClient, id *pb.UserFilter) {
    // calling the streaming API
    stream, err := client.GetUsers(context.Background(), id)
    if err != nil {
        log.Fatalf("Error on get users: %v", err)
    }
    for {
        user, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("%v.GetUsers(_) = _, %v", client, err)
        }
        log.Printf("User: %v", user)
    }
}
func main() {
    // Set up a connection to the gRPC server.
    conn, err := grpc.Dial(address, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    // Creates a new UserClient
    client := pb.NewUserClient(conn)

    user := &pb.UserRequest{
        Id:    1,
        Name:  "test",
        Email: "fasd@163.com",
        Phone: "132222222",
        Addresses: []*pb.UserRequest_Address{
            &pb.UserRequest_Address{
                Province: "hebei",
                City:     "shijiazhuang",
            },
        },
    }

    // Create a new user
    createUser(client, user)
    // Filter with an  id
    filter := &pb.UserFilter{Id: 1}
    getUsers(client, filter)
}

Start server.go

go run server.go

Open a new window and start client.go

go run client.go

The result is

2019/07/04 17:01:16 A new User has been added with id: 1
2019/07/04 17:01:16 User: id:1 name:"test" email:"fasd@163.com" phone:"132222222" addresses:<province:"hebei" city:"shijiazhuang" >

Summary

Api implementation is cumbersome, which brings difficulty to development. Generally speaking, gRPC is a good cross language rpc solution, of course, everyone has their own views or opinions. Using different solutions for different business scenarios is the result of compromise between operation efficiency and development efficiency.

 

Ruijiang cloud official website link: https://www.eflycloud.com/home?from=RJ0037

Tags: Google

Posted on Thu, 12 Mar 2020 01:03:00 -0400 by steveclondon