会话数

本页面介绍 Spanner 中的会话高级概念,包括创建客户端库、使用 REST 或 RPC API 或使用 Google 客户端库时的会话最佳做法。

会话概览

会话代表一种与 Spanner 数据库服务的通信渠道。会话用于执行事务来读取、写入或修改 Spanner 数据库中的数据。每个会话只应用于单个数据库。

会话一次可以执行一个或多个事务。执行多个事务时,会话称为多路复用会话

独立读取、写入和查询在内部使用一个事务。

会话池的性能优势

创建会话的开销很大。为了避免每次进行数据库操作时影响性能,客户端应该保留“会话池”,这是准备就绪可供使用的可用会话池。池应存储现有会话,并响应请求返回适当类型的会话,以及清理未使用的会话。如需查看有关如何实现会话池的示例,请参阅其中一个 Spanner 客户端库(例如 Go 客户端库Java 客户端库)的源代码。

会话设计为长期有效,因此在某会话用于数据库操作之后,客户端应将该会话返回到池,以供重复使用。

gRPC 通道概览

Spanner 客户端使用 gRPC 通道进行通信。一个 gRPC 通道大致相当于一条 TCP 连接。一个 gRPC 通道最多可处理 100 个并发请求。这意味着,应用至少需要与应用将执行的并发请求数除以 100 的值一样多的 gRPC 通道。

Spanner 客户端在您创建时会创建一个 gRPC 通道池。

使用 Google 客户端库时的最佳做法

下文介绍了将 Google 客户端库用于 Spanner 时的最佳做法。

配置池中的会话数和 gRPC 通道数

客户端库在会话池中具有默认会话数,在通道池中具有默认 gRPC 通道数。在大多数情况下,这两种默认值都足够了。以下是每个编程语言的默认会话数下限和上限以及默认 gRPC 通道数。

C++

MinSessions: 100
MaxSessions: 400
NumChannels: 4

C#

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Go

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Java

MinSessions: 100
MaxSessions: 400
NumChannels: 4

Node.js

Node.js 客户端不支持多个 gRPC 通道。因此,建议您创建多个客户端,而不是将单个客户端的会话池大小增加到超过 100 个会话。

MinSessions: 25
MaxSessions: 100

PHP

PHP 客户端不支持可配置数量的 gRPC 通道。

MinSessions: 1
MaxSessions: 500

Python

Python 支持四种不同的会话池类型,可用于管理会话。

Ruby

Ruby 客户端不支持多个 gRPC 通道。因此,建议您创建多个客户端,而不是将单个客户端的会话池大小增加到超过 100 个会话。

MinSessions: 10
MaxSessions: 100

应用使用的会话数等于应用执行的并发事务数。仅当您希望单个应用实例执行的并发事务数超过默认会话池可以处理的数量时,才应修改默认会话池设置。

对于高并发应用,建议如下:

  1. MinSessions 设置为单个客户端将执行的并发事务的预期数量。
  2. MaxSessions 设置为单个客户端可以执行的并发事务数上限。
  3. 如果预期的并发数在应用生命周期内变化不大,请设置 MinSessions=MaxSessions。这样可以防止会话池纵向扩容或纵向缩容。纵向扩容或纵向缩容会话池也会消耗一些资源。
  4. NumChannels 设置为 MaxSessions / 100。一个 gRPC 通道最多可并发处理 100 个请求。如果您观察到高尾延迟(p95/p99 延迟),请提高此值,因为这可能表明 gRPC 通道拥塞。

增加活跃会话数会使用 Spanner 数据库服务和客户端库中的额外资源。如果将会话数增加到超出应用实际需要的数量,可能会降低系统性能。

增加会话池与增加客户端数量

应用的会话池大小决定了单个应用实例可以执行的并发事务数量。不建议将会话池大小增加到超出单个应用实例可以处理的并发数上限。如果应用收到的请求数超出池中的会话数,则请求会排队等待会话可用。

客户端库使用的资源如下:

  1. 每个 gRPC 通道均使用一条 TCP 连接。
  2. 每次 gRPC 调用均需要一个线程。客户端库使用的线程数上限等于应用执行的并发查询数上限。这些线程位于应用用于自身业务逻辑的所有线程之上。

不建议将会话池的大小增加到超出单个应用实例可以处理的线程数上限。而是增加应用实例的数量。

管理写入会话比例

Spanner 会为某些客户端库的读写事务保留一部分会话,称为写入会话比例。如果您的应用耗尽了所有读取会话,那么 Spanner 将会为只读事务使用读写会话。读写会话需要 spanner.databases.beginOrRollbackReadWriteTransaction 权限。如果用户的角色为 spanner.databaseReader IAM 角色,则调用会失败,并且 Spanner 会返回以下错误消息:

generic::permission_denied: Resource %resource% is missing IAM permission:
spanner.databases.beginOrRollbackReadWriteTransaction

对于维持写入会话比例的客户端库,您可以对其进行设置。

C++

所有 C ++ 会话都相同。没有只读或只读写会话。

C#

C# 的默认写入会话比例是 0.2。您可以使用 SessionPoolOptions 的 WriteSessionsFraction 字段更改比例。

Go

所有 Go 会话都相同。没有只读或只读写会话。

Java

所有 Java 会话都相同。没有只读或只读写会话。

Node.js

所有 Node.js 会话都相同。没有只读或只读写会话。

PHP

所有 PHP 会话都是相同的。没有只读或只读写会话。

Python

Python 支持四种不同的会话池类型,可用于管理读取和读写会话。

Ruby

Ruby 的默认写入会话比例是 0.3。您可以使用 client 初始化方法更改比例。

创建客户端库或使用 REST/RPC 时的最佳做法

下文介绍了在 Spanner 客户端库中实现会话或者通过 RESTRPC API 使用会话的最佳做法。

这些最佳做法仅在您开发客户端库或使用 REST/RPC API 的情况下适用。如果您使用的是 Spanner 的 Google Cloud 客户端库之一,请参阅使用 Google 客户端库时的最佳做法

创建会话池并确定其大小

如需确定某客户端进程的会话池的最佳大小,请将下限设置为预期的并发事务数,并将上限设置为初始测试数(例如 100)如果上限不够,请提高上限。增加活跃会话数会使用 Spanner 数据库服务上的额外资源,因此不清理未使用的会话可能会降低性能。对于使用 RPC API 的用户,我们建议每个 gRPC 通道的会话数不超过 100。

处理已删除的会话

可以通过以下三种方式删除会话:

  • 客户端可以删除会话。
  • Spanner 数据库服务可以在会话空闲超过一小时后删除会话。
  • 如果会话存在超过 28 天,Spanner 数据库服务可能会删除该会话。

尝试使用已删除的会话会导致 NOT_FOUND 错误。如果遇到此错误,请创建并使用新会话,将新会话添加到池中,并从池中移除已删除的会话。

使空闲会话保持活跃状态

Spanner 数据库服务保留删除未使用会话的权利。如果您确实需要让空闲会话保持活跃状态,例如,在预计数据库使用近期内会显著增长的情况下,您可以防止会话被删除。执行开销较小的操作(例如,执行 SQL 查询 SELECT 1)可使会话保持活跃状态。如果您有近期并不需要的空闲会话,让 Spanner 删除该会话,然后在下次要用时创建新的会话。

让会话保持活动状态的一种情况是应对数据库上的常规高峰需求。如果每天的上午 9:00 到下午 6:00 大量使用数据库,则应在这段时间内保留一些空闲会话,因为高峰使用期间可能需要这些会话。下午 6:00 之后,您可以让 Spanner 删除空闲会话。在每天上午 9:00 之前,请创建一些新会话,以便它们能够满足预期需求。

另一种情况是,如果您有一个应用使用 Spanner,但必须在使用时避免连接开销。您可以将一组会话保持活动状态以避免连接开销。

向客户端库用户隐藏会话详细信息

如果您正在创建客户端库,请勿将会话曝露给客户端库使用者。让客户端能够进行数据库调用,但无需涉及会话创建和维护的复杂性。有关向客户端库使用者隐藏会话详细信息的客户端库示例,请参阅 Java 版 Spanner 客户端库。

处理非幂等写入事务的错误

没有重试保护的写入事务可能会多次应用变更。 如果某一变更不具有幂等性,则多次应用变更可能会导致失败。例如,即使在写入尝试之前某行不存在,插入操作也可能会失败并出现 ALREADY_EXISTS 错误。如果后端服务器提交了变更但无法将成功信息传达给客户端,则可能发生这种情况。在这种情况下,可能会重试变更,导致 ALREADY_EXISTS 失败。

在实现您自己的客户端库或使用 REST API 时,可以采用以下方法来应对这种情况:

  • 设计您的写入代码结构,使其具有幂等性。
  • 对写入使用重试保护。
  • 实现一个执行“upsert”逻辑的方法:若是新的则插入,若已存在则更新。
  • 代替客户端处理错误。

保持稳定连接

为获得最佳性能,用于承载会话的连接应保持稳定。当承载会话的连接发生更改时,Spanner 可能会取消会话上的活动事务,并在更新会话元数据时为数据库增加少量的额外负载。少数连接偶尔发生变化是无关紧要的,但应避免同时更改大量连接的情况。如果您在客户端和 Spanner 之间使用代理,则应保持每个会话的连接稳定性。

监控活跃会话

您可以从命令行、通过 REST APIRPC API 使用 ListSessions 命令,监控数据库中的活跃会话。ListSessions 可显示给定数据库的活跃会话。如果您需要查明会话泄露的原因,这一命令非常有用。(会话泄露是指系统创建会话,但未将其返回到会话池以重复使用的突发事件。)

借助 ListSessions,您能够查看有关活跃会话的元数据,包括创建会话的时间以及上次使用会话的时间。在排查会话问题时,分析此数据可以为您指明正确的方向。如果大多数活跃会话没有最近的 approximate_last_use_time,这可能表明会话没有被您的应用合理地重复使用。如需详细了解 approximate_last_use_time 字段,请参阅 RPC API 参考文档

如需详细了解如何使用 ListSessions,请参阅 REST API 参考文档RPC API 参考文档gcloud 命令行工具参考文档

自动清理会话泄露

当您使用会话池中的所有会话时,每个新事务都会等待会话返回到池中。如果系统创建了会话,但未将其返回到会话池以供重复使用,则称为会话泄露。 发生会话泄露时,等待打开会话的事务会无限期地卡住,并阻止应用运行。会话泄露通常是由运行时间极长且未提交的有问题的事务引起的。

您可以设置会话池,来自动解决这些不活跃的事务。当您启用客户端库以自动解决不活跃转换时,它会识别可能导致会话泄露的有问题的事务,将其从会话池中移除,并将其替换为新会话。

日志记录还可以帮助识别这些有问题的事务。如果已启用日志记录,当超过 95% 的会话池处于使用状态时,系统会默认共享警告日志。如果您的会话用量超过 95%,则需要增加会话池中允许的会话数上限,否则可能会出现会话泄露。警告日志包含运行时间比预期更长的事务的堆栈轨迹,可帮助识别会话池利用率高的原因。 警告日志的推送取决于您的日志导出器配置。

启用客户端库以自动解决不活跃的事务

您可以启用客户端库以发送警告日志并自动解决不活跃的事务,也可以启用客户端库以仅接收警告日志。

Java

如需接收警告日志并移除不活跃的事务,请使用 setWarnAndCloseIfInactiveTransactions

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnAndCloseIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

如需仅接收警告日志,请使用 setWarnIfInactiveTransactions

 final SessionPoolOptions sessionPoolOptions = SessionPoolOptions.newBuilder().setWarnIfInactiveTransactions().build()

 final Spanner spanner =
         SpannerOptions.newBuilder()
             .setSessionPoolOption(sessionPoolOptions)
             .build()
             .getService();
 final DatabaseClient client = spanner.getDatabaseClient(databaseId);

Go

如需接收警告日志并移除不活跃的事务,请将 SessionPoolConfigInactiveTransactionRemovalOptions 搭配使用。

 client, err := spanner.NewClientWithConfig(
     ctx, database, spanner.ClientConfig{SessionPoolConfig: spanner.SessionPoolConfig{
         InactiveTransactionRemovalOptions: spanner.InactiveTransactionRemovalOptions{
         ActionOnInactiveTransaction: spanner.WarnAndClose,
         }
     }},
 )
 if err != nil {
     return err
 }
 defer client.Close()

如需仅接收警告日志,请使用 customLogger

 customLogger := log.New(os.Stdout, "spanner-client: ", log.Lshortfile)
 // Create a logger instance using the golang log package
 cfg := spanner.ClientConfig{
         Logger: customLogger,
     }
 client, err := spanner.NewClientWithConfig(ctx, db, cfg)

多路复用会话

借助多路复用会话,您可以在单个会话中创建大量并发请求。多路复用会话是您在多个 gRPC 通道中使用的标识符。它不会引入任何额外的瓶颈。多路复用会话具有以下优势:

  • 由于采用了更简单的会话管理协议,后端资源消耗有所降低。例如,它们会避免与会话所有权维护和垃圾回收关联的会话维护活动。
  • 长期有效的会话,在空闲时不需要 keep-alive 请求。

以下项支持多路复用会话:

  • Java 和 Go 客户端库
  • 依赖于 Java 和 Go 客户端库的 Spanner 生态系统工具,例如 PGAdapter、JDBC、Hibernate、database/sql 驱动程序和 GORM。

  • 依赖于 Java 和 Go 客户端库的 Spanner 生态系统工具,例如 PGAdapter、JDBC、Hibernate、database 或 sql 驱动程序以及 GORM。您可以使用 OpenTelemetry 指标查看现有会话池和多路复用会话之间的流量分配情况。OpenTelemetry 有一个指标过滤条件 is_multiplexed,当设置为 true 时,它会显示多路复用会话。

所有类型的事务都支持多路复用会话。

客户端库会每 7 天轮替一次多路复用会话,以防止在过时会话中发送事务。

多路复用会话默认处于停用状态。您必须使用环境变量来启用多路复用会话,然后才能在客户端应用中使用它。如需使用 Java 或 Go 启用多路复用会话,请参阅启用多路复用会话

注意事项

如果您尝试提交空的读取或写入事务主体,或者提交每条查询或 DML 语句都失败的事务,则需要考虑多路复用会话的几种情况。多路复用会话要求您在每个提交请求中添加服务器生成的预提交令牌。对于包含查询或 DML 的交易,必须至少有一个之前成功的查询或 DML 交易,以便服务器向客户端库发回有效令牌。如果没有任何成功的查询或 DML 事务,客户端库会在提交之前隐式添加 SELECT 1

对于仅具有变更的多路复用会话上的读取或写入事务,如果其中一个变更针对的是架构中不存在的表或列,客户端可能会返回 INVALID_ARGUMENT 错误,而不是 NOT_FOUND 错误。

启用多路复用会话

如需在客户端应用中使用多路复用会话,您必须先设置环境变量以启用多路复用会话。

如需启用多路复用会话,请将 GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS 环境变量设置为 TRUE。此标志还会为 ReadOnly 事务启用多路复用会话支持。

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS=TRUE

如需为多路复用会话启用分区操作支持,请将 GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS 环境变量设置为 TRUE

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_PARTITIONED_OPS=TRUE

如需为多路复用会话启用读写事务支持,请将 GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW 环境变量设置为 TRUE

export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS_FOR_RW=True

您必须将 GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS 设置为 TRUE,这是支持多路复用会话中的事务的前提条件。

查看常规会话和多路复用会话的流量

Opentelemetry 具有 is_multiplexed 过滤条件,用于显示多路复用会话的流量。您可以将此过滤条件设置为 true to view multiplexed sessions and false` 以查看常规会话。

  1. 按照 Spanner Opentelemetry 准备工作部分中的步骤设置 Opentelemetry for Spanner。
  2. 前往 Metrics Explorer

    转到 Metrics Explorer

  3. 指标下拉列表中,按 generic 进行过滤。

  4. 点击通用任务,然后依次前往 Spanner > Spanner/num_acquired_sessions

  5. 过滤条件字段中,从以下选项中选择:

    a. is_multiplexed = false,以查看常规会话。 b. is_multiplexed = true,以查看多路复用会话。

    下图显示了已选择多路复用会话的过滤条件选项。

如需详细了解如何将 OpenTelemetry 与 Spanner 搭配使用,请参阅利用 OpenTelemetry 普及 Spanner 可观测性使用 OpenTelemetry 检查 Spanner 组件中的延迟时间

显示 is-multiplexed 过滤条件的 Opentelemetry 信息中心。