分享到

简介

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

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

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

打开数据库连接涉及哪些步骤?

在我们通常讨论连接管理和专门讨论连接池之前,仔细看看数据库连接过程中发生的事情可能会有所帮助。

对于客户端应用程序打开到数据库的连接,必须发生许多令人惊讶的步骤。对于每个连接,必须发生以下部分或全部步骤

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

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

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

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

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

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

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

在没有中间调解组件的情况下,你将被迫考虑

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

这些选择本身不一定是负面的,但对于我们在此描述的拥塞类型来说,它们可能有点过分。连接池是一种替代方案,它提供帮助你用当前拥有的资源做更多事情。

什么是连接池?

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

如前所述,在形成连接时,在数据库服务器实际运行查询之前,必须执行相当长的一系列操作。连接池器尝试通过在初始查询后保持连接打开并重用它来运行其他查询,从而分摊这些操作的成本。

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

连接池如何工作?

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

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

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

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

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

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

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

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

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

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

有许多可用于不同数据库系统的连接池器。我们可以看看一些可用于 PostgreSQL 的实现,以更好地了解不同的解决方案如何解决问题。

pgbouncer

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

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

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

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

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

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

pgpool

另一个 PostgreSQL 连接池器是 pgpool-II,通常简称为 pgpool。虽然 pgbouncer 是一个轻量级工具,专门专注于连接池,但 pgpool 提供了更多相关的附加功能。

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

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

结论

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

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

关于作者
Justin Ellingwood

Justin Ellingwood

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