转到gRPC简单服务异步和同步说明

我试图与gRPC一起了解GoLang“ Go”,并使简单的服务可扩展。 假设我有一个client1,它调用一个service1(加号),再调用service2(确定结果是否为质数),而service2将结果返回给service1,该结果全部通过gRPC返回给client1。 当我使用协议缓冲区“ proto3”并通过protoc生成Go代码时,我得到了以一种特定方式调用服务的生成方法。我认为将这些方法异步称为“ Go”是没有区别的。 而且底层调用似乎是“调用”,我认为它是同步的,一旦收到结果,调用就会返回。 我如何使service1成为“ performant”,我知道我可以在集群中运行它并拥有副本,但这意味着我只能根据集群中实例的数量为客户端提供服务。 我希望“单一”服务能够为多个客户提供服务(例如1000)。 这是一个简单的服务器,我不确定这是否有效:我确实知道 getprime 函数每次都会进行拨号,并且可能会移动它以使该拨号保留并重新使用;但更重要的是,我想提供一个简单的性能可扩展服务,并获得良好的理解。 (A)也许整个设计是不正确的,并且一旦收到指令“ ack”,service1应该立即返回,进行加法运算,然后将下一个请求发送给sercice2,后者确定答案是否是主要的;而且,service2只是以收到请求的确认作为响应。一旦service2确定了准备金,就会通过应答呼叫客户。 如果上面的(A)是更好的方法,那么仍然请解释下面的问题;处理多个客户端时会发生什么?对“ Listen”的调用会执行,“阻止或不阻止”等操作。

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)
    }
}

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.

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.