操作方法:使用 gRPC 调用服务

使用 servcie invocation 在服务之间调用

本文介绍如何使用 Dapr 通过 gRPC 连接服务。

通过使用 Dapr 的 gRPC 代理功能,您可以使用现有的基于 proto 的 gRPC 服务,并让流量通过 Dapr sidecar。 这样做可以为开发人员带来以下Dapr服务调用的好处:

  1. 双向认证
  2. 追踪
  3. Metrics
  4. 访问列表
  5. 网络层弹性
  6. 基于 API 令牌的身份验证

Dapr 允许代理各种 gRPC 调用,包括一元和 基于流 的调用。

步骤 1:运行 gRPC 服务器

下面的示例取自“hello world” grpc-go示例。 尽管这个例子是用Go语言编写的,但是相同的概念适用于所有支持gRPC的编程语言。

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "net"
  6. "google.golang.org/grpc"
  7. pb "google.golang.org/grpc/examples/helloworld/helloworld"
  8. )
  9. const (
  10. port = ":50051"
  11. )
  12. // server is used to implement helloworld.GreeterServer.
  13. type server struct {
  14. pb.UnimplementedGreeterServer
  15. }
  16. // SayHello implements helloworld.GreeterServer
  17. func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
  18. log.Printf("Received: %v", in.GetName())
  19. return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
  20. }
  21. func main() {
  22. lis, err := net.Listen("tcp", port)
  23. if err != nil {
  24. log.Fatalf("failed to listen: %v", err)
  25. }
  26. s := grpc.NewServer()
  27. pb.RegisterGreeterServer(s, &server{})
  28. log.Printf("server listening at %v", lis.Addr())
  29. if err := s.Serve(lis); err != nil {
  30. log.Fatalf("failed to serve: %v", err)
  31. }
  32. }

此 Go 应用程序实现了Greeter proto服务,并暴露了SayHello方法。

使用 Dapr CLI 运行 gRPC 服务器

  1. dapr run --app-id server --app-port 50051 -- go run main.go

使用Dapr CLI,我们正在为应用server分配一个唯一的ID,使用--app-id标志。

步骤 2: 调用服务

以下示例演示如何从 gRPC 客户端使用 Dapr 发现 Greeter 服务。 请注意,客户端不是直接在端口 50051 调用目标服务,而是通过端口 50007 调用其本地 Dapr sidecar,然后提供服务调用的所有功能,包括服务发现、跟踪、mTLS 和重试。

  1. package main
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. "google.golang.org/grpc"
  7. pb "google.golang.org/grpc/examples/helloworld/helloworld"
  8. "google.golang.org/grpc/metadata"
  9. )
  10. const (
  11. address = "localhost:50007"
  12. )
  13. func main() {
  14. // Set up a connection to the server.
  15. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
  16. if err != nil {
  17. log.Fatalf("did not connect: %v", err)
  18. }
  19. defer conn.Close()
  20. c := pb.NewGreeterClient(conn)
  21. ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  22. defer cancel()
  23. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
  24. r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Darth Tyrannus"})
  25. if err != nil {
  26. log.Fatalf("could not greet: %v", err)
  27. }
  28. log.Printf("Greeting: %s", r.GetMessage())
  29. }

下面这行告诉 Dapr 发现并调用名为 server 的应用:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")

gRPC 支持的所有语言都允许添加元数据。 以下是几个例子:

  1. Metadata headers = new Metadata();
  2. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
  3. GreeterService.ServiceBlockingStub stub = GreeterService.newBlockingStub(channel);
  4. stub = MetadataUtils.attachHeaders(stub, header);
  5. stub.SayHello(new HelloRequest() { Name = "Darth Malak" });
  1. var metadata = new Metadata
  2. {
  3. { "dapr-app-id", "server" }
  4. };
  5. var call = client.SayHello(new HelloRequest { Name = "Darth Nihilus" }, metadata);
  1. metadata = (('dapr-app-id', 'server'),)
  2. response = stub.SayHello(request={ name: 'Darth Revan' }, metadata=metadata)
  1. const metadata = new grpc.Metadata();
  2. metadata.add('dapr-app-id', 'server');
  3. client.sayHello({ name: "Darth Malgus" }, metadata)
  1. metadata = { 'dapr-app-id' : 'server' }
  2. response = service.sayHello({ 'name': 'Darth Bane' }, metadata)
  1. grpc::ClientContext context;
  2. context.AddMetadata("dapr-app-id", "server");

使用 Dapr CLI 运行客户端

  1. dapr run --app-id client --dapr-grpc-port 50007 -- go run main.go

查看遥测

如果您在本地运行 Dapr 并安装了 Zipkin,请在 http://localhost:9411 打开浏览器并查看客户端和服务器之间的跟踪。

部署到 Kubernetes

在 deployment 中设置以下 Dapr 注解:

  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4. name: grpc-app
  5. namespace: default
  6. labels:
  7. app: grpc-app
  8. spec:
  9. replicas: 1
  10. selector:
  11. matchLabels:
  12. app: grpc-app
  13. template:
  14. metadata:
  15. labels:
  16. app: grpc-app
  17. annotations:
  18. dapr.io/enabled: "true"
  19. dapr.io/app-id: "server"
  20. dapr.io/app-protocol: "grpc"
  21. dapr.io/app-port: "50051"
  22. ...

dapr.io/app-protocol: "grpc" 注解告诉 Dapr 使用 gRPC 调用应用。

如果您的应用程序使用TLS连接,您可以使用app-protocol: "grpcs"注解告知Dapr通过TLS调用您的应用程序(完整列表在这里)。 请注意,Dapr 不会验证应用程序提供的 TLS 证书。

命名空间

当运行于支持命名空间的平台,您需要在应用ID中包含目标应用的命名空间:myApp.production

例如,在不同的命名空间上调用 gRPC 服务器:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server.production")

查看有关命名空间的更多信息,请参阅跨命名空间API规范

步骤 3:跟踪和日志

上面的示例显示了如何直接调用本地或 Kubernetes 中运行的其他服务。 Dapr 输出指标、跟踪和日志记录信息,允许您可视化服务之间的调用图、日志错误和可选地记录有效负载正文。

有关跟踪和日志的更多信息,请参阅可观察性篇文章。

流式RPC的代理

当使用 Dapr 作为代理流处理 RPC 进行 gRPC 调用时,你必须设置一个额外的元数据选项 dapr-stream,其值为 true

例如:

  1. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
  2. ctx = metadata.AppendToOutgoingContext(ctx, "dapr-stream", "true")
  1. Metadata headers = new Metadata();
  2. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
  3. Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-stream", "true");
  1. var metadata = new Metadata
  2. {
  3. { "dapr-app-id", "server" },
  4. { "dapr-stream", "true" }
  5. };
  1. metadata = (('dapr-app-id', 'server'), ('dapr-stream', 'true'),)
  1. const metadata = new grpc.Metadata();
  2. metadata.add('dapr-app-id', 'server');
  3. metadata.add('dapr-stream', 'true');
  1. metadata = { 'dapr-app-id' : 'server' }
  2. metadata = { 'dapr-stream' : 'true' }
  1. grpc::ClientContext context;
  2. context.AddMetadata("dapr-app-id", "server");
  3. context.AddMetadata("dapr-stream", "true");

流式传输 gRPC 和弹性

当代理流式gRPC时,由于其长期存在性质,仅在”初始握手”上应用 弹性 策略。 因此:

  • 如果在初始握手之后,流被中断,Dapr 将不会自动重新建立连接。 您的应用程序将收到通知,流已结束,并且需要重新创建它。
  • 重试策略只影响初始连接“握手”。 如果您的弹性策略包括重试,Dapr 将检测到与目标应用程序建立初始连接时的故障,并将重试直到成功(或直到策略中定义的重试次数用尽)。
  • 同样,弹性策略中定义的超时仅适用于初始的“握手”过程。 连接建立后,超时不再影响流。

相关链接

社区示例

观看这个视频,了解如何使用 Dapr 的 gRPC 代理功能: