使用 gRPC

本页面向希望使用 gRPC 将 Cloud Run 服务与其他服务连接起来(例如在内部微服务之间提供简单的高性能通信)的开发者介绍了特定于 Cloud Run 的详细信息。您可以将所有 gRPC 类型(流式传输或一元)与 Cloud Run 搭配使用。

可能的使用场景包括:

  • 内部微服务之间的通信。
  • 高数据负载(gRPC 使用协议缓冲区,其速度最高可比 REST 调用快七倍)。
  • 您只需要一个简单的服务定义,不需要编写完整的客户端库。
  • 在 gRPC 服务器中使用流式传输 gRPC 来构建响应更快的应用和 API。

如需将您的服务与 gRPC 集成,请执行以下操作:

  • 如果使用流式传输 gRPC,将服务配置为使用 HTTP/2。 HTTP/2 是 gRPC 流式传输的传输方法。
  • 在 proto 文件中定义请求消息和响应,并对其进行编译。
  • 创建一台 gRPC 服务器以处理请求并返回响应:它应该侦听 PORT 环境变量。
  • 创建一个客户端,用于发送请求并处理来自 gRPC 服务器的响应。
  • (可选)添加身份验证。
  • 构建和部署服务。

配置服务以使用 HTTP/2

如果您将 gRPC 与 Cloud Run 搭配使用,Google 建议您将服务配置为使用 HTTP/2。虽然一些简单的 gRPC 功能无需使用 HTTP/2 即可运行,但许多 gRPC 功能(如流式传输和元数据)需要使用 HTTP/2。

在 proto 文件中定义和编译消息

proto 定义中不需要添加额外的内容或特定于 Cloud Run 的内容。与其他任何 gRPC 用法一样,您可以使用 gRPC 协议缓冲区来定义服务以及序列化数据。

创建 gRPC 客户端

使用 gRPC 的客户端中不需要添加额外的内容或特定于 Cloud Run 的内容:请遵循有关在客户端代码中使用服务定义的 gRPC 文档,以及特定于语言的 gRPC 教程中提供的示例客户端。

自动扩缩和负载均衡

Cloud Run 使用 Google 管理的负载均衡器,可在客户端和 Cloud Run 实例之间维持单独的连接。使用 gRPC 时,自动扩缩的行为如下:

  • 来自客户端的 gRPC 连接在边缘负载均衡器处终结。调整 KeepAlive 设置只会影响与负载均衡器的连接,而不会影响 Cloud Run 实例。客户端无法识别实例在何时丢弃。
  • 在缩容期间,负载均衡器会在后端实例关停时向后端实例发送 GOAWAY 消息以关闭连接。
  • 在扩容期间,负载均衡器会创建与后端实例的新连接。所有这些操作对客户端都是透明的。
  • 在自动扩缩期间,许多实例可以启动并多路复用为客户端与代理负载均衡器之间的单个连接。
  • 并发由每个实例的并发请求数上限决定。在流处理中,每个流都会作为一个请求计入并发请求数上限。

侦听 Cloud Run 服务中的 gRPC 请求

Cloud Run 中运行的 gRPC 服务器的唯一特殊要求是监听 PORT 环境变量指定的端口,如以下代码所示:

Go

func main() {
	log.Printf("grpc-ping: starting server...")

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}

	listener, err := net.Listen("tcp", ":"+port)
	if err != nil {
		log.Fatalf("net.Listen: %v", err)
	}

	grpcServer := grpc.NewServer()
	pb.RegisterPingServiceServer(grpcServer, &pingService{})
	if err = grpcServer.Serve(listener); err != nil {
		log.Fatal(err)
	}
}

打开与服务的 gRPC 连接

要打开与服务的 gRPC 连接以便发送 gRPC 消息,您需要指定主机网域(即 Cloud Run 服务的网址或映射到该服务的自定义网域),以及端口 443(即 gRPC 预期使用的端口)。

Go


import (
	"crypto/tls"
	"crypto/x509"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// NewConn creates a new gRPC connection.
// host should be of the form domain:port, e.g., example.com:443
func NewConn(host string, insecure bool) (*grpc.ClientConn, error) {
	var opts []grpc.DialOption
	if host != "" {
		opts = append(opts, grpc.WithAuthority(host))
	}

	if insecure {
		opts = append(opts, grpc.WithInsecure())
	} else {
		// Note: On the Windows platform, use of x509.SystemCertPool() requires
		// go version 1.18 or higher.
		systemRoots, err := x509.SystemCertPool()
		if err != nil {
			return nil, err
		}
		cred := credentials.NewTLS(&tls.Config{
			RootCAs: systemRoots,
		})
		opts = append(opts, grpc.WithTransportCredentials(cred))
	}

	return grpc.Dial(host, opts...)
}

发送不带身份验证的 gRPC 请求

以下示例展示了如何使用按之前所述方法配置的 gRPC 连接来发送不带身份验证的请求。

Go


import (
	"context"
	"time"

	pb "github.com/GoogleCloudPlatform/golang-samples/run/grpc-ping/pkg/api/v1"
	"google.golang.org/grpc"
)

// pingRequest sends a new gRPC ping request to the server configured in the connection.
func pingRequest(conn *grpc.ClientConn, p *pb.Request) (*pb.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	client := pb.NewPingServiceClient(conn)
	return client.Send(ctx, p)
}

使用身份验证发送 gRPC 请求

以下示例展示了如何在服务之间使用身份验证(如果调用服务具有对接收服务的调用方权限)。请注意,此代码会创建一个具有正确身份令牌的授权标头:这是必需的。服务到服务的身份验证中详细介绍了所需的权限和授权标头。

Go


import (
	"context"
	"fmt"
	"time"

	"google.golang.org/api/idtoken"
	"google.golang.org/grpc"
	grpcMetadata "google.golang.org/grpc/metadata"

	pb "github.com/GoogleCloudPlatform/golang-samples/run/grpc-ping/pkg/api/v1"
)

// pingRequestWithAuth mints a new Identity Token for each request.
// This token has a 1 hour expiry and should be reused.
// audience must be the auto-assigned URL of a Cloud Run service or HTTP Cloud Function without port number.
func pingRequestWithAuth(conn *grpc.ClientConn, p *pb.Request, audience string) (*pb.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// Create an identity token.
	// With a global TokenSource tokens would be reused and auto-refreshed at need.
	// A given TokenSource is specific to the audience.
	tokenSource, err := idtoken.NewTokenSource(ctx, audience)
	if err != nil {
		return nil, fmt.Errorf("idtoken.NewTokenSource: %w", err)
	}
	token, err := tokenSource.Token()
	if err != nil {
		return nil, fmt.Errorf("TokenSource.Token: %w", err)
	}

	// Add token to gRPC Request.
	ctx = grpcMetadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token.AccessToken)

	// Send the request.
	client := pb.NewPingServiceClient(conn)
	return client.Send(ctx, p)
}

gRPC 流式传输的示例代码

如需查看示例代码,请参阅 gRPC 基础教程中您所选的语言的 RouteGuide 实现。例如,使用 Go 时,请参阅实现 RouteGuide