Go gRPC 简单服务异步与同步讲解

Way*_*yne 6 go grpc

我试图将 GoLang "Go" 与 gRPC 一起理解,并使简单的服务可扩展。

假设我有一个 client1 调用 service1(添加数字),该 service1 调用 service2(确定结果是否为素数),并且 service2 将结果返回给 service1,然后 service1 将结果全部通过 gRPC 返回给 client1。

当我使用协议缓冲区“proto3”并通过 protoc 生成 Go 代码时。我得到了以一种特定方式调用服务的生成方法。我认为异步调用方法“Go”没有区别。

并且底层调用似乎是“调用”,我认为它是同步的,一旦接收到结果,调用就会返回。

如何使 service1 成为“高性能”,我知道我可以在集群中运行它并拥有副本,但这意味着我只能根据集群中的实例数量为客户端提供服务。

我想要一个“单一”服务能够为多个客户端(例如 1000)提供服务。

这是一个简单的服务器,我不确定这是否有效:我确实知道该getprime功能每次都会拨号,并且可能会移动它以使此拨号持续并重新使用;但更重要的是我想制作一个简单的高性能可扩展服务并得到一个很好的理解。

(A) 也许整个设计是不正确的,service1 应该在收到指令“ack”后立即返回,进行加法并将下一个请求发送到 sercice2,以确定答案是否为质数;再次 service2 只是响应收到请求的确认。一旦服务 2 确定了素数,就会向客户端发出呼叫并得到答复。

如果上面的(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)
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 5

谢谢你的提问。gRPC-Go 确实只是同步的;那就是你的一元 RPC(你的例子中的那个)只有在 RPC 完成时才会返回(从服务器得到响应)。

关于性能:

  1. Dial 操作会建立一个可能很昂贵的底层连接。所以每次调用 getprime 时都这样做是不明智的。更好的方法是创建一个客户端,保留它并调用其上的主要服务器。这种方式只有第一次 RPC 会产生连接成本。
  2. 对于服务器获取的每个 RPC 请求,我们启动一个 goroutine 来处理该请求。所以总的来说,这应该可以很好地扩展。

关于(A):服务处理程序向另一台服务器发出 RPC 调用并在返回之前等待其响应的情况并不少见。请注意,服务器无法调用客户端。


two*_*two 5

用 JimB 的回答来说:“同步”意味着进行远程调用的函数在继续之前等待答复,而不是整个服务器或客户端都这样做。服务器通常是多线程的,即使在处理同步调用时也是如此;它可以在响应第一个呼叫的同时接受并处理第二个呼叫。

同样,如果客户端有多个并发任务,每个任务都有一个正在运行的 gRPC 调用,则不会阻止该进程。此类客户端可能包括net/http为最终用户提供服务的服务器,或处理多个 RPC 的 gRPC 服务器。

如果您想从进行 RPC 调用的特定函数中执行其他操作,则可以添加显式语句。go例如,如果您想一次发出多个 RPC 调用,然后等待所有结果传入,您可以按照扇出调用示例编写代码。