Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go gRPC simple service Asynchronous and Synchronous explanation

Tags:

go

grpc

I am trying to understand GoLang "Go" together with gRPC and to make a simple service scalable.

Lets say I have a client1 that calls a service1(adds numbers) that calls service2(determines if the result is prime), and service2 returns the result to service1 that returns the result to client1 all via gRPC.

When I use protocol buffers "proto3" and generate the Go code via protoc. I get generated methods that call the service in one particular way. I see no distinction to call the methods asynchronously "Go".

And the underlying call seems to be "Invoke" which I believe is synchronous,the call returns once a result is received.

How do I make service1 "performant", I know I can run this in a cluster and have copies, but that would mean I can only serve clients as per the amount of instances within the cluster.

I want a "single" service to be able to serve multiple clients(e.g. 1000) .

Here is a simple server and I am not sure if this is performant or not: I do know that the getprime function does dial every time, and this could probably be moved to make this dial persist and be re-used; But more importantly I want to make a simple performant scaleable service and get a good understanding.

(A) Perhaps the whole design is incorrect and the service1 should just return as soon as the instruction is received "ack", do the addition and send the next request to sercice2 which determines if the answer is prime or not; again service2 just responds with an acknowledgement of the request being received. Once prime is determined by the service2 a call is made to the client with an answer.

If (A) above is the better approach, then still please explain the bottlenecks below; what happens when multiple clients are processed? The call to "Listen" does what, "blocks, or does not block", etc.

package main

import (
    pb "demo/internal/pkg/proto_gen/calc"
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
    "log"
    "net"
)

const (
    port = ":8080"
)

type service struct {
}

func (s *service) Calculate(ctx context.Context, req *pb.Instruction) (*pb.Response, error) {

    var answer float64
    answer = req.Number1 + req.Number2

    // call service prime
    p := getprime(int(answer))
    pa := pb.PrimeAnswer{Prime: p}
    return &pb.Response{Answer: answer, Prime: &pa}, nil
}

const (
    primeAddress = "127.0.0.1:8089"
)

func getprime(number int) bool {
    conn, err := grpc.Dial(primeAddress, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("Did not connect to prime service: %v", err)
    }
    defer conn.Close()

    client := pb.NewPrimeServiceClient(conn)
    p := pb.PrimeMessage{"", float64(number)}

    r, err := client.Prime(context.Background(), &p)
    if err != nil {
        log.Fatalf("Call to prime service failed: %v", err)
    }
    return r.Prime
}

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

    s := grpc.NewServer()
    pb.RegisterCalculatorServer(s, &service{})
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
like image 975
Wayne Avatar asked Mar 12 '18 20:03

Wayne


2 Answers

Thanks for your question. It is true that gRPC-Go is sync only; that is your Unary RPC(the one in your example) will return only when the RPC has finished (got a response from the server).

About performance:

  1. The Dial operation establishes an underlying connection which may be expensive. So it not wise to do it every time getprime is called. A better way is to create a client, keep it around and make calls to the prime server on it. This way only first RPC incurs the cost of connection.
  2. For each RPC request a server gets we launch a goroutine to process that request. So in general, this should scale fairly well.

About (A): It is not uncommon for a service handler to make an RPC call to yet another server and wait for its response before returning back. Note that there's no way for a server to make call to the client.

like image 55
Mahak Mukhi Avatar answered Oct 05 '22 22:10

Mahak Mukhi


To phrase what JimB said as an answer: "Synchronous" means that the function that makes the remote call waits for a reply before continuing, not that the whole server or client does. The server is normally multithreaded, even when processing synchronous calls; it can accept and work on a second call while it's responding to the first.

And similarly, if a client has multiple concurrent tasks that each have a gRPC call running, that won't block the process. Clients like that could include net/http servers serving end users, or gRPC servers handling multiple RPCs.

Where you might add explicit go statements is if you want to do something else from the specific function making the RPC call. For example, if you want to issue several RPC calls at once then wait for all their results to come in, you could write code following the examples of fan-out calls.

like image 23
twotwotwo Avatar answered Oct 06 '22 00:10

twotwotwo