分享至

简介

虽然开发和预演环境可以帮助您预测在生产中将面临的许多情况,但有些挑战只有在规模扩大后才会开始显现。数据库连接管理就属于这一范畴:来自客户端实例的请求数量可能很快超出数据库软件支持的连接限制。

需要连接管理策略和工具来解决这种资源争用,以防止长时间的排队、失败的请求和影响用户的错误。**连接池**是一种围绕部署中间排队系统来管理和回收数据库连接的策略,通常被成功地用于缓解这些问题。

在本指南中,我们将讨论什么是连接池,它旨在解决哪些特定问题,以及它是如何工作的。我们将介绍一些流行的实现作为代表性示例,并讨论当客户端请求超出数据库可用连接时,它们如何改变客户端的行为。

打开数据库连接涉及哪些操作?

在泛泛地谈论连接管理和具体地谈论连接池之前,仔细研究数据库连接过程中发生的事情可能会有所帮助。

对于客户端应用程序来说,打开一个数据库连接,需要执行令人惊讶的许多步骤。对于每个连接,必须执行以下部分或全部步骤:

  • 定位数据库服务器 IP 地址所需的任何 DNS 查找
  • 执行建立到服务器的 TCP 连接所需的三次握手
  • 通过TLS 握手协商并启用连接加密
  • 与数据库软件交换偏好和要求以建立会话参数
  • 执行数据库身份验证检查以确定客户端身份
  • 执行初始授权检查以确定客户端是否可以访问请求的数据库对象
  • 执行实际查询并返回结果
  • 关闭数据库会话、TLS 加密和 TCP 连接

连接数量如何影响数据库服务器资源?

除了完成上述所有操作所需的时间外,每个连接还需要资源来建立和维护。例如,在 PostgreSQL 中,一些测试工作负载显示每个连接使用 1.5-14.5MB 内存

CPU 使用率也随连接数量的增加而上升,因为服务器必须管理每个新连接的状态。随着更多内存和 CPU 用于管理连接,它们可能开始影响其他方面,例如事务的执行速率。管理大量连接的开销开始干扰数据库系统最佳缓存结果的能力,并降低其执行有用工作的能力。

连接数量如何影响客户端应用程序?

尽管上一段描述了数据库服务器必须支付的连接成本,但连接对客户端也有直接影响。数据库服务器只能配置为接受特定数量的连接。当达到该限制时,额外的连接请求将被拒绝。

这意味着,默认情况下,您的客户端代码需要实现逻辑以通过指数退避算法重复请求来处理这些失败。然而,更重要的是,您的客户端可能被迫频繁使用该功能,从而导致查询停滞、延迟以及可能迅速波及到用户的问题。

如果没有一个中间调解组件,您将被迫考虑:

  • 增加数据库服务器的连接限制(影响数据库服务器的内存和 CPU 使用率以及事务速率),
  • 扩展数据库以分配更多内存、CPU 或网络容量,或
  • 扩展数据库以将请求分布到更多机器上

这些选择本身不一定是负面的,但对于我们在此描述的拥塞类型来说,它们可能显得过度。连接池是一种替代方案,它有助于您利用现有资源做更多事情。

什么是连接池?

连接池是一种策略,它涉及为多个请求重复使用数据库连接,而不是在查询解决后立即关闭它们。通常,这是通过在数据库服务器及其客户端应用程序之间引入一个称为连接池器的软件来实现的,该软件负责管理两者之间的连接。

如前所述,在建立连接时,需要执行相当长的一系列操作,然后数据库服务器才能实际运行查询。连接池器试图通过在初始查询后保持连接打开并重用它来运行其他查询来分摊这些操作的成本。

在此系统中,客户端连接到连接池器而不是直接连接到数据库。客户端将池器视为数据库本身,池器解释查询以将适当的连接交给客户端。需要注意的是,客户端和池器之间打开和关闭连接仍然存在开销。然而,这些连接通常开销较低,因为大部分繁重过程发生在建立与数据库本身的连接时。

连接池如何工作?

连接池器负责代表客户端打开、关闭和维护到数据库的连接。它通过遵循类似于缓存系统可能使用的算法来实现。

当客户端连接到连接池器并请求连接时,池器会快速评估请求特征。它可能会查看诸如数据库用户、将执行的具体操作、加密类型或访问的数据库对象等信息。

一旦获得这些信息,它会查看其可用连接池,以查看是否有任何现有连接可用于运行新请求。如果找到合适的可用连接,它会将其交给客户端,并允许客户端通过该连接运行其查询。如果池中不存在适合运行新请求的连接,它将使用所需参数打开一个新连接到数据库,并将其交给客户端。

客户端照常使用连接执行其查询。当查询完成后,池器不会终止连接,而是将连接放回池中,以便后续查询可以重复使用。池器可以根据它选择的任何算法(例如,自建立以来的时间、自上次使用以来的时间等)异步地对其池中的连接进行垃圾回收。

内部池化和外部池化有什么区别?

广义而言,连接池是指在多个请求过程中维护连接的算法。这可以在客户端应用程序内部实现,也可以使用外部工具或服务进行外部实现。

连接池的内部实现通常是数据库驱动程序、ORM(对象关系映射器)或其他可能集成到客户端应用程序中的数据库客户端的功能。这些解决方案通过维护到数据库服务器的长期运行连接并将其用于代码库中的多个查询来提供连接池的一些好处。

虽然内部连接池很有用,但它确实有一些实际限制。每个正在执行的应用程序实例通常必须维护自己的连接池。这影响了连接在查询之间共享和重用的广度,因为每个连接池只服务于单个应用程序实例。

另一种解决方案是实现外部连接池。这种方法部署了一个独立的软件,可以为多个客户端实例进行通信和池化连接。虽然这种部署场景确实引入了额外的网络跳数,但它通常提供了额外的可扩展性和灵活性。连接池器可以与数据库服务器一起部署以服务许多不同的客户端,也可以与客户端应用程序一起部署以服务在单个服务器上运行的任何应用程序实例。

有哪些常见的外部连接池?

有许多适用于不同数据库系统的连接池。我们可以通过查看一些适用于 PostgreSQL 的实现,来更好地理解不同的解决方案如何处理这个问题。

pgbouncer

也许 PostgreSQL 最著名的连接池是 pgbouncer

pgbouncer 创建于 2007 年,专注于为管理 PostgreSQL 连接提供轻量级池化机制。它在部署位置和执行池化的具体方式方面都提供了很大的灵活性。

对于客户端连接是短生命周期的场景,该项目建议将 pgbouncer 部署在将执行客户端代码的 Web 服务器上。将池器与客户端软件放置在一起可以使两者之间的连接使用比 TCP 更轻量级的机制,从而减少延迟。对于需要将来自许多不同客户端的连接池化在一起的场景,您可以将其与数据库服务器一起部署。

使用 pgbouncer 时最重要的决定之一是选择要使用的池化模式。三个可用选项是:

  • 事务池化:客户端的每次事务完成后,连接都会被撤销。对于任何后续事务,连接将再次分配。这允许 pgbouncer 在客户端可能在事务之间执行其他操作时快速回收连接。
  • 会话池化:连接在客户端与池器连接期间分配给客户端。这意味着每个客户端连接都与一个专用的数据库连接配对。一旦客户端会话结束,连接仍然会被重用,但同时可以使用池器的客户端数量会大大减少。
  • 语句池化:连接被分配用于执行单个语句。这导致连接的快速分配和解除分配,从而允许许多客户端使用有限数量的连接,但在某些情况下可能会破坏事务语义并导致意外行为。

在大多数情况下,事务池化在回收空闲连接、管理合理数量的客户端以及维护数据库会话和事务语义方面的预期行为方面提供了最佳平衡。

pgpool

另一个 PostgreSQL 连接池是 pgpool-II,通常简称为 pgpool。虽然 pgbouncer 是一个专门用于连接池的轻量级工具,但 pgpool 提供了更广泛的相关功能选择。

除了连接池,pgpool 还支持在多个后端数据库实例之间进行查询负载均衡,并拥有一个看门狗服务,以实现自动故障转移的高可用性操作。此外,它还提供了一个管理 GUI,这对于某些部署场景非常有用。

尽管具有这些高级功能,但 pgpool 在实际管理连接池方面通常被认为功能有限。虽然 pgbouncer 允许三种池化模式,但 pgpool 只能以相当于会话模式的方式运行,这意味着只有在客户端断开连接时,连接才会被重新分配。这降低了 pgpool 可以处理的客户端数量。

结论

在本指南中,我们探讨了连接池的概念以及如何利用它来帮助减少数据库负载和资源消耗。我们概述了连接在客户端和数据库之间建立成本高昂的一些原因,并描述了重用连接如何降低后续查询的成本。之后,我们讨论了连接池器实际的工作原理,并查看了 PostgreSQL 生态系统中一些代表性的池器示例。

随着应用程序规模的扩大和查询负载变得更加复杂,数据库连接限制可能会迅速引发问题。虽然完全消除与连接管理相关的开销是不可能的,但连接池器是维护性能和增加数据库可服务客户端数量的宝贵工具。

关于作者
Justin Ellingwood

Justin Ellingwood

Justin 自 2013 年以来一直撰写有关数据库、Linux、基础设施和开发者工具的文章。他目前与妻子和两只兔子住在柏林。他通常不必用第三人称写作,这对所有相关方来说都是一种解脱。
© . All rights reserved.