RPC
Those unfamiliar with RPC may struggle with its relationship to TCP, HTTP, and so on. The latter is a protocol in network transmission, and RPC is a design and implementation framework, communication protocol is only a part of it, RPC not only to solve the problem of protocol communication, but also serialization and deserialization, as well as message notification.
A complete RPC architecture contains four core components, namely Client,Server,ClientOptions and ServerOptions. This option is what RPC needs to be designed and implemented.
-
Client: The caller of a service.
-
Server: The real service provider.
-
ClientOption: Socket management, serialization of network incoming and outgoing packets.
-
ServerOption: Socket management, alerting the Server layer to RPC method calls, and serialization of network packets.
The logic diagram of RPC is as follows
What is the gRPC
GRPC is a kind of RPC. It uses Protocol Buffer(Protobuf for short) as the serialization format. Protocol Buffer is a serialization framework from Google, which is lighter and more efficient than Json. Brings features such as bidirectional streaming, flow control, header compression, multiplexing requests over a single TCP connection, and more. These features allow it to perform better on mobile devices, saving power and space. With protoc you can use the proto file to help us generate the option layer code above.
In gRPC, a client application can call a method directly on a server application on another computer as if it were a local object, making it easier for you to create distributed applications and services.
The call model of gRPC is as follows
Applicable scenario
- Distributed scenarios: gRPC is designed for low latency and high throughput communications, making it ideal for lightweight microservices where efficiency is critical.
- Point-to-point real-time communication: gRPC provides excellent support for two-way streaming media and can push messages in real time without polling.
- Multilingual hybrid Development: Support for mainstream development languages makes gRPC an ideal choice for multilingual development environments.
- Network constrained environment: Serialize gRPC messages using Protobuf, a lightweight message format. The gRPC message is always smaller than the equivalent JSON message.
Four ways to call
Before learning gRPC, let’s introduce the client and server in RPC. In RPC, the server will open the service for the client to call, and each RPC call is a process that the client sends a request to the server to obtain the corresponding process. The intermediate process is encapsulated and looks like a local call. An RPC call is also a communication process.
RPC calls can be divided into single RPC, server – side streaming RPC, client – side streaming RPC and bidirectional streaming PRC, depending on whether the two ends interact with each other streaming. In order to facilitate you to understand the application scenarios of the four GRPC calls, here is an example. Suppose you are xiaochao and have a girlfriend named Tingting. Each emotion of Tingting represents a micro service, and each conversation between you can be interpreted as a CALL from PRC. The request package is HelloRequest and the response is HelloReply.
1. A single RPC
When you are waiting for Tingting to go back for dinner and Tingting is working overtime, the RPC call between you might look like this:
Xiao Chao: Coming back for dinner
Tingting: Still working overtime
This is called one-way RPC, where the client sends a request to the server and gets a response from the server, just like a normal function call.
- The client layer calls the SayHello interface to serialize the HelloRequest package
- Client Option sends serialized data to the server
- Server Option received an RPC request. Procedure
- The RPC request is returned to the server, which processes the request and gives the result to Server Option
- Server Option serializes the HelloReply and sends it to the client
- Client Option does the deserialization and returns it to the client layer
2. Streaming RPC on the server
When you lose a match, send a message to Tingting:
Xiao Chao: I lost today
Tingting: No, it’s just a competition
Tingting: Take you out for a nice dinner tonight
This is called server-side streaming RPC, where a client sends a request to a server and gets a data stream to read a series of messages. The client reads from the returned data stream until there are no more messages.
- The client layer calls the SayHello interface to serialize the HelloRequest package
- Client Option sends serialized data to the server
- Server Option received an RPC request. Procedure
- The RPC request is returned to the server, which processes it and streams data to Server Option
- Server Option serializes the HelloReply and sends it to the client
- Client Option does the deserialization and returns it to the client layer
3. Stream RPC on the client
When you upset Tingting:
Xiao Chao: What’s up, honey
Xiao Chao: Don’t be angry, take you to eat delicious
Tingting: roll
Client-side streaming RPC, in which a client writes and sends a series of messages to a server using a provided data stream. Once the client has finished writing messages, it waits for the server to read those messages and return a reply,
- The client layer calls the SayHello interface to serialize the HelloRequest package
- Client Option sends the serialized data stream to the server
- Server Option received an RPC request. Procedure
- The RPC request is returned to the server, which processes the request and gives the result to Server Option
- Server Option serializes the HelloReply and sends it to the client
- Client Option does the deserialization and returns it to the client layer
4. Bidirectional flow RPC
When you’re done with Tingting:
Xiao Chao: I watched a very nice video today
Tingting: What video
Xiao Chao: I’ll send it to you
Tingting: Is that good?
Two-way flow RPC, in which both sides can send a series of messages through a separate read/write data stream. The two data flow operations are independent of each other, so the client and server can read and write in any order they wish. For example, the server can wait for all client messages before writing a reply, or it can read one message and then write another, or some other combination of read and write. The order of messages in each data stream is maintained
This diagram does not make the process introduction, readers can try to see whether the diagram can understand the process, I believe that the understanding of client flow RPC and server flow RPC two ways, here must be understandable.
GPRC code implementation
GRPC uses Protobuf as its serialization format, which is lighter and more efficient than Json. Like Json, it is language – and platform-independent and extensible. About developers.google.com/protocol-bu Protobuf use please refer to the website address… .
Below we implement the Go language version of the four gRPC call way.
1. One-way RPC implementation
Write the proto
/ / proto3 standards
syntax = "proto3";
/ / package name
package helloworld;
// Define the RPC interface
service Greets {
rpc SayHello (HelloRequest) returns (HelloReply) {}}//HelloReply protocol content
message HelloReply {
string name = 1;
string message= 2; } / /HelloRequestProtocol message HelloRequest{
string name = 1;
string message = 2;
}
Copy the code
- Greet indicates the name of the Greet class that defines the RPC service. RPC SayHello (HelloRequest) returns (HelloReply) {} means to define the RPC method SayHello, pass HelloRequest, and return HelloReply
- Go to proto and run protoc -i. –go_out=plugins= GRPC:.. / helloWorld.proto in. The helloWorld.pb. go file is generated in the directory
Write the server
type Server struct{}// Implement SayHello interface
func (s *Server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Println(in.Name, in.Message)
return &pb.HelloReply{Name: "Tingting", Message: "Not coming back."}, nil
}
func main(a) {
// Protocol type and IP address, port
lis, err := net.Listen("tcp".": 8002")
iferr ! =nil {
fmt.Println(err)
return
}
// Define an RPC server
server := grpc.NewServer()
// Register the service, similar to register the SayHello interface
pb.RegisterGreetsServer(server, &Server{})
// Perform mapping binding
reflection.Register(server)
// Start the service
err = server.Serve(lis)
iferr ! =nil {
fmt.Println(err)
return}}Copy the code
-
Pb is the alias of the file generated by proto
-
Define the server structure as the structure for RPC calls. This structure must implement the SayHello interface
-
listen -> grpc.NewServer() -> pb.RegisterGreetsServer(server, &Server{}) -> s.Serve(lis)
Write the client
func main(a) {
// Create a GRPC connection
conn, err := grpc.Dial("localhost:8002", grpc.WithInsecure())
iferr ! =nil {
fmt.Println(err)
return
}
defer conn.Close()
// Create an RPC client
client := pb.NewGreetsClient(conn)
// Set the timeout period
_, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
// Call the method
reply, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "Little super", Message: "Coming back for dinner?"})
iferr ! =nil {
log.Fatalf("couldn not greet: %v", err)
}
log.Println(reply.Name, reply.Message)
}
Copy the code
- Grpc. Dial(“localhost:8002”, grpc.withinsecure ()) connects to the server, and grpc.withinsecure () cancels plaintext detection
- Context.withtimeout (context.background (), time.second) Sets the timeout period
- C := pb.NewGreetsClient(conn) Creates the client for RPC calls
- C. sayhello (context.background (), &pb.HelloRequest{Name: Name}) makes an RPC call
Abstract interface
In fact, this is to implement this interface, because both sides are single invocation, so the call and implementation of the interface is this
type GreetsClient interface{ SayHello(ctx context.Context, in *HelloRequest, opts ... grpc.CallOption) (*HelloReply, error) }Copy the code
2. RPC on the server
Write the proto
/ / proto3 standards
syntax = "proto3";
/ / package name
package helloworld;
// Define the RPC interface
service Greet{
rpc SayHello (HelloRequest) returns(stream HelloReply) {}}//HelloReply protocol content
message HelloReply {
string name = 1;
string message= 2; } / /HelloRequestProtocol message HelloRequest{
string name = 1;
string message = 2;
}
Copy the code
Compared to a single RPC call, there is an extra stream in HelloRequest because it is a client stream
Write the server
type Server struct{}// Implement the RPC interface
func (*Server) SayHello(request *pb.HelloRequest, server pb.Greet_SayHelloServer) error {
fmt.Println(request)
var err error
for i := 0; i < 2; i++ {
if i == 0 {
err = server.Send(&pb.HelloReply{Name: "Little super", Message: "It's okay. It's just a race."})}else {
err = server.Send(&pb.HelloReply{Name: "Little super", Message: "Take you out for a nice dinner tonight."})}iferr ! =nil {
fmt.Println(err)
return err
}
}
return nil
}
func main(a) {
// Protocol type and IP address, port
listen, err := net.Listen("tcp".": 8002")
iferr ! =nil {
fmt.Println(err)
return
}
// Define an RPC server
s := grpc.NewServer()
// Register the service, similar to register the SayHello interface
pb.RegisterGreetServer(s, &Server{})
// Perform mapping binding
reflection.Register(s)
// Start the service
err = s.Serve(listen)
iferr ! =nil {
fmt.Println(err)
}
}
Copy the code
Write the client
The client sends a stream. Different from the single RPC method, the client obtains a stream transfer object greetClient through RPC calls. The stream transfer object can be used to continuously send data to the peer end
func main(a) {
// Create a GRPC connection
grpcConn, err := grpc.Dial("127.0.0.1"+": 8002", grpc.WithInsecure())
iferr ! =nil {
fmt.Println(err)
return
}
// Create a GRPC client
client := pb.NewGreetClient(grpcConn)
// Set the timeout period
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Call the RPC method to get the stream interface
res, err := client.SayHello(ctx, &pb.HelloRequest{Name: "Little super", Message: "Lost the game today."})
iferr ! =nil {
fmt.Println(err)
return
}
// Loop to receive data
for {
recv, err := res.Recv()
iferr ! =nil {
fmt.Println(err)
break
}
fmt.Println(recv)
}
}
Copy the code
Abstract interface
Interface to be implemented by the server
// GreetsServer is the server API for Greets service.
type GreetsServer interface {
SayHello(Greets_SayHelloServer) error
}
Copy the code
The interface that the client invokes
type GreetsClient interface{ SayHello(ctx context.Context, opts ... grpc.CallOption) (Greets_SayHelloClient, error) }Copy the code
3. RPC on the server
Write the proto
/ / proto3 standards
syntax = "proto3";
/ / package name
package helloworld;
// Define the RPC interface
service Greets{
rpc SayHello (stream HelloRequest) returns (HelloReply) {}}//HelloReply protocol content
message HelloReply {
string name = 1;
string message= 2; } / /HelloRequestProtocol message HelloRequest{
string name = 1;
string message = 2;
}
Copy the code
Authoring server
type Server struct{}
// Implement the RPC method until the peer call CloseAndRecv reads the EOF
func (*Server) SayHello(in pb.Greets_SayHelloServer) error {
for {
recv, err := in.Recv()
// Send the response after receiving the data
if err == io.EOF {
err := in.SendAndClose(&pb.HelloReply{Name: "Tingting", Message: "Roll"})
iferr ! =nil {
return err
}
return nil
} else iferr ! =nil {
return err
}
fmt.Println(recv)
}
}
func main(a) {
// Bind protocol, IP, and port
lis, err := net.Listen("tcp".": 8002")
iferr ! =nil {
fmt.Println("failed to listen: %v", err)
return
}
// Create a GRPC service object
server := grpc.NewServer()
// Register the RPC service
pb.RegisterGreetsServer(server, &Server{})
// Register server reflection
reflection.Register(server)
// Start the server
err = server.Serve(lis)
iferr ! =nil {
fmt.Println(err)
return}}Copy the code
Writing a client
func main(a) {
// Create a GRPC connection
grpcConn, err := grpc.Dial("127.0.0.1"+": 8002", grpc.WithInsecure())
iferr ! =nil {
fmt.Println(err)
return
}
// Create a GRPC client
client := pb.NewGreetsClient(grpcConn)
// Set the timeout period
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Call the RPC method to get a client to loop data
greetClient, err := client.SayHello(ctx)
iferr ! =nil {
fmt.Println("sayHello error")
fmt.Println(err)
return
}
maxCount := 2
curCount := 0
// Send in a loop
// If CloseAndRecv() is called, the server will read the EOF, and the server can determine whether the client has sent the data according to the EOF
for {
if curCount == 0 {
err = greetClient.Send(&pb.HelloRequest{Name: "Little super", Message: "What's up, baby?"})}else {
err = greetClient.Send(&pb.HelloRequest{Name: "Little super", Message: "Don't be mad. Let's get you something to eat."})}iferr ! =nil {
fmt.Println("send error")
fmt.Println(err)
return
}
curCount += 1
if curCount >= maxCount {
res, err := greetClient.CloseAndRecv()
iferr ! =nil {
fmt.Println(err)
break
}
fmt.Println(res)
break}}}Copy the code
Abstract interface
Client interface
type GreetsClient interface { SayHello(ctx context.Context, opts ... grpc.CallOption) (Greets_SayHelloClient, error) }Copy the code
Server interface
// GreetsServer is the server API for Greets service.
type GreetsServer interface {
SayHello(Greets_SayHelloServer) error
}
Copy the code
Two-way flow of RPC
Two-way flow RPC to the reader to practice it, I believe that understand the single RPC, client flow RPC, server flow RPC three transmission methods, write two-way flow RPC should not have any problem.
Implementation summary
In fact, understand the single RPC, server-side streaming RPC, client streaming RPC, two-way flow PRC four GRPC application scenarios, it is very easy to achieve
- Select a gRPC service based on the application scenario
- Write proto file and use protoc to generate.pb.go file
- Server implementation interface -> LISTEN -> grpc.newServer () -> pb.RegisterGreetsServer(server, & server {}) -> s.sever (lis)
- Client grpc.Dial->pb.NewGreetsClient-> Context. WithTimeout->client.SayHello -> Loop reads data in case of stream transmission
Welcome to pay attention to my official account, check the super super follow-up content updates!