分享到

介绍

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

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

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

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

在我们一般性地讨论连接管理和专门讨论连接池之前,仔细看看数据库连接期间发生了什么可能很有帮助。

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

  • 定位数据库服务器的 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、基础设施和开发人员工具的文章。他目前与妻子和两只兔子住在柏林。他通常不必以第三人称写作,这对所有相关方来说都是一种解脱。