Microservices Communication using gRPC Protocol
Today, we'll explore the gRPC protocol, a technology that has achieved remarkable success by opting for HTTP/2 over HTTP/1.1 and embracing protocol buffers in place of XML and JSON.
Microservices inter-process communication
In a microservices-based software system, inter-process communication is vital for applications to exchange information. Microservices can interact with each other through two primary communication mechanisms:
The most prevalent approach for implementing request-reply communication is often through RESTful services, typically utilizing JSON payloads over HTTP. However, RESTful services come with some drawbacks, including being bulky, less efficient, and constraining for certain scenarios. The bulkiness of RESTful services primarily stems from their reliance on JSON, which employs a text-based encoding format that consumes more space compared to binary formats.
Why gRPC for microservices communication?
In order to address the limitations commonly found in RESTful services, there is a growing need for a contemporary inter-process communication system that offers scalability and superior efficiency. Enter gRPC, an advanced open-source RPC framework.
The gRPC framework leverages a binary encoding format called protocol buffers and operates atop HTTP/2. It boasts strong typing, supports multiple programming languages, and facilitates bidirectional streaming for both client and server sides.
gRPC benefits
gRPC offers a refreshed take on the old RPC design method by making it interoperable, modern, and efficient using such technologies as Protocol Buffers and HTTP/2. The following benefits make it a solid candidate for replacing REST in some operations.
Lightweight messages. Depending on the type of call, gRPC-specific messages can be up to 30 percent smaller in size than JSON messages.
High performance. By different evaluations, gRPC is 5, 7, and even 8 times faster than REST+JSON communication.
Built-in code generation. gRPC has automated code generation in different programming languages including Java, C++, Python, Go, Dart, Objective-C, Ruby, and more.
More connection options. While REST focuses on request-response architecture, gRPC provides support for data streaming with event-driven architectures: server-side streaming, client-side streaming, and bidirectional streaming.
Here are the main types of gRPC:
Example using Golang
This is a basic gRPC server and client written in Go. It is based on the gRPC Quickstart and gRPC Basics: Go tutorials .
We are going to implemented a simple gRPC server and client with the following functionality:
Setting up a gRPC-Go project
mkdir GO-grpc-demo
cd GO-grpc-demo
mkdir client server proto
go get google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
export PATH="$PATH:$(go env GOPATH)/bin"
go mod init github.com/prabhatpankaj/GO-grpc-demo
go mod tidy
// proto/greet.proto
// for syntax highlighting we use proto3 version
syntax="proto3";
// path to our current folder, where the generated files will be placed
option go_package = "./proto";
// package name for our proto file
package greet_service;
// defining all the Services for the gRPC
service GreetService {
// simple Unary RPC
rpc SayHello(NoParam) returns (HelloResponse);
// server streaming RPC
rpc SayHelloServerStreaming(NamesList) returns (stream HelloResponse);
// client streaming RPC
rpc SayHelloClientStreaming(stream HelloRequest) returns (MessagesList);
// bidirectional streaming RPC
rpc SayHelloBidirectionalStreaming(stream HelloRequest) returns (stream HelloResponse);
}
// when you are not passing any params still you will need to add a type
// NoParam acts as a type for no parameters given.
message NoParam {};
message HelloRequest {
string name = 1;
}
message HelloResponse {
string message = 1;
}
message NamesList {
repeated string names = 1;
}
message MessagesList {
repeated string messages = 1;
}
protoc --go_out=. --go-grpc_out=. proto/greet.proto
This will generate greet_grpc.pb.go and greet.pb.go as
package main
import (
"fmt"
"log"
"net"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
)
const (
port = "8080"
)
// this is the struct to be created, pb is imported upstairs
type helloServer struct {
pb.GreetServiceServer
}
func main() {
//listen on the port
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port))
if err != nil {
log.Fatalf("Failed to start server %v", err)
}
// create a new gRPC server
grpcServer := grpc.NewServer()
// register the greet service
pb.RegisterGreetServiceServer(grpcServer, &helloServer{})
log.Printf("Server started at %v", lis.Addr())
//list is the port, the grpc server needs to start there
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("Failed to start: %v", err)
}
}
package main
import (
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreetServiceClient(conn)
names := &pb.NamesList{
Names: []string{"Akhil", "Alice", "Bob"},
}
// need to write grpc type function here
}
Simple Unary RPC
client/unary.go
package main
import (
"context"
"log"
"time"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
// callSayHello will need to be called from main.go to make the call to the server
func callSayHello(client pb.GreetServiceClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := client.SayHello(ctx, &pb.NoParam{})
if err != nil {
log.Fatalf("Could not greet: %v", err)
}
log.Printf("%s", res.Message)
}
领英推荐
package main
import (
"context"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func (s *helloServer) SayHello(ctx context.Context, req *pb.NoParam) (*pb.HelloResponse, error) {
return &pb.HelloResponse{
Message: "Hello unary",
}, nil
}
package main
import (
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreetServiceClient(conn)
callSayHello(client)
}
hello unary is coming from server/unary.go as simple request/response .
server-side streaming RPC
client/server_stream.go
package main
import (
"context"
"io"
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func callSayHelloServerStream(client pb.GreetServiceClient, names *pb.NamesList) {
log.Printf("Streaming started")
stream, err := client.SayHelloServerStreaming(context.Background(), names)
if err != nil {
log.Fatalf("Could not send names: %v", err)
}
for {
message, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("Error while streaming %v", err)
}
log.Println(message)
}
log.Printf("Streaming finished")
}
package main
import (
"log"
"time"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func (s *helloServer) SayHelloServerStreaming(req *pb.NamesList, stream pb.GreetService_SayHelloServerStreamingServer) error {
log.Printf("Got request with names : %v", req.Names)
for _, name := range req.Names {
res := &pb.HelloResponse{
Message: "Hello " + name,
}
if err := stream.Send(res); err != nil {
return err
}
// 2 second delay to simulate a long running process
time.Sleep(2 * time.Second)
}
return nil
}
package main
import (
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreetServiceClient(conn)
names := &pb.NamesList{
Names: []string{"name 1", "name 2", "name 3"},
}
// callSayHello(client)
callSayHelloServerStream(client, names)
}
Here i am sending Names: []string{"name 1", "name 2", "name 3"} from client and in return getting stream of data from server as below.
client-side streaming RPC
client/client_stream.go
package main
import (
"context"
"log"
"time"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func callSayHelloClientStream(client pb.GreetServiceClient, names *pb.NamesList) {
log.Printf("Client Streaming started")
stream, err := client.SayHelloClientStreaming(context.Background())
if err != nil {
log.Fatalf("Could not send names: %v", err)
}
for _, name := range names.Names {
req := &pb.HelloRequest{
Name: name,
}
if err := stream.Send(req); err != nil {
log.Fatalf("Error while sending %v", err)
}
log.Printf("Sent request with name: %s", name)
time.Sleep(2 * time.Second)
}
res, err := stream.CloseAndRecv()
log.Printf("Client Streaming finished")
if err != nil {
log.Fatalf("Error while receiving %v", err)
}
log.Printf("%v", res.Messages)
}
package main
import (
"io"
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func (s *helloServer) SayHelloClientStreaming(stream pb.GreetService_SayHelloClientStreamingServer) error {
var messages []string
for {
req, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.MessagesList{Messages: messages})
}
if err != nil {
return err
}
log.Printf("Got request with name : %v", req.Name)
messages = append(messages, "Hello "+req.Name)
}
}
package main
import (
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreetServiceClient(conn)
names := &pb.NamesList{
Names: []string{"name 1", "name 2", "name 3"},
}
// callSayHello(client)
// callSayHelloServerStream(client, names)
callSayHelloClientStream(client, names)
}
Here i am sending Names: []string{"name 1", "name 2", "name 3"} from client as stream . Once Client streaming finished , it returns a single response message as below.
bidirectional streaming RPC
client/bi_stream.go
package main
import (
"context"
"io"
"log"
"time"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func callSayHelloBidirectionalStream(client pb.GreetServiceClient, names *pb.NamesList) {
log.Printf("Bidirectional Streaming started")
stream, err := client.SayHelloBidirectionalStreaming(context.Background())
if err != nil {
log.Fatalf("Could not send names: %v", err)
}
waitc := make(chan struct{})
go func() {
for {
message, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("Error while streaming %v", err)
}
log.Println(message)
}
close(waitc)
}()
for _, name := range names.Names {
req := &pb.HelloRequest{
Name: name,
}
if err := stream.Send(req); err != nil {
log.Fatalf("Error while sending %v", err)
}
time.Sleep(2 * time.Second)
}
stream.CloseSend()
<-waitc
log.Printf("Bidirectional Streaming finished")
}
package main
import (
"io"
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
)
func (s *helloServer) SayHelloBidirectionalStreaming(stream pb.GreetService_SayHelloBidirectionalStreamingServer) error {
for {
req, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
log.Printf("Got request with name : %v", req.Name)
res := &pb.HelloResponse{
Message: "Hello " + req.Name,
}
if err := stream.Send(res); err != nil {
return err
}
}
}
package main
import (
"log"
pb "github.com/prabhatpankaj/GO-grpc-demo/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
const (
port = ":8080"
)
func main() {
conn, err := grpc.Dial("localhost"+port, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
client := pb.NewGreetServiceClient(conn)
names := &pb.NamesList{
Names: []string{"name 1", "name 2", "name 3"},
}
// callSayHello(client)
// callSayHelloServerStream(client, names)
// callSayHelloClientStream(client, names)
callSayHelloBidirectionalStream(client, names)
}
Here i am sending Names: []string{"name 1", "name 2", "name 3"} from client.
It started streaming from client and server as bidirectional stream as below.
Final Code can be downloaded from Github Repo
References: