【问题标题】:Go gRPC simple service Asynchronous and Synchronous explanationGo gRPC 简单服务异步和同步解释
【发布时间】:2018-08-20 23:50:07
【问题描述】:

我正在尝试将 GoLang“Go”与 gRPC 一起理解,并使简单的服务具有可扩展性。

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

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

底层调用似乎是“Invoke”,我相信它是同步的,一旦收到结果,调用就会返回。

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

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

这是一个简单的服务器,我不确定这是否有效: 我知道getprime 函数每次都会拨号, 并且这可能会被移动以使这个表盘持续存在并被重新使用;但更重要的是,我想做一个简单的高性能可扩展服务并获得一个很好的理解。

(A) 也许整个设计不正确, service1 应该只是返回 收到“ack”指令后,立即进行加法并将下一个请求发送到 sercice2,确定答案是否为素数;再次 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)
    }
}

【问题讨论】:

  • Go 通常不需要异步 API,您可以使用 goroutine 管理并发。
  • 或者,如果您询问如何处理并发请求,这就是 grpc.Server 所做的。除非您自己实施,否则您无需担心。
  • 谢谢吉姆;那么回调呢,gRPC 调用你的 gRPC 方法实现来执行一些操作。在我的示例中,它是计算方法。如果要执行长时间运行的操作,您是否需要在此方法中使用通道和执行例程?
  • 恐怕我还是不明白。 Go grpc API 不是围绕回调构建的。如果要进行长时间运行的操作,请进行长时间运行的操作。通道和 goroutine 只是语言的结构——这有点像问你是否会使用函数和变量来执行长时间运行的操作。
  • 如果你问 Go gRPC 服务器是否会处理并发连接;是的,它会的,如果没有,它就不会很好地工作。

标签: go grpc


【解决方案1】:

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

关于性能:

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

关于(A):服务处理程序对另一个服务器进行 RPC 调用并等待其响应然后返回的情况并不少见。 请注意,服务器无法调用客户端。

【讨论】:

  • 您能否详细说明第(2)点。请纠正我的理解...这是否意味着“网络/网络服务器”为每个连接的客户端创建一个 go 例程,该例程调用 gRPC 存根实现 ["func (s *service) Calculate(ctx contex...." ]. 这些“线程”调用不会相互阻塞,也不需要同步阻塞。gRPC 存根方法有自己的“局部变量堆栈”。这些 gRPC 方法调用是隔离的,不会相互影响:不阻塞无需担心本地共享状态。
  • 你的评论基本上是对的,尽管如果你让你的服务器做一些更复杂的事情,你可能会“担心共享状态”,例如如果您在任何地方添加全局内存缓存。人们使用来自sync module 的工具来协调对共享数据的访问。
  • 我想强调你需要学会运行你自己的测试才能在这种事情上走得更远。编写一个测试程序来查看“如果我在这里添加time.Sleep(10*time.Second) 会发生什么?”可以在几分钟内得到答案,而不是在这里等一夜等待有人回答——不得不等一夜听起来就像过去大型机时代人们必须做的事情!
  • 添加到@twotwotwo:是的,这是正确的。但是,请记住,连接到服务器和进行 RPC 是两件不同的事情,应该被认为是不同的。并非每次调用 gRCP 存根都会导致建立连接。但除了你的理解是正确的之外,对服务器端服务处理程序的每次调用都来自不同的 goroutine。
【解决方案2】:

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

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

可能添加显式go 语句的地方是如果您想从发出 RPC 调用的特定函数中执行其他操作。例如,如果您想一次发出多个 RPC 调用,然后等待它们的所有结果都进来,您可以在 examples of fan-out calls 之后编写代码。

【讨论】:

  • 感谢您的解释:越来越清晰了。请验证我的理解:(1) 方法 "func (s *service) Calculate(ctx context.Context, req *pb.Instruction)" = 如果我们故意添加 10 分钟的睡眠(如果数字是素数),那么会得到不是素数的结果不会被之前尚未处理的素数请求延迟? (2) “使远程调用在继续之前等待回复的函数” = 客户端对服务器的调用(gRPC 服务)。 (3) “服务器通常是多线程的” = 服务器在处理请求时不会阻塞。
  • 1) 是的——你很容易为此编写一个测试。 2)是的,等待在客户端。 3) 是的,一个待处理的请求不会阻止服务器处理其他请求。如果这里的术语仍然令人困惑,您可能需要查看有关线程的材料,例如 these lecture notesthe wiki pagethe Tour of Go 的 goroutines 位或 Go by ExampleGoroutines 开始。
  • 查看其他人的代码(例如在 GitHub 上)并编写更多的测试程序也可能会有所帮助,尤其是当问题更多的是“什么是库 XYZ 的并发模型?”时而不是“并发原语一般如何工作?”
猜你喜欢
  • 2019-01-16
  • 2018-01-11
  • 1970-01-01
  • 1970-01-01
  • 2020-11-13
  • 2021-10-16
  • 2016-05-12
  • 1970-01-01
  • 2011-10-08
相关资源
最近更新 更多