本页面介绍了 Spanner 中会话的高级概念, 包括创建客户端库时的会话最佳实践 使用 REST 或 RPC API,或使用 Google 客户端库。
会话概览
会话代表与 Spanner 数据库的通信渠道 服务。会话用于执行读取、写入或修改 存储在 Spanner 数据库中。每个会话只应用于单个数据库。
会话可以一次执行一个或多个事务。时间 执行多项事务,则该会话称为多路复用会话。
独立读取、写入和查询在内部使用一个事务。
会话池的性能优势
创建会话的开销很大。为了避免每次 数据库操作发生后,客户端应保留一个会话池,即一个池, 可用的会话总数。该池应存储现有会话 收到请求后,系统会返回相应类型的会话,并处理清理 未使用会话的比例有关如何实现会话池的示例,请参阅 一个 Spanner 客户端库的源代码,例如 Go 客户端库或 Java 客户端库。
会话设计为长期有效,因此在会话用于 数据库操作,客户端应将会话返回池以便重复使用。
gRPC 渠道概览
Spanner 客户端使用 gRPC 通道进行通信。一个 gRPC 通道类似于 TCP 连接。一个 gRPC 通道可以处理 最多 100 个并发请求。这意味着应用至少需要 gRPC 通道数与应用将会的并发请求数量相同 除以 100。
执行以下操作时,Spanner 客户端会创建一个 gRPC 通道池 创建它。
使用 Google 客户端库时的最佳做法
下面介绍了使用 Google 客户端时的最佳实践 库。
配置池中的会话数和 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
应用使用的会话数等于 并发事务处理。您应该将 仅在您希望使用单个应用实例时采用默认会话池设置 可以执行比默认会话池可以处理的更多并发事务。
对于高并发应用,建议采用以下做法:
- 将
MinSessions
设置为 单个客户端将要执行什么操作 - 将
MaxSessions
设置为 单个客户端可以执行的 - 如果预期并发未更改,则设置
MinSessions=MaxSessions
大量内存。这可以防止会话池 进行伸缩。增加或减少会话池也会消耗一些 资源。 - 将
NumChannels
设置为MaxSessions / 100
。一个 gRPC 通道最多可处理 同时处理 100 个请求。如果您观察到高尾特性,请提高此值 延迟时间(第 95 和第 99 延迟时间),因为这可能表示 gRPC 频道拥塞。
增加活跃会话的数量会占用 Spanner 数据库服务和客户端库中。增加 超出应用实际需要的会话数可能会降低 系统性能。
增加会话池而不是增加客户端数量
应用的会话池大小决定了 单个应用实例可执行的事务。增加会话 池大小超出了单个应用实例可以允许的最大并发数 标识名。如果应用收到大量请求 就会将请求加入队列 在等待会话可用期间发生。
客户端库使用的资源如下:
- 每个 gRPC 通道使用一个 TCP 连接。
- 每次 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。您可以将
的 WriteSessionsFraction 字段的
SessionPoolOptions
。
Go
所有 Go 会话都相同。没有只读或只读写会话。
Java
所有 Java 会话都相同。没有只读或只读写会话。
Node.js
所有 Node.js 会话都相同。没有只读或只读写会话。
PHP
所有 PHP 会话都是相同的。没有只读或只读写会话。
Python
Python 支持四种不同的会话池类型,可用于管理读取和读写会话。
Ruby
Ruby 的默认写入会话比例是 0.3。您可以使用 client 初始化方法更改比例。
创建客户端库或使用 REST/RPC 时的最佳做法
下面介绍了在客户端中实现会话的最佳实践 库,或者通过 REST 或 RPC API。
这些最佳做法仅在您开发客户端库或使用 REST/RPC API 的情况下适用。如果您使用的是 Google Workspace 中的 请参阅 使用 Google 客户端库时的最佳做法。
创建会话池并确定其大小
要确定客户端进程的最佳会话池大小,请将 将下限设置为预期的并发事务数量,并将上限设置为 绑定到初始测试编号(例如 100)。如果上限不够,请提高上限。增加活跃会话的数量会消耗额外的资源 因此未能清理未使用的会话 可能会降低性能对于使用 RPC API 的用户,我们建议每个 gRPC 通道的会话数不超过 100。
处理已删除的会话
可以通过以下三种方式删除会话:
- 客户端可以删除会话。
- Spanner 数据库服务可以在会话 空闲超过 1 小时。
- 如果该会话发生以下情况,Spanner 数据库服务可能会删除该会话: 超过 28 天。
尝试使用已删除的会话会导致 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 可能会中止会话上正在进行的事务,并导致 在更新会话元数据时为数据库增加额外负载。少数连接偶尔发生变化是无关紧要的,但应避免同时更改大量连接的情况。如果您使用代理 之间,您应该保持连接稳定性 。
监控活跃会话
您可以使用 ListSessions
命令监控数据库中的活动会话
从命令行、REST API 或 RPC API 运行。
ListSessions
显示给定数据库的活动会话。如果您需要查明会话泄漏的原因,这一命令非常有用。(会话泄露是指
但并未返回会话池以便重复使用。)
通过 ListSessions
,您可以查看有关当前会话的元数据,包括
会话的创建时间和上次使用会话的时间。在排查会话问题时,分析此数据可以为您指明正确的方向。如果大多数
正在进行的会话没有最近的approximate_last_use_time
,这可能
表明您的应用没有正确重复使用会话。请参阅
如需详细了解 approximate_last_use_time
字段,请参阅 RPC API 参考文档。
请参阅 REST API 参考文档、RPC API 参考文档或 gcloud
命令行工具参考文档,详细了解如何使用 ListSessions
。
自动清理会话泄漏
使用会话池中的所有会话后,每个新事务 会等到会话返回池。何时创建会话 但不会返回到会话池进行重复使用,这种情况就称为会话泄漏。 出现会话泄漏时,等待打开会话的事务会卡住 并屏蔽应用会话泄漏通常是由以下原因导致的: 长期运行的有问题的事务 错误。
您可以设置会话池来自动解决这些问题 无效交易。启用客户端库可自动 解决非活动状态转换,从而发现有问题的事务, 可能会导致会话泄露,将其从会话池中移除, 并将其替换为新会话。
Logging 还可以帮助识别这些有问题的事务。 如果启用了日志记录功能,则默认情况下,当日志数量超过 100 个时, 您的会话池中有 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
如需接收警告日志并移除不活跃的事务,请使用
InactiveTransactionRemovalOptions
会员价为 SessionPoolConfig
。
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)
多路复用会话
通过多路复用会话,您可以创建不限数量的并发请求 。多路复用会话具有以下优势:
- 降低了后端资源要求。例如,他们会避免会话 与会话所有权维护相关的维护活动 垃圾回收。
- 空闲时不需要 keep-alive 请求的长期会话。
- 典型的客户端库可以为每个客户端使用一个多路复用会话。通过 对于使用多路复用技术的客户端,正在使用的常规会话数较少 与仅使用常规 会话。
- 无需仅对一个 gRPC 通道具有亲和性。客户端可以发送请求 同一多路复用会话的多个通道上。
多路复用会话支持以下各项:
- Java 和 Go 客户端库”。
- 依赖于 Java 和 Go 客户端库的 Spanner 生态系统工具, 例如 PGAdapter、JDBC、Hibernate、database/sql 驱动程序和 GORM。
- 适用于只读事务的 Spanner API
您可以使用 OpenTelemetry 指标查看流量的拆分情况
现有会话池与多路复用会话之间分配的值。OpenTelemetry
指标过滤条件 is_multiplexed
,在设置为
true
。
JDBC 和 PG 适配器默认启用多路复用会话。适用于 Java 和 Go 客户端库,则此功能默认处于停用状态。您可以使用 Java 客户端库或 Go 客户端库,以启用 多路复用会话如需使用 Java 或 Go 启用多路复用会话,请参阅 启用多路复用会话。
启用多路复用会话
要使用 Java 或 Go 客户端启用多路复用会话,请将
GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS
至 true
。
export GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS=TRUE
查看常规会话和多路复用会话的流量
OpenTelemetry 使用 is_multiplexed
过滤条件,以显示
多路复用会话要查看常规会话,请将此过滤条件设置为 true to view multiplexed sessions
and
false。
- 按照 Spanner OpenTelemetry 准备工作 部分。
前往 Metrics Explorer。
在指标下拉菜单中,按
generic
进行过滤。点击 Generic Task(常规任务),然后导航到 Spanner > Spanner/num_acquired_sessions。
在过滤条件字段中,从以下选项中进行选择:
a.
is_multiplexed = false
可查看常规会话。 b.is_multiplexed = true
以查看多路复用会话。下图显示了包含多路复用会话的 Filter 选项 已选择。
如需详细了解如何将 OpenTelemetry 与 Spanner 搭配使用,请参阅 利用 OpenTelemetry 普及 Spanner 可观测性 使用 OpenTelemetry 检查 Spanner 组件中的延迟时间。